Move Constructors - Converting Lvalues to Rvalues
Timon Gehr
timon.gehr at gmx.ch
Sat Oct 12 22:43:23 UTC 2024
On 10/12/24 07:56, Manu wrote:
>
> The proposal in the DIP is very simple; struct rvalues are ref too now,
> so don't worry about the ref stuff; everything's a ref. The problem to
> be solved is, how do we appropriately distinguish an rvalue from an
> lvalue; and while we've had a myriad of proposals adding attributes,
> Walter found an arrangement where the distinction can be expressed in
> existing language in an astonishingly elegant way; recognise that by-
> value calls (accepts rvalue) are all actually move opportunities.
>
> void f(ref T) // arg is an lvalue; the syntax says "I have received a
> reference to /someone else's/ thing"; or put another way, the callee
> does NOT own the argument.
> void f(T) // arg is an rvalue; this syntax says "I have received this
> thing"; the callee owns the argument, and as such, is a valid recipient
> of any move operation.
> In order to make move operations actually move operations, they need to
> be passed by ref (by /rvalue-ref/, so to speak), and that is literally
> the entire point of the DIP; the calling convention is adjusted so a by-
> value (r-value) parameter is passed by rvalue-ref.
>
> This is what I mean where I say we're talking about "move semantics",
> but everyone seems to be fixated on move constructors as if they're an
> extraordinarily interesting part of this story.
I buy this except:
- With DIP1040, a move constructor `this(S)` is actually fundamentally
different from a function `f(S)`, and not just because it is a constructor.
I think this should be adjusted so it works the way you say. Then there
can be some special syntax to elide destructor calls explicitly.
- Move and copy constructors are indeed a bit special because the
compiler may implicitly call them in ways it would not call any other
constructor.
Here, idk. It's not really an important concern to me personally but I
can see why someone might have an issue with this.
For copy constructors, consider:
```d
import std.stdio;
struct S{
this(int x){ writeln("constructed"); }
this(ref S){ writeln("copied"); }
}
void foo(S s){}
void main(){
S s=2; // ok, actually does `S s=S(2)`
S t=s; // ok, actually does `S s=S(s)`
// foo(2); // error, refuses to call `foo(S(s))` to convert to `int`
foo(s); // ok, calls foo(S(s)) to convert to rvalue
}
```
For move constructors, consider:
```d
import std.stdio;
struct S{
size_t address;
this(int){ address=cast(size_t)&this; }
this(S){ writeln("rvalue constructor"); }
}
S foo(bool x){
S s=2,t=2;
if(x) return s;
else return t;
}
void main(){
auto s=foo(true); // (no output)
auto t=foo(false); // (no output)
writeln(s.address," ",cast(size_t)&s); // (two distinct numbers)
writeln(t.address," ",cast(size_t)&t); // (two distinct numbers)
}
```
So with the compilation strategy that DMD currently uses for this code,
it would need to start calling what is currently an rvalue constructor,
but at the moment nothing is called. This is a change in behavior.
To me this seems desirable, but maybe there is some code out there that
does not expect rvalue constructors to be called in this fashion?
Perhaps there should be a deprecation period?
More information about the Digitalmars-d
mailing list