Move Constructors - Converting Lvalues to Rvalues

Timon Gehr timon.gehr at gmx.ch
Thu Oct 3 00:19:44 UTC 2024


On 10/3/24 01:17, Manu wrote:
> On Thu, 3 Oct 2024 at 04:06, Walter Bright via Digitalmars-d 
> <digitalmars-d at puremagic.com <mailto:digitalmars-d at puremagic.com>> wrote:
> 
>     On 10/1/2024 1:06 PM, Timon Gehr wrote:
>      > I think in case we did go the function route, I think any
>     implementation of
>      > `move` that is much more complex than the following is a failure:
>      >
>      > ```d
>      > auto move(T)(@moved T arg)=>arg;
>      > ```
> 
>     The major difference between @move and __rvalue is the former is
>     attached to the
>     parameter, and the latter is attached to the argument. This might
>     seem a
>     distinction without a difference, but this has large implications
>     with how
>     overloading works.
> 
>     For example, how do we distinguish a move constructor from a copy
>     constructor?
>     ```
>     this(ref S); // copy constructor
>     this(S);     // move constructor
>     S s = t;     // calls copy constructor (lvalue)
>     S s = f();   // calls move constructor (rvalue)
>     ```
>     The current overloading rules work out of the box, an rvalue goes
>     for the move
>     constructor, and an lvalue goes to the copy constructor.
> 
>     The problem here is when I want to move t into s. How do I get it to
>     call the
>     move constructor?
>     ```
>     S move(ref S s) { return s; } // convert argument from lvalue to rvalue
>     S s = move(t);
>     ```
>     This works, however, it creates a copy of s and then moves the copy!
>     There needs
>     to be a way to tell the compiler to use the move construct, hence:
>     ```
>     S s = __rvalue(t);
>     ```
>     All __rvalue does is flag the expression in ( ) as an rvalue. Then
>     the rest of
>     the semantics go from there. Note that a struct parameter with a move
>     constructor will always pass by ref regardless, which is what we
>     want here.
>     Also, move semantics will only work on structs. Not classes,
>     integers, pointers,
>     arrays, etc. If move semantics are desired for them, they'll need to
>     be wrapped
>     in a struct, or use a template to wrap it for you.
> 
>     Consider:
>     ```
>     this(ref S);   // copy constructor
>     this(@move S); // move constructor
>     ```
>     I don't know how to make overloading work with this.
> 
> 
> It's always some weird little detail that changes when I feel like we 
> discussed/designed a thing and then you implement it! :P
> your __rvalue() is essentially the `T move(ref T)` intrinsic we 
> discussed at length; so why not just make the move intrinsic rather than 
> this thing? It seems trivial, but the reason I say this (as we discussed 
> at length), is that in addition to the trivial act of stripping away the 
> ref to make it an rvalue, the move expression will inevitably need to be 
> enhanced with lifetime tracking semantics. In the future, you will want 
> to put logic in that intrinsic to mark the end of lifetime of the memory 
> region, and possibly mark the transition of ownership for ownership 
> tracking... 'move' is the operation we seek, and it has lifetime related 
> semantics involved with the operation. __rvalue() doesn't feel like the 
> right abstraction for that set of semantics; you're gonna find yourself 
> wondering where to pin the lifetime semantics in the future...
> 
> Timon: Do you have any comment? Am I wrong?

I don't think you are wrong, there are just a few different ways to go 
about this and to me it seems Walter opened a discussion to explore the 
design space a bit further.

I think `__rvalue` indeed boils down to a `move` intrinsic, just by a 
less nice name.

As far as I understand, Walter's current intention is to keep the memory 
region alive by default after a move, where killing the memory region is 
a potential optimization enabled by alias analysis.

In any case, I think if it is not possible to implement a `move` 
function that internally uses `__rvalue` and behaves just like 
`__rvalue` from the outside, that would indicate an issue with the type 
system and it in particular precludes perfect forwarding for moves 
without unnecessary intermediate memory operations and move constructor 
calls. This is why I also discuss enabling the functionality via 
parameter attributes instead.


More information about the Digitalmars-d mailing list