Discussion Thread: DIP 1040--Copying, Moving, and Forwarding--Community Review Round 1

Timon Gehr timon.gehr at gmx.ch
Wed Mar 17 01:06:45 UTC 2021


On 16.03.21 23:30, deadalnix wrote:
> On Tuesday, 16 March 2021 at 09:14:58 UTC, Walter Bright wrote:
>> Postblit's problems arose from it not having access to both objects. 
>> The opAssign does have access to both, and the qualifiers can be 
>> applied to both parameters, so I don't see a barrier to it working.
>>
> 
> YES!
> 
> This is why it is unsuitable for copies. During a copy, there are 2 
> objects.
> 
> Any solution, that provide to objects when moving, a situation where 
> only one object exists, will just open a can of worm of the same nature 
> as postblit for copies opened.
> 
> This is self evident. This is so obvious that I don't know how to unpack 
> it any further.
> 
> Pretend you have one object when you have two => problems.
> Pretend you have two objects when you have one => problems.
> 
>> An opAssign gives the implementer complete control over the operation 
>> of it, including when and how destruction takes place of the original 
>> destination's contents.
> 
> That break all the invariant provided by the ctor/dtor mechanism and 
> because struct are composable (you can use structs as member of structs) 
> then the mess is not bounded to the one struct you are toying with.

This reasoning makes sense to me, but perhaps sometimes you don't want 
to actually destroy the move target. E.g., it's possible that you want 
to move a dynamic array element-wise instead of by reference to avoid 
creating dangling references (if you had pointers to the array 
elements). There's no good reason why that should be possible for static 
array members but not dynamic array members. This is what Walter means 
by complete control. So a "better" solution would allow for this while 
still doing the right thing for new fields by default.

For the common case, I think the move opAssign should be auto-generated 
from the destructor and the move constructor/postblit. So whatever the 
solution is, having a move constructor/postblit should not require an 
implementation of the move opAssign, but it should rather auto-generate 
it using the move constructor/postblit.

So I think the reasoning may not fully apply to move opAssign. OTOH, I 
think you are making a very strong point that a (move) postblit is in 
the common case better than a move _constructor_. Postblit for copies is 
bad, but for moves into destructed/uninitialized memory, it is often 
precisely what we want. I guess the main benefit of a move constructor 
is that it allows you to fix internal references, while you are out of 
luck with postblit.

Summary:

move constructor:
move into uninitialized memory, error prone, internal references supported

(move) postblit:
move into uninitialized memory, composes well, internal references not 
supported

move opAssign: move into existing memory, error prone, allows for full 
control but can usually be auto-generated from destructor and move 
constructor/postblit


So, obvious question: What design satisfies the following constraints?

move constructor v2:
move into uninitialized memory, composes well, internal references supported

move opAssign v2:
move into existing memory, composes well, allows for full control, but 
can be auto-generated in the common case


It's pretty clear what a move constructor v2 would be: Require all 
fields to be explicitly initialized, even those that have a field 
initializer. This would allow to a manual postblit-like implementation, 
i.e., first this.tupleof=move(other.tupleof), then do fixup, as well as 
repairing internal references if that's required.

Issues with this:
- It does not really make sense to reinitialize fields of immutable 
objects that have a field initializer.
- You can't truly get postblit behavior as IIRC std.algorithm.move 
actually writes the init value over its argument, so maybe still 
supporting postblit is better. (Unless the optimizer is reliably good 
enough here.)


What's a move opAssign v2? Maybe move opAssign but require all fields to 
be mentioned?


One issue with original DIP that occurred to me while writing this stuff 
down:
- How do you manually move an object field-wise in a move constructor or 
move opAssign? You'd want to move out the fields, but the compiler does 
not really have a good way to see that. If you use std.algorithm.move, 
you get additional overhead that a direct call to the move constructor 
or move opAssign would not suffer. Do we rely on the optimizer here?


More information about the Digitalmars-d mailing list