I wanted to make a 2D array like structure and support D slice like operations,
but I had surprisingly bad experience.

I quickly copy pasted the example from the docs: https://dlang.org/spec/operatoroverloading.html#array-ops

It's something like this:
struct Array2D(E)
{
    E[] impl;
    int stride;
    int width, height;

    this(int width, int height, E[] initialData = [])
    ref E opIndex(int i, int j)
    Array2D opIndex(int[2] r1, int[2] r2)
    auto opIndex(int[2] r1, int j)
    auto opIndex(int i, int[2] r2)
    int[2] opSlice(size_t dim)(int start, int end)
    @property int opDollar(size_t dim : 0)()
    @property int opDollar(size_t dim : 1)()
}

So basic indexing works fine:
Array2D!int foo(4, 4);
foo[0, 1] = foo[2, 3];

But array copy and setting/clearing doesn't:
int[] bar = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ];
foo[] = bar[];

And I get this very cryptic message:
(6): Error: template `example.Array2D!int.Array2D.opSlice` cannot deduce function from argument types `!()()`, candidates are: (51): `example.Array2D!int.Array2D.opSlice(ulong dim)(int start, int end) if (dim >= 0 && (dim < 2))`

1. WTF `!()()` and I haven't even called anything with opSlice i.e. `a .. b`?

Anyway, it doesn't overload [] with opIndex(), so fine, I add that.
T[] opIndex() { return impl; }

Now I get:
foo[] = bar[]; // or foo[] = bar;
Error: `foo[]` is not an lvalue and cannot be modified

Array copying docs say:
When the slice operator appears as the left-hand side of an assignment expression, it means that the contents of the array are the target of the assignment rather than a reference to the array. Array copying happens when the left-hand side is a slice, and the right-hand side is an array of or pointer to the same type.

2.WTF I do have slice operator left of assignment.
So I guess [] is just wonky named getter (and not an operator) for a slice object and that receives the = so it's trying to overwrite/set the slice object itself.

Next I added a ref to the E[] opIndex():
ref E[] opIndex() { return impl; }

Now foo[] = bar[] works as expected, but then I tried
foo[] = 0;
and that fails:
Error: cannot implicitly convert expression `0` of type `int` to `int[]`

3. WTF. Didn't I just get reference directly to the slice and array copy works, why doesn't array setting?

The ugly foo[][] = 0 does work, but it's so ugly/confusing that I'd rather just use a normal function.

So I added:
ref E[] opIndexAssign(E value) { impl[] = value; return impl; }

And now foo[] = 0; works, but foo[0, 1] = foo[2, 3] doesn't.

I get:
Error: function `example.Array2D!int.Array2D.opIndexAssign(int f)` is not callable using argument types `(int, int, int)`
expected 1 argument(s), not 3

4. WTF. So basically adding opIndexAssign(E value) disabled ref E opIndex(int i, int j). Shouldn't it consider both?

I'm surprised how convoluted this is. Is this really the way it's supposed to work or is there a bug or something?


So what is the best/clear/concise/D way to do these for a custom type?

I was planning for:
foo[] = bar; // Full copy
foo[] = 0; // Full clear
foo[0 .. 5, 1] = bar[ 0 .. 5]; // Row/Col copy
foo[1, 0 .. 5] = 0; // Row/Col clear
foo[0 .. 5, 2 .. 4] = bar[ 1 .. 6, 0 .. 2 ]; // Box copy
foo[0 .. 5, 2 .. 4] = 0; // Box clear

Anyway, this is not a huge deal breaker for me, I was just surprised and felt like I'm missing something. I suppose I can manually define every case one by one and not return/use any references etc. or use alias this to forward to impl[] (which I don't want to do since I don't want to change .length for example)
or just use normal functions and be done with it.

And it's not actually just a regular array I'm making, so that's why it will be mostly custom code, except the very basics.

Reply via email to