October 12
On Saturday, 12 October 2024 at 08:06:16 UTC, Manu wrote:
> 4. rvalue constructors exist and are used, and are NOT move constructors
>
>
> They are rvalue constructors; it doesn't matter what they do, their selection criteria remains identical.
>
> I could similarly write your a copy constructors that doesn't make a copy... that's my prerogative.

You mean rvalue constructors that duplicate their argument are just incorrectly implemented move constructors?
October 12
On 10/11/24 09:25, Manu wrote:
> I don't see how elevating this declaration to a move-constructor actually changes the semantics at all... I don't think it does? I think this function ALREADY IS A MOVE CONSTRUCTOR (at least; it's an rvalue- constructor, which is the same thing from a semantic perspective); it's just working with an inefficient calling convention.
> Move semantics as proposed simply improve this code, no?

DIP1040 suggests to pass the argument implicitly by reference and to not call the destructor on it.

As I mentioned in my DConf talk, this can lead to accidental memory leaks. Particularly if it is existing code that previously assumed the destructor will run.

Another point where it will differ is automatic generation of move constructors and move `opAssign`. I think now it has to be done manually or you get copies.

Finally, for some reason, at the moment you cannot have both `this(ref S)` and `this(S)`. But `opAssign(ref S)` and `opAssign(S)` works. This moves around values in memory a bit more often though. A semantic move may just pass a reference further down the call stack. (IIRC non-POD struct types are always passed by `ref` in the ABI.)
October 12
On 10/11/24 17:44, Manu wrote:
> On Thu, 10 Oct 2024, 17:22 Walter Bright via Digitalmars-d, <digitalmars-d@puremagic.com <mailto:digitalmars-d@puremagic.com>> wrote:
> 
>     On 10/8/2024 11:08 PM, Jonathan M Davis wrote:
>      > So, if
>      >
>      >      this(S)
>      >
>      > suddenly becomes a move constructor, existing code will have a normal
>      > constructor suddenly turned into a move constructor.
> 
>     Yup. Kaboom.
> 
> 
> No that's wrong; this is EXACTLY the situation that move semantics exist to address. Move constructor like this should ACTUALLY BE a move constructor!
> 

I agree that it would be desirable to have:

```d
this(ref S){} // copy constructor
this(S){} // move constructor

void opAssign(ref S){} // copy assign
void opAssign(S){} // move assign
```

Currently this is actually what I do for `opAssign`.

However, ideally there is a way to avoid calling the destructor on the argument after resetting it manually. DIP1040 suggests to just not call it by default. I think there should be an explicit way to elide the destructor call. The destructor should be called by default, as this is the least surprising semantics.

Anyway, apparently Walter has run into some issues with the implementation using this syntax, so not sure this will work out. I don't see why it cannot work though.
October 12
On 10/12/24 10:06, Manu wrote:
> 
>     4. rvalue constructors exist and are used, and are NOT move constructors
> 
> 
> They are rvalue constructors; it doesn't matter what they do, their selection criteria remains identical.

Just as long as the semantics of the actual declaration does not change. DIP1040 proposed to change the semantics of the move constructor declaration itself.

Anyway, I agree that a clean design may be that a constructor that accepts an lvalue of the same type should be a copy constructor and a constructor that accepts an rvalue of the same type should be a move constructor (templated or not). Without any special semantics of the declaration itself.

Then standard overload resolution (with an after-the-fact check whether there is `ref` on the first parameter) can be used to check whether something is copyable or moveable.


I guess one change in behavior is that code that previously worked with implicit compiler-generated moves or explicit moves will now invoke something that had been written as an rvalue constructor, which also precluded there being any user-defined copy constructor in the struct or its fields (disabled or otherwise).

I am not sure for what purpose rvalue constructors are even being written today, but I think the only way to invoke them is as an explicit `S(s)`. If someone does something funky in such a constructor, it may not actually do the right thing if it suddenly starts being called for moves.
October 13
On 10/12/24 02:57, Walter Bright wrote:
> On 10/11/2024 12:06 AM, Manu wrote:
>> But, I think you actually missed my point here; I provided a ref and non-ref overload for Other... how do I move-construct from some OTHER type?
> 
> I'm not sure moving a T to an S even makes sense.
> ...

It seems to me that moving a `T` to an `S` and moving a `T` into an rvalue constructor call for an `S` is not really a useful distinction, and the second thing certainly makes sense.
October 13
On Sun, 13 Oct 2024, 01:21 Kagamin via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Saturday, 12 October 2024 at 08:06:16 UTC, Manu wrote:
> > 4. rvalue constructors exist and are used, and are NOT move constructors
> >
> >
> > They are rvalue constructors; it doesn't matter what they do, their selection criteria remains identical.
> >
> > I could similarly write your a copy constructors that doesn't make a copy... that's my prerogative.
>
> You mean rvalue constructors that duplicate their argument are just incorrectly implemented move constructors?
>

No, that's not what I mean. It's just "a constructor", and you're the master of your code. Write it to do whatever it needs to do!

>


October 13
On Sun, 13 Oct 2024, 07:17 Timon Gehr via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 10/11/24 09:25, Manu wrote:
> > I don't see how elevating this declaration to a move-constructor
> > actually changes the semantics at all... I don't think it does? I think
> > this function ALREADY IS A MOVE CONSTRUCTOR (at least; it's an rvalue-
> > constructor, which is the same thing from a semantic perspective); it's
> > just working with an inefficient calling convention.
> > Move semantics as proposed simply improve this code, no?
>
> DIP1040 suggests to pass the argument implicitly by reference and to not call the destructor on it.
>
> As I mentioned in my DConf talk, this can lead to accidental memory leaks. Particularly if it is existing code that previously assumed the destructor will run.
>

I feel like we resolved this; we agreed that the no destructor call in 1040 was a mistake. If the callee decides to do anything invasive with its argument, like stealing it's allocations in a move operation, it must return it to a destructible state. The memory's lifetime in the calling code would continue, and call the destructor as usual.

I felt confident we agreed on that requirement and it resolved the issues you illustrated?

Another point where it will differ is automatic generation of move
> constructors and move `opAssign`. I think now it has to be done manually or you get copies.
>
> Finally, for some reason, at the moment you cannot have both `this(ref
> S)` and `this(S)`. But `opAssign(ref S)` and `opAssign(S)` works. This
> moves around values in memory a bit more often though. A semantic move
> may just pass a reference further down the call stack.


Yes, pretty much the entire point of this work is enabled passing the point "further down the line", copying at each function boundary is precisely the problem to be solved.

(IIRC non-POD
> struct types are always passed by `ref` in the ABI.)
>

Yes, essentially, we're just acknowledging in the language semantics what is already true in the ABI.

>


October 13
On Sun, 13 Oct 2024, 08:01 Timon Gehr via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 10/12/24 10:06, Manu wrote:
> >
> >     4. rvalue constructors exist and are used, and are NOT move
> constructors
> >
> >
> > They are rvalue constructors; it doesn't matter what they do, their selection criteria remains identical.
>
> Just as long as the semantics of the actual declaration does not change. DIP1040 proposed to change the semantics of the move constructor declaration itself.
>
> Anyway, I agree that a clean design may be that a constructor that accepts an lvalue of the same type should be a copy constructor and a constructor that accepts an rvalue of the same type should be a move constructor (templated or not). Without any special semantics of the declaration itself.
>
> Then standard overload resolution (with an after-the-fact check whether there is `ref` on the first parameter) can be used to check whether something is copyable or moveable.
>
>
> I guess one change in behavior is that code that previously worked with implicit compiler-generated moves or explicit moves will now invoke something that had been written as an rvalue constructor, which also precluded there being any user-defined copy constructor in the struct or its fields (disabled or otherwise).


Yes, that's the case in question. We need to see some case studies; I have
a suspicion that much code that makes sense and is not actually a bug is
actually already some kind of move constructor.
The calling rules choosing a compiler-generated move or an rvalue
constructor are murky (because they are both perfect match; there much be a
hard coded tie-breaker, and it must be circumstantial), and I bet any code
that uses that is brittle and subject to very special care when handling.


I am not sure for what purpose rvalue constructors are even being
> written today, but I think the only way to invoke them is as an explicit
> `S(s)`.


Maybe, and that might be the definition of the hard-coded tie-breaker rule I described a moment ago... It's non-uniform in any event, a complete surprise that there would be 2 separate cases.

If someone does something funky in such a constructor, it may
> not actually do the right thing if it suddenly starts being called for moves.
>

Maybe. Let's find out!
I wouldn't worry about it... It's already a bad API pattern that depends on
weird and brittle rules.

What the hell could it reasonably mean to initialise an S from another S
and not be some kind of move when written in your code exactly the way you
expect a move to appear?
I think if we find examples in the wild either a) it's already a move in
waiting, or b) it's actually broken and the author didn't realise.

My bet is that instances of this code were written by inexperienced D programmers who probably had a poor understanding around the semantics of what they wrote.

But let's find out! We need case studies relating to this super weird and
frankly nonsense pattern.
In any event, if this is the only breaking change we encounter, then we're
in amazingly good shape!
I will submit the PR to correct every project that suffers from this
myself...

>


October 13
On 10/13/24 01:33, Manu wrote:
> On Sun, 13 Oct 2024, 07:17 Timon Gehr via Digitalmars-d, <digitalmars- d@puremagic.com <mailto:digitalmars-d@puremagic.com>> wrote:
> 
>     On 10/11/24 09:25, Manu wrote:
>      > I don't see how elevating this declaration to a move-constructor
>      > actually changes the semantics at all... I don't think it does? I
>     think
>      > this function ALREADY IS A MOVE CONSTRUCTOR (at least; it's an
>     rvalue-
>      > constructor, which is the same thing from a semantic
>     perspective); it's
>      > just working with an inefficient calling convention.
>      > Move semantics as proposed simply improve this code, no?
> 
>     DIP1040 suggests to pass the argument implicitly by reference and to
>     not
>     call the destructor on it.
> 
>     As I mentioned in my DConf talk, this can lead to accidental memory
>     leaks. Particularly if it is existing code that previously assumed the
>     destructor will run.
> 
> 
> I feel like we resolved this; we agreed that the no destructor call in 1040 was a mistake.

Yes, I think that's the best resolution, but you asked "how can elevating a declaration to a move constructor change semantics", and not everyone may be on the same page what a move constructor is, especially as DIP1040 specifies something else. In particular, this may be part of what the person you were replying to (Jonathan? you didn't specify the author) had in mind.

> If the callee decides to do anything invasive with its argument, like stealing it's allocations in a move operation, it must return it to a destructible state. The memory's lifetime in the calling code would continue, and call the destructor as usual.
> 
> I felt confident we agreed on that requirement and it resolved the issues you illustrated?
> ...

Yes, this is one way to handle that. Personally I do not like the whole "destructible state"/.init thing a lot, but I think it does work about as well as null pointers in the worst case.

Also, having the caller be responsible for destruction of rvalue arguments is a bit unfortunate as it prevents tail calls, but it is unlikely to work out anyway given Walter wants to preserve DIP1000 semantics for lifetimes.

>     Another point where it will differ is automatic generation of move
>     constructors and move `opAssign`. I think now it has to be done
>     manually
>     or you get copies.
> 
>     Finally, for some reason, at the moment you cannot have both `this(ref
>     S)` and `this(S)`. But `opAssign(ref S)` and `opAssign(S)` works. This
>     moves around values in memory a bit more often though. A semantic move
>     may just pass a reference further down the call stack.
> 
> 
> Yes, pretty much the entire point of this work is enabled passing the point "further down the line", copying at each function boundary is precisely the problem to be solved.
> 
>     (IIRC non-POD
>     struct types are always passed by `ref` in the ABI.)
> 
> 
> Yes, essentially, we're just acknowledging in the language semantics what is already true in the ABI.
> 

Yup.
October 13
On 10/12/2024 3:12 PM, Timon Gehr wrote:
> It seems to me that moving a `T` to an `S` and moving a `T` into an rvalue constructor call for an `S` is not really a useful distinction, and the second thing certainly makes sense.

Moving into an existing object is not the same as moving into an object that has not been constructed yet.