Move Constructor Syntax

ShadoLight ettienne.gilbert at gmail.com
Wed Oct 16 15:02:46 UTC 2024


On Wednesday, 16 October 2024 at 13:23:53 UTC, Arafel wrote:
> On 16.10.24 15:09, ShadoLight wrote:
>>> That would make sense, but this would in turn mean that the 
>>> move constructor can never be invoked explicitly.
>> 
>> What do you mean?
>>     s3 = S(S(2));
>> ...is invoking the move constructor explicitly.
>
> I meant the original syntax before the lowering:
>
>     s3 = S(s2)
>

No, I think we are missing each other. Maybe the examples are a 
bit confusing because of the variable naming. Let's rename them:

Something like:
```d
      S a, b, c;
      a = S(1);      // case(1) -> this (int i)
      b = S(a);      // case(2) -> this (ref S s)  -> implicit 
copy ctor
      c = S(S(2));   // case(3) -> this (S s)
      // ...and a is valid here
```

> IIRC, you say that the compiler would lower it to first a copy 
> constructor to create a temporary, then a move constructor on 
> the temporary. Thus, seen from the outside, it would keep the 
> current behavior (I mean, s2 would still be valid).
>


I'm asking if, in the case where the compiler sees a lvalue being 
used for construction, but with no copy constructor present (like 
in your examples) - it is feasible if the compiler creates a copy 
constructor for you (like in C++)? So no temporary is created in 
case(2), since this (implicit) copy constructor will be invoked, 
and normal copy construction proceeds.

This also guarantees `a` (in my example, or `s1` in your example) 
remains valid after the constructor. This is simple and matches 
what Manu proposes.

case(3 ) `c = S(S(2));` on the other hand passes a rvalue 
instance of S(2) to the move constructor:
- the `this (int i)` constructor is first called and a temporary 
S rvalue is created,
- then the temporary rvalue is passsed to `this (S s)` move 
constructor and 'moved' as per the DIP.


> But what if I did want to move s2 into s3? How would I do it? 
> Or is a move something that cannot be forced explicitly? I 
> didn't see this addressed in the DIP either.
>

In this case we can simply treat moving an existing `s2` into 
`s3` as ambiguous (because you cannot, if you want to maintain 
the normal constructor syntax, differentiate a move (if that is 
what you want) from a copy of a `s2` lvalue into `s3`. If you 
just do...
     `b = S(a);      // case(2) -> this (ref S s)  -> implicit 
copy ctor`
... the copy constructor is always preferred. To force a move, 
you should need to do...
     `b = S(__rvalue(a));`
... to turn `a` into a rvalue, which then matches on the move 
constructor.

I think this is reasonable and AFAICS this is in line with move 
semantics.

I also don't think this will break code. If a copy of s2 into s3 
is done instead of an expected move of s2 into s3 (and s2 is not 
accessed again afterwards - which is expected because why else 
would you prefer the move?), then s2 will either be collected by 
the GC or it's destructor will be called if it is on the stack 
and it goes out of scope.


> The problem I see is that you can't have it both ways with the 
> proposed syntax: either you can't call a move constructor 
> manually on an lvalue because an intermediate copy gets 
> inserted, or you are changing the semantics for the existing 
> usages.
>

Right. Which is why I'm proposing that `copy` takes precedence 
over `move` for lvalues, and `move` takes precedence over `copy` 
for rvalues. This can then be used to optimize finding the best 
match in the overload set.

I think this is in line with Manu's expectations.

> Again, perhaps move constructors are not meant to be invoked 
> directly, but I think the DIP should be explicit about what 
> happens in that case, or state that it's not allowed.

I don't see why not. Just invoke it with an rvalue.


More information about the Digitalmars-d mailing list