www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - The difference between T[] opIndex() and T[] opSlice()

reply Salih Dincer <salihdb hotmail.com> writes:
Hi,

What is the difference between T[] opIndex() and T[] opSlice(), 
which haven't parameters?

```d
struct S(T)
{
     T[] arr;
     
     T[] opIndex() => arr[];/*
     T[] opSlice() => arr;//*/
}

alias Type = int;
void main()
{
     auto s = S!Type([1,2,3]);
     auto arr = s[]; // calls s.opIndex()
     
     assert(arr == [1,2,3]);
     assert(is(typeof(arr): Type[]));
}
```

Also, is it correct to use [] when returning?

Thanks...
Oct 01 2023
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 1 October 2023 at 17:41:08 UTC, Salih Dincer wrote:
 Also, is it correct to use [] when returning?

 Thanks...
[Here](https://dlang.org/spec/operatoroverloading.html#slice), the opIndex() is proposed and an example of parameterized the opSlice() is given for multidimensional arrays. Like there's no difference, huh? Puff :)
Oct 01 2023
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 1, 2023 11:51:17 AM MDT Salih Dincer via Digitalmars-d-
learn wrote:
 On Sunday, 1 October 2023 at 17:41:08 UTC, Salih Dincer wrote:
 Also, is it correct to use [] when returning?

 Thanks...
[Here](https://dlang.org/spec/operatoroverloading.html#slice), the opIndex() is proposed and an example of parameterized the opSlice() is given for multidimensional arrays. Like there's no difference, huh? Puff :)
I suspect that the only people who really understand the full mess with opIndex and opSlice at this point are the folks who have done a bunch with multi-dimensional containers, which fortunately, I haven't had to deal with any time recently, so I'm not well-versed on all of the nitty-gritty details. The situation used to be a bit clearer, but folks wanted better support for multi-dimensional containers, so some changes were made. The result is that if you're dealing with multiple arguments, the difference in which is called should come down to whether you're using the slice operator between indices - .. - or whether you're using commas (though since you can mix and match to an extent with multi-dimensional containers, it's still pretty confusing IMHO). Either way, when you have no arguments, the situation is certainly more confusing than it used to be. Previously, you would have always used opSlice with no parameters for something like returning the full slice of a container, but I think that it's now possible to do exactly the same thing with opIndex (confusing as that may be), because that made some sense when writing a variadic opIndex. For most code, you'd just write an opIndex with a single parameter for indexing an element, opSlice with two parameters for slicing the range or container, and then either opIndex or opSlice with no parameters to return a slice of the entire container (in which case, personally, I'd use opSlice, because semantically, that's what you're doing, but either should work IIRC). - Jonathan M Davis
Oct 01 2023
parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 2 October 2023 at 02:01:34 UTC, Jonathan M Davis wrote:
 
 For most code, you'd just write an opIndex with a single 
 parameter for indexing an element, opSlice with two parameters 
 for slicing the range or container, and then either opIndex or 
 opSlice with no parameters to return a slice of the entire 
 container (in which case, personally, I'd use opSlice, because 
 semantically, that's what you're doing, but either should work 
 IIRC).
Overloading has nothing to do with indexing, so I'll use opSlice. ```d import std.stdio; import std.range; struct Matrix(T) {  private T[][] elements; size_t length; T* ptr; this(size_t length) { this.length = length * length; size_t m = T.sizeof * this.length; ubyte[] arr = new ubyte[](m); ptr = cast(T*)arr.ptr; m /= length; foreach(i; 0 .. length) { size_t n = i * m; elements ~= cast(T[])arr[n .. n + m]; } }  ref T opIndex(size_t i) in(i < length)    => ptr[i]; auto opDollar() => length;  auto opSliceAssign(T value, size_t a, size_t b)  in(a <= length && b <= length)   => ptr[a..b] = value; auto opSlice() => elements;  auto opSliceAssign(T value)  {    foreach(i; 0 .. length) { ptr[i] = value; }  } } void main() {  auto arr = Matrix!double(3);  size_t n;  foreach(value; iota(0.1, 1, 0.1))    arr[n++] = value;    arr[].writefln!"%-(%-(%s %)\n%)\n";    arr[0..$/2] = 0;   // reset a slice  arr[].writefln!"%-(%-(%s %)\n%)\n";  arr[] = 0;             // reset all    arr[].writefln!"%-(%-(%s %)\n%)\n";    arr[6..9] = 1;       // set a slice  arr[].writefln!"%-(%-(%s %)\n%)\n"; } ``` SDB 79
Oct 01 2023
prev sibling next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/1/23 1:41 PM, Salih Dincer wrote:
 Hi,
 
 What is the difference between T[] opIndex() and T[] opSlice(), which 
 haven't parameters?
None. It used to be that opSlice was the only way, and the mechanisms opSlice uses are still valid. -Steve
Oct 01 2023
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 1 October 2023 at 17:41:08 UTC, Salih Dincer wrote:
 Hi,

 What is the difference between T[] opIndex() and T[] opSlice(), 
 which haven't parameters?
`T[] opSlice()` is the D1 version and exists only for backwards compatibility. You should use `T[] opIndex()` in new code.
Oct 02 2023
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Monday, 2 October 2023 at 16:05:39 UTC, Paul Backus wrote:
 `T[] opSlice()` is the D1 version and exists only for backwards 
 compatibility. You should use `T[] opIndex()` in new code.
Forgive me for asking again, I think opSliceAssign(T value) has also been improved, right? ```d // ... auto opSlice() { return elements[]; } auto opSliceAssign(T value) { foreach(i; 0 .. length) { ptr[i] = value; } } } ``` In an old version (for example, v2.0.83), the code you implemented in the places where Slice is written above works as desired. In the most current versions, the parameterized opIndexAssign(T value) gives the error:
 onlineapp.d(51): Error: function 
 `onlineapp.Matrix!double.Matrix.opIndexAssign(double value)` is 
 not callable using argument types `(double, ulong)`
 onlineapp.d(51):        expected 1 argument(s), not 2
**Source:** https://run.dlang.io/is/TPAg5m Thanks... SDB 79
Oct 02 2023
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 2 October 2023 at 20:34:11 UTC, Salih Dincer wrote:
 In an old version (for example, v2.0.83), the code you 
 implemented in the places where Slice is written above works as 
 desired.  In the most current versions, the parameterized 
 opIndexAssign(T value) gives the error:

 onlineapp.d(51): Error: function 
 `onlineapp.Matrix!double.Matrix.opIndexAssign(double value)` 
 is not callable using argument types `(double, ulong)`
 onlineapp.d(51):        expected 1 argument(s), not 2
**Source:** https://run.dlang.io/is/TPAg5m
I don't know what's wrong in your example but this works for me: ```d struct S { void opIndexAssign(int value) { import std.stdio; writeln("assigned ", value); } } void main() { S s; s[] = 7; } ```
Oct 02 2023
next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 2 October 2023 at 20:42:14 UTC, Paul Backus wrote:
 I don't know what's wrong in your example but this works for me:
I found the reason for the error: https://forum.dlang.org/thread/vckvftkdzcrnikuduuqp forum.dlang.org SDB 79
Oct 02 2023
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Monday, 2 October 2023 at 20:42:14 UTC, Paul Backus wrote:
 On Monday, 2 October 2023 at 20:34:11 UTC, Salih Dincer wrote:
 In an old version (for example, v2.0.83), the code you 
 implemented in the places where Slice is written above works 
 as desired.  In the most current versions, the parameterized 
 opIndexAssign(T value) gives the error:

 onlineapp.d(51): Error: function 
 `onlineapp.Matrix!double.Matrix.opIndexAssign(double value)` 
 is not callable using argument types `(double, ulong)`
 onlineapp.d(51):        expected 1 argument(s), not 2
**Source:** https://run.dlang.io/is/TPAg5m
I don't know what's wrong in your example but this works for me: ```d struct S { void opIndexAssign(int value) { import std.stdio; writeln("assigned ", value); } } void main() { S s; s[] = 7; } ```
So in the example linked by Salih, the `opIndex` returns a ref, which is a valid mechanism to properly do `a[0] = val;`. However, since `opIndexAssign` exists, the compiler expects that to be used instead. Essentially, by naming the slice assign the same operator as index assign, you have eliminated the possibility for ref assignment via indexing. Now, you can define a further `opIndexAssign(T val, size_t idx)`. However, now you lose capabilities like `a[0]++`, which I don't think has a possibility of implementing using an `opIndex` operator, and it would be pretty ugly if you had to. This seems like a design flaw in the `opIndex` overloading changes. I would stick with `opSliceAssign` if faced with this problem (glad it still works!) It could also be considered a bug but I don't know the overload implications. -Steve
Oct 03 2023
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 3 October 2023 at 13:07:00 UTC, Steven Schveighoffer 
wrote:
 Now, you can define a further `opIndexAssign(T val, size_t 
 idx)`. However, now you lose capabilities like `a[0]++`, which 
 I don't think has a possibility of implementing using an 
 `opIndex` operator, and it would be pretty ugly if you had to.
Works for me, with both `++` and `+=`: https://run.dlang.io/is/JckTVG AST output confirms that these are lowered to use `opIndex`. Looking at the spec, it seems like `opIndex` would only be pre-empted here if you overloaded `opIndexUnary` (for `++`) and/or `opIndexOpAssign` (for `+=`).
Oct 03 2023
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 10/3/23 12:09 PM, Paul Backus wrote:
 On Tuesday, 3 October 2023 at 13:07:00 UTC, Steven Schveighoffer wrote:
 Now, you can define a further `opIndexAssign(T val, size_t idx)`. 
 However, now you lose capabilities like `a[0]++`, which I don't think 
 has a possibility of implementing using an `opIndex` operator, and it 
 would be pretty ugly if you had to.
Works for me, with both `++` and `+=`: https://run.dlang.io/is/JckTVG AST output confirms that these are lowered to use `opIndex`. Looking at the spec, it seems like `opIndex` would only be pre-empted here if you overloaded `opIndexUnary` (for `++`) and/or `opIndexOpAssign` (for `+=`).
OK, so it's not as bad as I thought, but surely the compiler should recognize that `opIndexAssign(val, idx)` doesn't work, but `opIndex(idx) = val` does? -Steve
Oct 03 2023
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 3 October 2023 at 16:45:39 UTC, Steven Schveighoffer 
wrote:
 OK, so it's not as bad as I thought, but surely the compiler 
 should recognize that `opIndexAssign(val, idx)` doesn't work, 
 but `opIndex(idx) = val` does?
Maybe. On the other hand, if you make a typo in the body of your templated `opIndexAssign` overload, do you want the compiler to silently fall back to `opIndex`, or do you want an error? There are pros and cons to both approaches. At the very least, the spec should do a better job of documenting when the compiler will try a fallback and when it won't.
Oct 03 2023
parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Tuesday, 3 October 2023 at 17:52:20 UTC, Paul Backus wrote:
 On Tuesday, 3 October 2023 at 16:45:39 UTC, Steven 
 Schveighoffer wrote:
 OK, so it's not as bad as I thought, but surely the compiler 
 should recognize that `opIndexAssign(val, idx)` doesn't work, 
 but `opIndex(idx) = val` does?
Maybe. On the other hand, if you make a typo in the body of your templated `opIndexAssign` overload, do you want the compiler to silently fall back to `opIndex`, or do you want an error? There are pros and cons to both approaches. At the very least, the spec should do a better job of documenting when the compiler will try a fallback and when it won't.
Who will be the hero and add the documentation? 😇
Oct 03 2023
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Tuesday, 3 October 2023 at 18:09:55 UTC, Imperatorn wrote:
 At the very least, the spec should do a better job of 
 documenting when the compiler will try a fallback and when it 
 won't.
Who will be the hero and add the documentation? 😇
More importantly, is there a priority order? Because in our last example, when we leave a single overload, all features are executed through the ref opIndex except the bit: ```d struct S { int[] i; ref opIndex(size_t index) => i[index];  //void opIndexAssign(int value) { i[] = value; }  //void opIndexAssign(int value, size_t idx) { i[idx] = value; } } void main() {  auto s = S([2, 2]);  s[0] = 2;  assert(s.i == [2, 2]); s[1] = 42; assert(s.i == [2, 42]); s[0]++; assert(s.i == [3, 42]); s[0] += 1; assert(s.i == [4, 42]); } ``` So the important thing is: Who is dominant when another overload comes into play? SDB 79
Oct 03 2023
parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 3 October 2023 at 18:29:49 UTC, Salih Dincer wrote:
 More importantly, is there a priority order?  Because in our 
 last example, when we leave a single overload, all features are 
 executed through the ref opIndex except the bit:
The spec says:
 If an index expression can be rewritten using `opIndexAssign` 
 or `opIndexOpAssign`, those are preferred over `opIndex`.
Source: https://dlang.org/spec/operatoroverloading.html#array So, the more specialized overloads are tried first, and the more general `opIndex` is tried last. The only thing that's unclear here is the meaning of "can be rewritten"--as we've seen, the compiler only checks whether the specialized function *exists*, not whether it actually *works*, and will sometimes perform rewrites even when the resulting code does not compile.
Oct 03 2023
prev sibling parent reply Gaurav Negi <gauravbackliner gmail.com> writes:
Well, in the D programming language, both opIndex and opSlice are 
two different operators used to access elements of a custom type.

     struct S(T)
     {
         T[] arr;

         T opIndex(size_t index) const
         {
             assert(index < arr.length, "Index out of range");
             return arr[index];
         }

         T[] opSlice(size_t startIndex, size_t endIndex) const
         {
             assert(startIndex <= endIndex && endIndex <= 
arr.length, "Invalid slice indices");
             return arr[startIndex..endIndex];
         }
     }

     alias Type = int;
     void main()
     {
         auto s = S!Type([1, 2, 3]);

         auto element = s[0]; // Calls s.opIndex()
         assert(element == 1);

         auto slice = s[1..3]; // Calls s.opSlice()
         assert(slice == [2, 3]);
     }

Thanks
Oct 05 2023
parent Salih Dincer <salihdb hotmail.com> writes:
On Thursday, 5 October 2023 at 16:40:49 UTC, Gaurav Negi wrote:
 Well, in the D programming language, both opIndex and opSlice 
 are two different operators used to access elements of a custom 
 type.
Yeah, D is on its way to becoming a near-perfect programming language... ```d enum initialSituation = [1, 2, 3]; import std.stdio; void main() {  auto s = S(initialSituation);  s[] = 1; s[1] = 2;  s[2] += 2;   assert(s.arr == initialSituation);  auto d = s *= 2;   assert(d == S([2, 4, 6])); } /* Prints: [1, 1, 1]: onlineapp.S.opSliceAssign [1, 2, 1]: onlineapp.S.opIndexAssign [1, 2, 1]: onlineapp.S.opIndex [2, 4, 6]: onlineapp.S.opOpAssign!"*".opOpAssign */ struct S {  int[] arr;  auto opSliceAssign (int value)  {    scope(exit)    {     writefln("%s: %s", arr, __FUNCTION__);    }    return arr[] = value;  }  auto opIndexAssign(int value, int index) { scope(exit)    {     writefln("%s: %s", arr, __FUNCTION__);    }    arr[index] = value; } ref opIndex(size_t index)  { scope(exit)    {     writefln("%s: %s", arr, __FUNCTION__);    }    return arr[index];  }  ref opOpAssign(string op)(int value)  {   scope(exit)    {     writefln("%s: %s", arr, __FUNCTION__);    }    mixin("arr[] " ~ op ~ "=value;");    return this;  } } ``` SDB 79
Oct 05 2023