Thread overview
What is the difference between a[x][y] and a[x,y]?
Jan 07, 2020
Robert M. Münch
Jan 07, 2020
Adam D. Ruppe
Jan 07, 2020
Robert M. Münch
Jan 07, 2020
H. S. Teoh
Jan 08, 2020
Robert M. Münch
Jan 08, 2020
H. S. Teoh
January 07, 2020
I read all the docs but I'm not toally sure. Is it that [x][y] is a 2D array-index, where as [x,y] is a slice?

But the example in docs for opIndexAssign uses the [x,y] syntax, which is confusing:

```
struct A
{
   int opIndexAssign(int value, size_t i1, size_t i2);
}

void test()
{
   A a;
   a[i,3] = 7;  // same as a.opIndexAssign(7,i,3);
}
```

And what is the difference between opIndexAssign and opIndexOpAssign?

Background: I have a class with a 2D array of some other class objects as memember and want to be able to use it as LHS and RHS.

-- 
Robert M. Münch
http://www.saphirion.com
smarter | better | faster

January 07, 2020
On Tuesday, 7 January 2020 at 17:38:59 UTC, Robert M. Münch wrote:
> I read all the docs but I'm not toally sure. Is it that [x][y] is a 2D array-index, where as [x,y] is a slice?

So [x][y] indexes an array of arrays. [x,y] indexes a single array that has two dimensions.

This can be kinda confusing because we often think of

int[4][4]

as being a 2d array, but the D language actually technically sees that as an array of arrays. So it is indexed with [x][y].

There is no built in multi-dimensional array, it is only syntax provided for library types to implement with the opIndexAssign overloads.

> And what is the difference between opIndexAssign and opIndexOpAssign?

foo[5] = x; // opIndexAssign
foo[4] += x; // opIndexOpAssign

So the operator there like += or -= or *= etc become opAssign, whereas plain = is just plain Assign.

January 07, 2020
On 2020-01-07 17:42:48 +0000, Adam D. Ruppe said:

> So [x][y] indexes an array of arrays.

Yes, that's what I understand. And both can be dynamic, so that I can have a "flattering" layout where not all arrays have the same length.

>  [x,y] indexes a single array that has two dimensions.

Does this fix the dimension sizes? So I can't add a "row" or "column" at runtime?

What are the difference use-cases for these two? For example, I'm doing a grid widget but want to add/remove rows/columns. Can this only be done with a array-of-array? Is the memory continuous in the [x,y] case, whereas in the [x][y] this is not necessarily the case?

> This can be kinda confusing because we often think of
> 
> int[4][4]
> 
> as being a 2d array, but the D language actually technically sees that as an array of arrays. So it is indexed with [x][y].

Yes, it's confusing as I don't understand the [x,y] case :-)

> There is no built in multi-dimensional array, it is only syntax provided for library types to implement with the opIndexAssign overloads.

I don't get that sentence. Above you write "a single array that has two dimensions" which IMO is exactly a multi-dimensional array.

> So the operator there like += or -= or *= etc become opAssign, whereas plain = is just plain Assign.

Got it. Thanks.

-- 
Robert M. Münch
http://www.saphirion.com
smarter | better | faster

January 07, 2020
On Tue, Jan 07, 2020 at 06:38:59PM +0100, Robert M. Münch via Digitalmars-d-learn wrote:
> I read all the docs but I'm not toally sure. Is it that [x][y] is a 2D array-index, where as [x,y] is a slice?

arr[x][y] is indexing an array of arrays.
arr[x,y] is indexing an actual multi-dimensional array.

The language does not have a built-in implementation of multi-dimensional arrays, so the only time you can use the arr[x,y] syntax is when you write your own type that overloads opIndex.

Here's a quick-n-dirty example:

	struct Array2D(T) {
		private T[] impl;
		private int width, height;

		this(int w, int h) {
			width = w;
			height = h;
			impl.length = width*height;
		}

		// Notice that opIndex takes *two* parameters, this is
		// what allows us to use arr[x,y] syntax.
		ref T opIndex(int i, int j) {
			return impl[i + width*j];
		}
	}

	auto arr = Array2D!int(3, 3);
	arr[0, 0] = 1;
	arr[1, 1] = 2;
	...

In the same vein you can write a type that declares opIndex with 3 or more parameters, then you can get higher-dimensional indexing.

If you implement opDollar, then you can also use $ in your indexing, e.g.:

	struct MyArray(T) {
		private int width, height;
		...
		int opSlice(size_t dim)() {
			static if (dim == 0)
				return width;
			else
				return height;
		}
	}

	MyArray!int arr;
	arr[$-2, $-3] = 1;


You can also implement slicing syntax like:

	arr[2 .. $, 0 .. 3]

To do this, you need to overload .opSlice with two indices, returning some object defining a range (i.e., encapsulates "2..$" or "0..3"), say we call it IdxRange. Then overload opIndex to understand arguments of type IdxRange. For example:

	struct IdxRange { int start, end; }

	struct MyArray(T) {
		...
		IdxRange opSlice(int start, int end) {
			return IdxRange(start,end);
		}

		T opIndex(IdxRange colRange, IdxRange rowRange) {
			... // return subarray here
		}
	}

Basically, this:

	arr[2 .. $, 0 .. 3]

gets translated to this:

	arr.opIndex(arr.opSlice(2, arr.opDollar!0),
	            arr.opSlice(0, 3));

It's up to you how to implement all of this, of course.  The language itself doesn't ship a built-in type that implements this, but it does provide the scaffolding for you to build a custom multi-dimensional array type.


[...]
> And what is the difference between opIndexAssign and opIndexOpAssign?
[...]

opIndexAssign is for implementing operations of the form:

	arr[x, y] = z;

opIndexOpAssign is for implementing operations of the form:

	arr[x, y] += z;
	arr[x, y] *= z;
	// etc.

In the previous examples I had opIndex return ref, for simplicity, so you could just assign to the array element via the ref. But if your array implementation differentiates between looking up an element vs. assigning to an element, then you'll want to provide your own implementation of opIndexAssign and/or opIndexOpAssign:

	struct MyArray(T) {
		...
		void opIndexAssign(T value, int x, int y) {
			T* elem = lookupElement(x, y);
			*elem = value;
		}
		void opIndexOpAssign(string op)(T value, int x, int y) {
			T* elem = lookupElement(x, y);
			mixin("*elem "~op~"= value;");
		}
	}


T

-- 
Try to keep an open mind, but not so open your brain falls out. -- theboz
January 08, 2020
On 2020-01-07 19:06:09 +0000, H. S. Teoh said:

> It's up to you how to implement all of this, of course. The language
> itself doesn't ship a built-in type that implements this, but it does
> provide the scaffolding for you to build a custom multi-dimensional
> array type.

Hi, thanks for your extensive answer! Helped a lot...

And the above paraghraph brings it to the core.

How can this be added to the D docs? I think adding such clear "this is the idea how you should use it" intros would help a lot to see the world from a d-ish perspective.

-- 
Robert M. Münch
http://www.saphirion.com
smarter | better | faster

January 08, 2020
On Wed, Jan 08, 2020 at 09:09:23AM +0100, Robert M. Münch via Digitalmars-d-learn wrote:
> On 2020-01-07 19:06:09 +0000, H. S. Teoh said:
> 
> > It's up to you how to implement all of this, of course. The language itself doesn't ship a built-in type that implements this, but it does provide the scaffolding for you to build a custom multi-dimensional array type.
> 
> Hi, thanks for your extensive answer! Helped a lot...
> 
> And the above paraghraph brings it to the core.
> 
> How can this be added to the D docs? I think adding such clear "this is the idea how you should use it" intros would help a lot to see the world from a d-ish perspective.
[...]

File a bug against dlang.org, and maybe when I get some free time I'll try to write something up.


T

-- 
Designer clothes: how to cover less by paying more.