Discussion: Rvalue refs and a Move construtor for D

kinke noone at nowhere.com
Thu Sep 5 20:29:55 UTC 2019


On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:
> His initial point about the advantage of rvalue still remains 
> unchallenged.
>
> Example:
> ```
> void foo(@rvalue ref S value, int n = 0)
> {
>     if (n > 32)
>         return;
>
>     foo(__move(value), n + 1);
> }
>
> [...]
>
> The whole program only calls the destructor for the lvalue, and 
> only once. You need a competitive alternative.

I know that rvalue refs in the language would enable the same 
thing, but that's exactly what I'd like to avoid to keep things 
nice and simple. When using some 3rd-party code, you don't want 
to depend on them providing the required (and ugly) rvalue ref 
signatures when coming along with a complex 1KB struct. And there 
are no rvalue refs in existing code as of now, so existing code 
bases would have to be uglified to exploit the potential.

This is more or less how I'd imagine it at this time:

struct S
{
     // only called for: `auto s = move(rhs)`
     moveThis(ref S rhs);
     ~this();
     // only called for: `s = move(rhs)`
     opMoveAssign(ref S rhs);
}

// forwarding `auto ref` parameter - unchanged:
void callee(ref S lvalArg);
void callee(S rvalArg);
void foo()(auto ref S s) // either `ref S` reference or `S` value
{
     // ref case: pass along `s` ref
     // value case: `move(s)` (no actual moving, rather imagine 
forwarding an rvalue ref when coming from C++)
     callee(forward(s));
}

void foo(S param);
// NEW D ABI: non-PODs and large PODs passed by ref, not on the 
stack.
// Already the case for D on Win64 and how C++ seems to do it 
generally.
// Also new: `foo` doesn't destruct `param` anymore, that is to 
be done by the
// caller. That's how C++ does it and currently a hurdle for C++ 
interop.

void caller(S s)
{
     foo(S());     // construct temporary and pass by ref, then 
destruct
     foo(s);       // copy-construct temporary and pass by ref, 
then destruct
     foo(move(s)); // pass `s` directly by ref, then destruct and 
reset to S.init
}

Potential problems:
* Don't access the moved lvalue anywhere else in the statement.
* An lvalue moved in an argument expression is destructed twice, 
i.e., 2 times at the same address.
* Cannot represent C++ functions with rvalue refs (except for 
move ctor and move-assignment operator).
* Some more, I'm sure. ;)


More information about the Digitalmars-d mailing list