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