Bug 15582 – Slice returned by opSlice() not accepted as lvalue
Status
RESOLVED
Resolution
WONTFIX
Severity
enhancement
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2016-01-18T19:22:35Z
Last change time
2022-10-28T10:40:49Z
Assigned to
No Owner
Creator
Marc Schütz
Comments
Comment #0 by schuetzm — 2016-01-18T19:22:35Z
I believe this should work:
struct S {
int[10] data;
auto opSlice() { return data[]; }
}
void main() {
S s;
s[] = 10; // Error: s[] is not an lvalue
s[][] = 10; // works
}
Comment #1 by b2.temp — 2016-01-18T22:16:00Z
(In reply to Marc Schütz from comment #0)
> I believe this should work:
>
> struct S {
> int[10] data;
> auto opSlice() { return data[]; }
> }
>
> void main() {
> S s;
> s[] = 10; // Error: s[] is not an lvalue
> s[][] = 10; // works
> }
I don't aggree, it's like if you say that
s.opSlice() = 10;
should work.
Comment #2 by schuetzm — 2016-01-19T18:08:05Z
(In reply to b2.temp from comment #1)
> I don't aggree, it's like if you say that
>
> s.opSlice() = 10;
>
> should work.
That should work, too.
Note that appending another pair of brackets is already accepted. As long as there's no ambiguity with assigning to the slice itself (i.e. the lhs is not an lvalue, and opSlice doesn't return by ref), it should just do the appropriate element-wise operation.
Analogously with *=, += etc.
Comment #3 by b2.temp — 2016-01-21T22:23:37Z
(In reply to Marc Schütz from comment #2)
> (In reply to b2.temp from comment #1)
> > I don't aggree, it's like if you say that
> >
> > s.opSlice() = 10;
> >
> > should work.
>
> That should work, too.
>
> Note that appending another pair of brackets is already accepted. As long as
> there's no ambiguity with assigning to the slice itself (i.e. the lhs is not
> an lvalue, and opSlice doesn't return by ref), it should just do the
> appropriate element-wise operation.
>
> Analogously with *=, += etc.
Actually you overload the wrong operator, because this works:
struct S {
int[10] data;
void opIndexAssign(int v) {data[] = v;}
}
void main() {
S s;
s[] = 10;
}
So I suggest you to close the issue.
Comment #4 by schuetzm — 2016-01-22T18:37:59Z
(In reply to b2.temp from comment #3)
> Actually you overload the wrong operator, because this works:
>
> struct S {
> int[10] data;
> void opIndexAssign(int v) {data[] = v;}
> }
>
> void main() {
> S s;
> s[] = 10;
> }
>
> So I suggest you to close the issue.
No, the overloaded operator actually doesn't matter, it's just how I discovered the problem. As I said, even a slice returned by a normal method (or function) should be assignable, in fact it doesn't matter where that slice came from.
The rule should be simple; for any `<slice> = <value>`:
* if <slice> is an rvalue, do element-wise assignment from value
=> this handles my example as well as the normal slice assignment operator
int[] a = [4,5,6], b = a;
a[] = 42;
assert(a == [42,42,42]);
a[] = [1,2,3];
assert(a == [1,2,3]);
assert(a is b);
S s;
s[] = 42;
s[] = [1,2,3];
* if <slice> is an lvalue and <value> is itself a slice of the correct type, assign the slice to the lvalue
int[] a = [4,5,6], b = a;
a = [1,2,3];
assert(a !is b);
// not applicable for `S`, because `opSlice()` returns an lvalue
* if <slice> is an lvalue and <value> has the slice's element type, error
int[] a = [4,5,6], b = a;
a = 42; // error
// not applicable for `S`, because `opSlice()` returns an lvalue
As you can see, built-in slices already work along these rules. But it's currently not possible to construct a user-defined type that behaves the same way, except by using `alias this` (often too blunt), or by overloading all kinds of operators (too tedious).
Comment #5 by hsteoh — 2016-01-22T19:03:58Z
I believe you're misunderstanding what opSlice() does.
Contrary to what you seem to be thinking, opSlice() is *not* used for implementing slicing notation. That was an older usage that is now deprecated (though still supported for backward compatibility); with Kenji's multidimensional array PR merged, opSlice() is used for implementing "a .. b" notation for user-defined types. The correct function to use for implementing "x[]" is opIndex() (with zero arguments), not opSlice().
Secondly, you seem to be confusing array-copying notation with slicing notation. When you write `x[] = 10;` it does NOT mean "assign 10 to a slice of x", because that makes no sense (how do you assign a scalar to an array?). It's actually a special notation meaning "do an array copy of 10 into x".
Your code example shows how your misunderstanding compounded with deprecated opSlice usage to produce something that looks wrong, but actually is entirely consistent with how the language works:
Firstly, `s[]` by default tries to invoke S.opIndex() (with no arguments), but since this is not defined, it falls back to backward-compatible behaviour by translating `s[]` into `s.opSlice()`.
Secondly, in opSlice()'s implementation, the notation `data[]` means "take a slice of the static array `data`", IOW, "make a dynamic array that points to the elements of the static data `data`".
So what is returned by opSlice is a dynamic array (that happens to point to the elements of s.data). So what you end up with, is that the LHS of the assignment is a dynamic array, and you're trying to assign the scalar 10 to it. This is, of course, invalid. (The compiler's error message is not helpful, though.)
Finally, when you write `s[][]`, it gets translated to `<<dynamic_array>>[]`, which is special notation for array copying, which just so happens to be what you intended from the beginning, but looks weird.
So what you're asking for, really, has nothing to do with *slicing*. What you're asking for is for user-defined types to support array-copying notation. Currently the way to do this is to implement opIndexAssign, as another commenter has already noted above.
Part of your confusion may have arisen from the arguably unfortunate design choice of overloading slicing notation with array-copying notation. It *looks* like slicing but really isn't, as proven by the following example:
-----
int[] a = [1,2,3,4,5];
int[] b = [6,7,8,9,0];
a[0 .. 2] = b[3 .. 4]; // array copying notation
auto x = a[0 .. 2]; // slicing notation
auto y = b[3 .. 4]; // slicing notation
x = y; // ** N.B.: NOT the same thing as the array copying notation above!
-----
If the line marked "array copying notation" is really the same thing as slicing, then the line "x = y" ought to overwrite the first two elements of a with the last two elements of b. However, this is NOT what happens. Instead, what does happen is that the dynamic array x is assigned to point to what y points to. The underlying arrays a and b are unchanged. Therefore, array copying notation cannot be the same thing as slicing, in spite of all appearances. Q.E.D.
Comment #6 by schuetzm — 2016-01-24T15:08:40Z
@H. S. Teoh:
You're right that opSlice() should actually be opIndex(), but this has nothing to do with the problem (I guess we both agree on that).
I may indeed be misunderstanding things. Let me comment on some of your points, maybe my stance becomes clearer.
> Secondly, in opSlice()'s implementation, the notation `data[]` means "take a
> slice of the static array `data`", IOW, "make a dynamic array that points to
> the elements of the static data `data`".
Right. To clarify, since this may be a cause of confusion, what I mean by "slice" is exactly a "dynamic array".
> So what you end up with, is that the LHS of the assignment is a dynamic array,
> and you're trying to assign the scalar 10 to it. This is, of course, invalid.
This is where I used to disagree. In my understanding, the language should behave as if dynamic arrays `T[]` had an operator `T opAssign(T value)` that does element-wise assignment.
Now, looking at [1], the documentation indeed describes the expected behaviour in terms of a "slice operator", thus supporting your point of view.
> If the line marked "array copying notation" is really the same thing as
> slicing, then the line "x = y" ought to overwrite the first two elements of a
> with the last two elements of b.
I'm aware of this. But in `x = y`, `x` is an lvalue, see my comment #4.
But in light of what the specification says, the current behaviour is indeed consistent. It seems that my mental model was indeed different.
I still think that the current behaviour is suboptimal, but it's certainly not a bug then. I'm turning this into an enhancement request; people can close it if it isn't useful.
[1] http://dlang.org/spec/arrays.html#array-setting