__rvalue and Move Semantics first draft
kinke
noone at nowhere.com
Sat Nov 9 14:32:11 UTC 2024
Thanks, this is definitely a step in the right direction, getting
us perfect forwarding. I very much like its simplicity. First
thoughts wrt. the `__rvalue` builtin:
> This means that an __rvalue(lvalue expression) argument
> destroys the expression upon function return. Attempts to
> continue to use the lvalue expression are invalid. The compiler
> won't always be able to detect a use after being passed to the
> function, which means that the destructor for the object must
> reset the object's contents to its initial value, or at least a
> benign value.
What IMO needs to be stressed here is that there's always one
implicit use of the original lvalue after the __rvalue usage -
its destruction when going out of scope! So the dtor at the very
least needs to make sure that it can handle a double-destruction,
adjusting the payload to make the 2nd destruction a 'noop', not
freeing effective resources twice etc.
And that's my only real problem with the proposal in its current
shape - who's going to revise all existing code to check for
problematic struct dtors that don't handle double-destruction,
just in case someone applies __rvalue on one of these types, or a
custom struct with those types as fields?
The proposed `__rvalue` is very similar to what I proposed in
https://forum.dlang.org/thread/xnwhexrctbfgntfklzaf@forum.dlang.org, the proposed revised `forward` semantics in the non-ref-storage-class case. The main difference is that I went the suppress-2nd-destruction way, limiting its applicability to local variables (incl. params) only, where the destruction could be controlled via a magic destructor-guard variable for each local that might be __rvalue'd.
When going with the double destruction to keep things simpler and
allow __rvalue for *all* lvalues (I guess PODs too, which aren't
guaranteed to be passed by ref under the hood, and so might still
be blitted or passed in registers, depending on the platform
ABI), then I'd propose automatically performing a reset-blit to
`T.init` after the function call (incl. the case where the callee
threw - the rvalue has still been destructed in that case, so we
still need to reset the payload for the 2nd destruction). This
has a number of advantages:
* No need to check and fix up all existing dtors.
* Well-defined state of the lvalue after its usage as __rvalue -
`T.init` -, not some nebulous 'initial value, or at least a
benign value' (as proposed, the state the first destruction left
the object in, or if the type has no dtor (not all non-PODs have
a dtor), the state the callee left the object in).
* Not paying the price for resets for every destruction, only
after __rvalue usages. I guess the overall number of destructions
is usually orders of magnitude greater than __rvalue usages.
Eliding the `T.init` reset and the 2nd destruction - in suited
cases - could be implemented as an optimization later.
---
Wrt. safety, I think we should at least also mention the aliasing
problem/danger:
```D
void callee(ref S x, S y) {
assert(&x != &y);
}
void caller() {
S lval;
callee(lval, __rvalue(lval));
}
```
More information about the dip.development
mailing list