March 05, 2021
On Friday, 5 March 2021 at 12:19:54 UTC, Mike Parker wrote:
> This is the discussion thread for the first round of Community Review of DIP 1040, "Copying, Moving, and Forwarding":
>
> [...]

Does this DIP allow for creation for move constructors for external(c++) classes for c++ version? It would be missed opportunity not include it in the dip.

-Alex
March 06, 2021
On Friday, 5 March 2021 at 23:32:04 UTC, Paul Backus wrote:
> On Friday, 5 March 2021 at 23:03:57 UTC, tsbockman wrote:
>> From the DIP:
>>> A Move Constructor for struct S is declared as:
>>>     this(S s) { ... }
>>> A Move Assignment Operator for struct S is declared as:
>>>     void opAssign(S s) { ... }
>>
>> Is the parameter to these methods really pass-by-value?
>>
>> If so, why???
>
> 1) When you pass an rvalue to a by-value parameter in D, it's moved, not copied.

This is good to know, but doesn't solve the problem I asked about; it just makes it more complicated. According to the DIP, lvalues are moved in some circumstances also, not just rvalues.

So, the move constructor and move assignment operator taking their argument by value thus imply that every custom move operation *either* infinitely recurses (for rvalues), *or* triggers a redundant copy (for lvalues).

> 2) If you have a by-ref overload and a by-value overload for the same function, the ref one is called for lvalues and the by-value one is called for rvalues.

You seem focused on overload selection, but I'm asking what would actually happen when the move operator is called:

1) Does it copy the data to a temporary, or not?
2) Is the address of the move operator's argument the address of the original value to be moved, or not?

The currently proposed syntax implies that the answers are (1) yes and (2) no, but I think the answers need to be (1) no and (2) yes for sanity, performance, and flexibility reasons. For example, knowing the source address for the move would allow the move operator to actually tell whether something is an interior pointer that needs to be updated, or not.

> In fact, this is already how you have to write your opAssign if you want to support explicit move-assignment from rvalues of non-copyable types. See for example SumType's opAssign overload [1], and the corresponding unit test [2].

It's the move constructor, which we don't have yet, that implies infinite recursion (https://issues.dlang.org/show_bug.cgi?id=20424). Also, this proposal is about adding proper support for custom move operators to the language, because what we have right now isn't good enough. So, "that's how it works now" isn't a compelling reason to keep doing it that way.
March 06, 2021
On Friday, 5 March 2021 at 22:04:27 UTC, Max Haughton wrote:
> This is true, however it's worth saying that typically the reason for having move semantics isn't so much performance (for small objects) but managing the lifetime of the struct properly. In this case for move semantics to be useful S would probably have a destructor, which prevents passing in registers anyway.

That is true in C++, that is NOT true in D at the moment.

If the proposal ends up forcing passage of non POD by reference rather than value at the ABI level (like in C++), then it's a non starter IMO.
March 07, 2021
On Saturday, 6 March 2021 at 19:16:51 UTC, deadalnix wrote:
> On Friday, 5 March 2021 at 22:04:27 UTC, Max Haughton wrote:
>> This is true, however it's worth saying that typically the reason for having move semantics isn't so much performance (for small objects) but managing the lifetime of the struct properly. In this case for move semantics to be useful S would probably have a destructor, which prevents passing in registers anyway.
>
> That is true in C++, that is NOT true in D at the moment.
>
> If the proposal ends up forcing passage of non POD by reference rather than value at the ABI level (like in C++), then it's a non starter IMO.

The point about destructors at least is true at least according to ldc and gdc.
March 08, 2021
On Friday, 5 March 2021 at 12:19:54 UTC, Mike Parker wrote:
>

1. The "Returning an EMO by value" section says:

"
S func()
{
    S s;
    return s;
}

This works exactly as it does currently for non-EMO objects.
"

So this code is allowed to compile. However, in the "Returning an EMO by move ref" section we have this code:

"
S func(return S s)
{
    S s2;
    return s2;  // error, can't return local by Move Ref
}
"

Isn't this rather restrictive? I can imagine a scenario where you would like to return the parameter if some condition is met or return a local otherwise:

S func(return S s)
{
   S s2;
   if (_some_condition)
     return s;
   else
     return s2;
}

Moreover, what happens in this case if we have a struct that's not an EMO, but defines a move constructor?

2. What are the situations where a move constructor call is implicitly inserted by compiler? This is not explicitly stated in the DIP and it is rather confusing. For example, when an instance is passed by `move ref` is there a move constructor call?

3. The DIP should explicitly state what happens when you pass an rvalue instance of an EMO by ref. How does that interact with `-preview=rvaluerefparam` ?

4. The perfect forwarding section is superficially described and it is hard to assess its correctness and relationship to the move constructor.

5. Structs with internal pointers that are moved should be part of the motivation of the DIP.

Cheers,
RazvanN
March 08, 2021
On 3/8/2021 12:23 AM, RazvanN wrote:
> On Friday, 5 March 2021 at 12:19:54 UTC, Mike Parker wrote:
>>
> 
> 1. The "Returning an EMO by value" section says:
> 
> "
> S func()
> {
>      S s;
>      return s;
> }
> 
> This works exactly as it does currently for non-EMO objects.
> "
> 
> So this code is allowed to compile. However, in the "Returning an EMO by move ref" section we have this code:
> 
> "
> S func(return S s)
> {
>      S s2;
>      return s2;  // error, can't return local by Move Ref
> }
> "
> 
> Isn't this rather restrictive?

The trouble is determining when the caller allocates the space for local variable, as it does with NRVO. Hence the restriction.


> I can imagine a scenario where you would like to return the parameter if some condition is met or return a local otherwise:
> 
> S func(return S s)
> {
>     S s2;
>     if (_some_condition)
>       return s;
>     else
>       return s2;
> }
> 
> Moreover, what happens in this case if we have a struct that's not an EMO, but defines a move constructor?

That means it does not have a Move Assignment Operator. It doesn't get EMO semantics. The Move Constructor section applies.


> 2. What are the situations where a move constructor call is implicitly inserted by compiler? This is not explicitly stated in the DIP and it is rather confusing.

It's in the Move Constructor section.


> For example, when an instance is passed by `move ref` is there a move constructor call?

No, because it is passed by ref.

> 3. The DIP should explicitly state what happens when you pass an rvalue instance of an EMO by ref. How does that interact with `-preview=rvaluerefparam` ?

With EMOs, there is no need to use the 'ref' annotation. If you do use 'ref', the special EMO semantics do not apply.


> 4. The perfect forwarding section is superficially described and it is hard to assess its correctness and relationship to the move constructor.

Please be more specific?

> 5. Structs with internal pointers that are moved should be part of the motivation of the DIP.

Yes.

March 08, 2021
On Monday, 8 March 2021 at 10:38:25 UTC, Walter Bright wrote:
> On 3/8/2021 12:23 AM, RazvanN wrote:
>> On Friday, 5 March 2021 at 12:19:54 UTC, Mike Parker wrote:
>>>

>> Moreover, what happens in this case if we have a struct that's not an EMO, but defines a move constructor?
>
> That means it does not have a Move Assignment Operator. It doesn't get EMO semantics. The Move Constructor section applies.
>
Even if the move assignment operator is implicitly generated? The DIP states: "If a Move Assignment Operator is not defined for a struct that has a Move constructor, a default Move Assignment Operator is defined and implemented as a move for each of its fields, in lexical order."

Is it possible to have a non-EMO struct that defines solely a move constructor or solely a move assignment operator? It seems like if you define one, you implicitly get the other one.
>
>> 2. What are the situations where a move constructor call is implicitly inserted by compiler? This is not explicitly stated in the DIP and it is rather confusing.
>
> It's in the Move Constructor section.
>
>
>> For example, when an instance is passed by `move ref` is there a move constructor call?
>
> No, because it is passed by ref.
Ok, correct me if I am wrong, but it seems that if you define a move constructor, you implicitly get a move assignment operator and viceversa. This means that your struct becomes an EMO if you define one or the other. Once you have an EMO struct, besides the trivial `S a = b` are there any other situations where the move constructor may be called? It seems that EMOs are always passed by reference. If that is the case, why bother defining a move constructor when it will not get called? If I am mistaken, can you please provide a non-trivial example where the move constructor gets called? Also, will a move constructor ever get called when an argument is passed to a function?
>
>> 3. The DIP should explicitly state what happens when you pass an rvalue instance of an EMO by ref. How does that interact with `-preview=rvaluerefparam` ?
>
> With EMOs, there is no need to use the 'ref' annotation. If you do use 'ref', the special EMO semantics do not apply.
>
So I assume you get an error? Also, what happens with `auto ref` deduction when called with an EMO.
>
>> 4. The perfect forwarding section is superficially described and it is hard to assess its correctness and relationship to the move constructor.
>
> Please be more specific?
Sorry for being un-informative. I will explain in more detail.
The DIP has this example:

ref S fwd(return ref S s) { return s; }

void f(S s);
...
S s;
f(fwd(s));
f(fwd(S());

Assuming S is an EMO, when we have `f(fwd(S()))` what happens here? Is a reference to the rvalue passed to `fwd` or does the move constructor get called?
If we simply call `f(S())`, what happens here? Is `S()` passed by move ref or do we have a move constructor call?

The DIP talks about move refs, but the examples only use lvalues. Is the move constructor ever called for an rvalue instance of an EMO?
>
>> 5. Structs with internal pointers that are moved should be part of the motivation of the DIP.
>
> Yes.


March 08, 2021
On Monday, 8 March 2021 at 08:23:55 UTC, RazvanN wrote:
> "
> S func(return S s)
> {
>     S s2;
>     return s2;  // error, can't return local by Move Ref
> }
> "
>
> Isn't this rather restrictive?

It is important for soundness. When you pass by ref, the owner remains the caller, not the callee.

> I can imagine a scenario where you would like to return the parameter if some condition is met or return a local otherwise:
>

Yes, absolutely, in which case you either want to return it by ref, or take it by value.

March 10, 2021
On Friday, 5 March 2021 at 23:03:57 UTC, tsbockman wrote:
> On Friday, 5 March 2021 at 12:19:54 UTC, Mike Parker wrote:
>> This is the discussion thread for the first round of Community Review of DIP 1040, "Copying, Moving, and Forwarding":
>
> From the DIP:
>> A Move Constructor for struct S is declared as:
>>     this(S s) { ... }
>> A Move Assignment Operator for struct S is declared as:
>>     void opAssign(S s) { ... }
>
> Is the parameter to these methods really pass-by-value?
> ...
> If the parameter is, in fact, intended to be pass-by-reference, then I must strenuously object to the chosen syntax.

Over in the feedback thread, Atila Neves also concluded that the syntax is misleading here:

On Wednesday, 10 March 2021 at 21:27:25 UTC, Atila Neves wrote:
> I eventually understood what this meant, but this confused me when I read it the first time. I'd reword it to mention that the syntax looks like a by-value parameter but ends up being passed by reference. It also confused me that the 2nd function had `ref` in there.

Am I the only one who thinks that it would be better to have syntax that accurately reflects the semantics, instead of just documenting "this syntax is a lie"?
March 10, 2021
On Wednesday, 10 March 2021 at 22:51:58 UTC, tsbockman wrote:
> On Friday, 5 March 2021 at 23:03:57 UTC, tsbockman wrote:
>> On Friday, 5 March 2021 at 12:19:54 UTC, Mike Parker wrote:
>>> This is the discussion thread for the first round of Community Review of DIP 1040, "Copying, Moving, and Forwarding":
>>
>> From the DIP:
>>> A Move Constructor for struct S is declared as:
>>>     this(S s) { ... }
>>> A Move Assignment Operator for struct S is declared as:
>>>     void opAssign(S s) { ... }
>>
>> Is the parameter to these methods really pass-by-value?
>> ...
>> If the parameter is, in fact, intended to be pass-by-reference, then I must strenuously object to the chosen syntax.
>
> Over in the feedback thread, Atila Neves also concluded that the syntax is misleading here:
>
> On Wednesday, 10 March 2021 at 21:27:25 UTC, Atila Neves wrote:
>> I eventually understood what this meant, but this confused me when I read it the first time. I'd reword it to mention that the syntax looks like a by-value parameter but ends up being passed by reference. It also confused me that the 2nd function had `ref` in there.
>
> Am I the only one who thinks that it would be better to have syntax that accurately reflects the semantics, instead of just documenting "this syntax is a lie"?

I'm still fairly open minded about this, however aesthetically at least I really like the idea of move semantics being fairly invisible - e.g. it's not passing a struct by move but rather being up to the struct. When you take into account the object lifetime in a move it's not pass by value of reference.

Definitely needs to be carefully considered.