Basically, given two static arrays of S, or when calling array opSliceAssign, I'd expect the assignment to trigger element-wise assignment.
However, what happens is that the elements of the original array are destroyed, and then postblit copies are copied over, element by element:
//----
import std.algorithm;
import std.stdio;
struct S
{
int i;
this(this){"post: ".writeln(i);}
void opAssign(S){"opAssign".writeln();}
~this(){"dest: ".writeln(i);}
}
void main()
{
S[2] a = [S(1), S(2)];
S[2] b = [S(3), S(4)];
"begin".writeln();
a = b;
"end".writeln();
}
//----
post: 1
post: 2
post: 3
post: 4
begin
post: 3
dest: 1
post: 4
dest: 2
end
dest: 4
dest: 3
dest: 4
dest: 3
//----
opSlice/opSlice assignement will produce the same effect:
//----
void main()
{
S[] a = [S(1), S(2)];
S[] b = [S(3), S(4)];
"begin".writeln();
a[] = b[];
"end".writeln();
}
//----
destruction+postblit is not the same as opAssign, so if this is an "optimization", it is wrong.
Comment #1 by maxim — 2012-11-01T10:57:55Z
I don't understand clearly this issue. You first state that when arrays are assigned you expect that this is done element-by element, but actually all elements of an array are firstly destroyed and then replaced.
However output shows just the opposite: assignment is done element-by-element. Differences between two versions of main() are in the fact that elements of fixed arrays are constructed (because fixed arrays are of value semantic) and in order of assignment: in fixed array case elements are copied from the beginning and in case of dynamic arrays the operation starts from the last element. None of versions calls opAssign. I don't understand how your conclusion is based on code and what you are trying to say.
Comment #2 by monarchdodra — 2012-11-01T11:07:07Z
(In reply to comment #1)
> I don't understand clearly this issue. You first state that when arrays are
> assigned you expect that this is done element-by element, but actually all
> elements of an array are firstly destroyed and then replaced.
>
> However output shows just the opposite: assignment is done element-by-element.
> Differences between two versions of main() are in the fact that elements of
> fixed arrays are constructed (because fixed arrays are of value semantic) and
> in order of assignment: in fixed array case elements are copied from the
> beginning and in case of dynamic arrays the operation starts from the last
> element. None of versions calls opAssign. I don't understand how your
> conclusion is based on code and what you are trying to say.
I'm sorry I did not make myself clear. The issue was not about the order (I hadn't even noticed the difference between both version).
The issue is that the elements in the destination array (a) are destroyed and then postblit recreated.
I'd have expected to see assignments instead.
I mean:
//----void main()
{
S[2] a = [S(1), S(2)];
S[2] b = a; //Fine postblit here
a[] = b[]; //But HERE, please use opAssign.
}
//----
(In reply to comment #3)
> (In reply to comment #2)
> > I mean:
> > //----void main()
> > {
> > S[2] a = [S(1), S(2)];
> > S[2] b = a; //Fine postblit here
> > a[] = b[]; //But HERE, please use opAssign.
> > }
> > //----
>
> I see now and it does make sense. But it seems to be impossible now because of
> how _d_arrayassign function
> (https://github.com/D-Programming-Language/druntime/blob/master/src/rt/arrayassign.d#L30)
> assigns arrays: it uses memcpy+postblit+destroy because TypeInfo class
> (https://github.com/D-Programming-Language/druntime/blob/master/src/object.di#L68)
> lacks entry related to opAssign method.
I did some extra thinking about this, and this might be invalid. While one might "expect" opAssign to be called, doing this would mean it is impossible to have a strong exception safe behavior should one of the assignements fails.
Using postblit does (can[1]) guarantee that.
[1]: See also http://d.puremagic.com/issues/show_bug.cgi?id=10967
Comment #5 by issues.dlang — 2015-07-20T15:02:41Z
(In reply to monarchdodra from comment #4)
> I did some extra thinking about this, and this might be invalid. While one
> might "expect" opAssign to be called, doing this would mean it is impossible
> to have a strong exception safe behavior should one of the assignements
> fails.
IIRC, isn't opAssign actually supposed to assign to a copy and then have the result blitted into the variable that you're actually assigning to in order to make it exception safe like you're concerned about? The extension of that would be to do opAssign into a copy of the static array, and then blit it into the original afterwards. I'm not sure how desirable that is, but it would be an extension of what happens with opAssign in general (assuming that I'm remembering that right).
Comment #6 by monarchdodra — 2015-07-24T08:08:14Z
(In reply to Jonathan M Davis from comment #5)
> (In reply to monarchdodra from comment #4)
> > I did some extra thinking about this, and this might be invalid. While one
> > might "expect" opAssign to be called, doing this would mean it is impossible
> > to have a strong exception safe behavior should one of the assignements
> > fails.
>
> IIRC, isn't opAssign actually supposed to assign to a copy and then have the
> result blitted into the variable that you're actually assigning to in order
> to make it exception safe like you're concerned about? The extension of that
> would be to do opAssign into a copy of the static array, and then blit it
> into the original afterwards. I'm not sure how desirable that is, but it
> would be an extension of what happens with opAssign in general (assuming
> that I'm remembering that right).
Well (IIRC), not exactly. The *default* implementation of opAssign, provided you implemented "this(this)" works that way, yes. However, once you *do* define opAssign, the all bets are off. The compiler calls your opAssign, and you have to make your own guarantees.
That said, back to my original comment, the current implementation postblits the elements 1 at a time anyways, so the exception safety is just as bad as assigning the elements 1 at a tie.
Comment #7 by issues.dlang — 2015-07-24T17:53:25Z
(In reply to monarchdodra from comment #6)
> (In reply to Jonathan M Davis from comment #5)
> > IIRC, isn't opAssign actually supposed to assign to a copy and then have the
> > result blitted into the variable that you're actually assigning to in order
> > to make it exception safe like you're concerned about? The extension of that
> > would be to do opAssign into a copy of the static array, and then blit it
> > into the original afterwards. I'm not sure how desirable that is, but it
> > would be an extension of what happens with opAssign in general (assuming
> > that I'm remembering that right).
>
> Well (IIRC), not exactly. The *default* implementation of opAssign, provided
> you implemented "this(this)" works that way, yes. However, once you *do*
> define opAssign, the all bets are off. The compiler calls your opAssign, and
> you have to make your own guarantees.
Hmmm. Well, I don't know how to test that to figure out what's going on, given that bitblitting is involved and thus you have no functions to add code to print out or whatnot. I guess that the only way to figure out what's happening for sure would be to read the assembly, and I'm not very good at that.
> That said, back to my original comment, the current implementation postblits
> the elements 1 at a time anyways, so the exception safety is just as bad as
> assigning the elements 1 at a tie.
Which is why I was suggesting that one solution might be to do it all into another static array first and then blit that over after it succeeds. I'm not sure that that's a _good_ solution, since it requires blitting everything over, but it would at least theoretically give you the exception safety.
Either way, it seems rather odd to be using postblits instead of opAssign when doing assignment, even if it's in a static array.
Comment #8 by robert.schadek — 2024-12-13T18:02:17Z