October 17
On Thursday, 17 October 2024 at 00:05:55 UTC, Manu wrote:
> Special-casing the constructor is just admitting that the overload selection mechanics are broken for **all other function calls**... It's not a matter of 'preference'; if the overload mechanics work properly then special casing the constructor wouldn't even be thinkable or raised in conversation. It's a hack; a really dumb hack which just leaves EVERY OTHER FUNCTION broken.
>
> The fact you have "no strong preference" surprises me, maybe I've misunderstood some way in which this is not a hack that's totally broken? Can you explain to me how every other function call isn't broken under the special-case-for-move-constructor solution? Overload selection has to work, it is basically the meat of this whole thing... there's not really anything else to it.

The reason overload selection is broken for constructors but not for other functions is that "constructors" in D are really several different kinds of functions lumped together into a single overload set.

These include

1. Initialization functions
2. Conversion functions
3. Copying functions

And in the future, we may add

4. Moving functions

There is no fundamental reason why these different kinds of functions should share the same name and overload set. Indeed, there are languages where they do not--in Rust, for example, conversion is done with `from` and `into`, and copying is done with `clone`, with the constructor reserved solely for initialization.

It is only because D forces the programmer to combine all of these into a single overload set that the normal overload resolution mechanism is insufficient, and we need a special case to separate them again.
October 17
On 10/17/24 02:05, Manu wrote:
> On Thu, 17 Oct 2024, 07:36 Timon Gehr via Digitalmars-d, <digitalmars- d@puremagic.com <mailto:digitalmars-d@puremagic.com>> wrote:
> ...
> 
>     I agree with Manu's reasoning why having `this(ref S)` and `this(S)`
>     work as "initialization from lvalue" and "initialization from rvalue",
>     corresponding to copy and move respectively would be cleaner.
> 
>     But overall I don't have a strong preference, as dedicated syntax also
>     has some benefits. The thing I think is most important is getting the
>     semantics right.
> 
> 
> Special-casing the constructor is just admitting that the overload selection mechanics are broken for **all other function calls**... It's not a matter of 'preference'; if the overload mechanics work properly then special casing the constructor wouldn't even be thinkable or raised in conversation. It's a hack; a really dumb hack which just leaves EVERY OTHER FUNCTION broken.
> 
> The fact you have "no strong preference" surprises me, maybe I've misunderstood some way in which this is not a hack that's totally broken?

There is a tradeoff. I think a `this(S)` has to call the destructor of the argument at the end of its scope. This is the right thing to do for normal functions, but you might expect a move constructor to not call it as it already destroys it as part of its normal operation. DIP1040 tried to fix this by _special-casing the constructor_. ;)

With a syntax based on `ref`, it is clear that there will not be a destructor call.

> Can you explain to me how every other function call isn't broken under the special-case-for-move-constructor solution?

Move semantics still needs a separate solution, but this thread is about move constructors. Move constructors are not needed for move semantics, they are needed to manually hook moves that involve a transfer of values between different memory locations.

> Overload selection has to work, it is basically the meat of this whole thing... there's not really anything else to it.
> 
> Broken calling semantics for every function other than the constructor is not a 'compromise', it baffles me that people would even consider it.
> I mean, you don't 'call' constructors so often, but you do call everything else.
> 

I am completely with you here.
October 17
On 10/17/24 15:11, Timon Gehr wrote:
> 
> 
>> Can you explain to me how every other function call isn't broken under the special-case-for-move-constructor solution?
> 
> Move semantics still needs a separate solution, but this thread is about move constructors. Move constructors are not needed for move semantics, they are needed to manually hook moves that involve a transfer of values between different memory locations.

Maybe here part of your question was why special-casing move constructors solves the overload resolution issue? The issue is an implementation detail that no longer occurs when move constructor and copy constructor are not functions in the same overload set.

I think it is a nice side-effect that this problem is fixed, but it should not be the main motivation for special move constructor syntax.
October 17
On Thu, 17 Oct 2024, 12:01 Paul Backus via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Thursday, 17 October 2024 at 00:05:55 UTC, Manu wrote:
> > Special-casing the constructor is just admitting that the overload selection mechanics are broken for **all other function calls**... It's not a matter of 'preference'; if the overload mechanics work properly then special casing the constructor wouldn't even be thinkable or raised in conversation. It's a hack; a really dumb hack which just leaves EVERY OTHER FUNCTION broken.
> >
> > The fact you have "no strong preference" surprises me, maybe I've misunderstood some way in which this is not a hack that's totally broken? Can you explain to me how every other function call isn't broken under the special-case-for-move-constructor solution? Overload selection has to work, it is basically the meat of this whole thing... there's not really anything else to it.
>
> The reason overload selection is broken for constructors but not for other functions is that "constructors" in D are really several different kinds of functions lumped together into a single overload set.
>

I can't imagine any other way.
Here is a construction expression:
  S(arg)

What kind of constructor that is completely depends on arg, and it's selected by overload resolution. That expression could be a copy, or a move, or just a normal initialisation from an unrelated argument...


These include
>
> 1. Initialization functions
> 2. Conversion functions
> 3. Copying functions
>
> And in the future, we may add
>
> 4. Moving functions
>
> There is no fundamental reason why these different kinds of functions should share the same name and overload set.


Yes there is:
  S(arg)

That's how you initialise an S... It's not a suite of separate concepts; it's just "initialise an S", and the proper constructor is selected by overload resolution.


Indeed,
> there are languages where they do not--in Rust, for example, conversion is done with `from` and `into`, and copying is done with `clone`, with the constructor reserved solely for initialization.
>

Our syntax is very clear: S(arg)

It is only because D forces the programmer to combine all of
> these into a single overload set that the normal overload resolution mechanism is insufficient, and we need a special case to separate them again.
>

I still don't see it. There's no reason to separate them... why do you say they should be 'separated'? What does that even mean?

>


October 18
On Thu, 17 Oct 2024, 23:16 Timon Gehr via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 10/17/24 02:05, Manu wrote:
> > On Thu, 17 Oct 2024, 07:36 Timon Gehr via Digitalmars-d, <digitalmars-
> > d@puremagic.com <mailto:digitalmars-d@puremagic.com>> wrote:
> > ...
> >
> >     I agree with Manu's reasoning why having `this(ref S)` and `this(S)`
> >     work as "initialization from lvalue" and "initialization from
> rvalue",
> >     corresponding to copy and move respectively would be cleaner.
> >
> >     But overall I don't have a strong preference, as dedicated syntax
> also
> >     has some benefits. The thing I think is most important is getting the
> >     semantics right.
> >
> >
> > Special-casing the constructor is just admitting that the overload selection mechanics are broken for **all other function calls**... It's not a matter of 'preference'; if the overload mechanics work properly then special casing the constructor wouldn't even be thinkable or raised in conversation. It's a hack; a really dumb hack which just leaves EVERY OTHER FUNCTION broken.
> >
> > The fact you have "no strong preference" surprises me, maybe I've misunderstood some way in which this is not a hack that's totally broken?
>
> There is a tradeoff. I think a `this(S)` has to call the destructor of the argument at the end of its scope. This is the right thing to do for normal functions, but you might expect a move constructor to not call it as it already destroys it as part of its normal operation. DIP1040 tried to fix this by _special-casing the constructor_. ;)
>
> With a syntax based on `ref`, it is clear that there will not be a destructor call.


But upon very trivial investigation we quickly determined that all arguments need to be cleaned up; it avoids complicated special cases and additional attribution. If there's an opportunity for destructor elision, it would be an advanced optimisation. It shouldn't be the basic semantic for simplicity's sake... and when you accept that, any complexity around the design disappears completely.


> Can you explain to me how every other function call isn't broken
> > under the special-case-for-move-constructor solution?
>
> Move semantics still needs a separate solution, but this thread is about move constructors. Move constructors are not needed for move semantics, they are needed to manually hook moves that involve a transfer of values between different memory locations.


They're not 'a thing' though, they are just a natural consequence of move semantics. Move construction will work naturally given an rvalue constructor, no special cases, no rules, probably not even one single line of code in the compiler is necessary once move semantics are working generally.

I think this is where it's gotten lost...
Move semantics don't need a "separate solution", they are actually the
conversation we're meant to be having.

This talk about move constructors is where it's all gotten lost in the woods; If move semantics exist, then move constructors don't need "a solution" at all, they just work naturally with no further intervention.

I'm pretty sure that confusion is the basis for the madness going on here... people seem to be obsessed with move construction while completely ignoring or overlooking move semantics in general, or somehow thinking it's separate or secondary?

So, to belabour the point... move semantics is the primary concept here; and there should be no "move constructor" as a distinct feature, it's just a natural consequence of move semantics existing. No conversation about move constructors is necessary... this is all just leading to confusion.

Reframe thought experiments in terms of `void f(ref T)`, and `void f(T)`,
hopefully that should eliminate confusion. Select the proper overload in
that set when calling f(...), and we're done here.

Please, everyone stop talking about "move constructors"... at least until you've understood what move semantics are. Get your head around move semantics, then you will naturally understand how redundant and misleading this entire conversation about move constructors is...


> Overload selection
> > has to work, it is basically the meat of this whole thing... there's not really anything else to it.
> >
> > Broken calling semantics for every function other than the constructor is not a 'compromise', it baffles me that people would even consider it. I mean, you don't 'call' constructors so often, but you do call everything else.
> >
>
> I am completely with you here.


I know, but once readers understand and accept this, they will also understand that move constructors aren't "a thing", they're just a normal constructor with a rvalue as argument, and calling semantics/overload selection is necessarily the same as any other function.

I think this confusion and noise will all disappear as soon as people understand this.

>


October 18
On Thu, 17 Oct 2024, 23:17 Timon Gehr via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 10/17/24 15:11, Timon Gehr wrote:
> >
> >
> >> Can you explain to me how every other function call isn't broken under the special-case-for-move-constructor solution?
> >
> > Move semantics still needs a separate solution, but this thread is about move constructors. Move constructors are not needed for move semantics, they are needed to manually hook moves that involve a transfer of values between different memory locations.
>
> Maybe here part of your question was why special-casing move constructors solves the overload resolution issue? The issue is an implementation detail that no longer occurs when move constructor and copy constructor are not functions in the same overload set.
>

I understand it's an implementation challenge; Razvan talked about it a
bit, but it's the same implementation challenge as any normal function
overloaded the same way afaict. It has to be solved in general.
...at which point, the problem will already have been resolved with respect
to the constructor too, and none of this discussion about the constructor
actually exists. The general solution naturally solves the constructor case
without any additional attention.

I don't think you should talk about the hack as if it's worthy of attention, because the rest of the work naturally factors this problem out of existence, so it's all just a misleading waste of thought and breath. It seems to be confusing people fundamentally.


I think it is a nice side-effect that this problem is fixed, but it
> should not be the main motivation for special move constructor syntax.
>

It's a hack, and I actually think it's just basically counterproductive to humour this train of thought, because it seems to be perpetuating confusion and leading people away from understanding what move semantics even are. Help them arrive at the understanding that this issue doesn't actually exist, and doesn't require a 'solution'.

>


October 17
On 10/17/24 16:56, Manu wrote:
> On Thu, 17 Oct 2024, 23:16 Timon Gehr via Digitalmars-d, <digitalmars- d@puremagic.com <mailto:digitalmars-d@puremagic.com>> wrote:
> 
>     On 10/17/24 02:05, Manu wrote:
>      > On Thu, 17 Oct 2024, 07:36 Timon Gehr via Digitalmars-d,
>     <digitalmars-
>      > d@puremagic.com <mailto:d@puremagic.com> <mailto:digitalmars-
>     d@puremagic.com <mailto:digitalmars-d@puremagic.com>>> wrote:
>      > ...
>      >
>      >     I agree with Manu's reasoning why having `this(ref S)` and
>     `this(S)`
>      >     work as "initialization from lvalue" and "initialization from
>     rvalue",
>      >     corresponding to copy and move respectively would be cleaner.
>      >
>      >     But overall I don't have a strong preference, as dedicated
>     syntax also
>      >     has some benefits. The thing I think is most important is
>     getting the
>      >     semantics right.
>      >
>      >
>      > Special-casing the constructor is just admitting that the overload
>      > selection mechanics are broken for **all other function
>     calls**... It's
>      > not a matter of 'preference'; if the overload mechanics work
>     properly
>      > then special casing the constructor wouldn't even be thinkable or
>     raised
>      > in conversation. It's a hack; a really dumb hack which just
>     leaves EVERY
>      > OTHER FUNCTION broken.
>      >
>      > The fact you have "no strong preference" surprises me, maybe I've
>      > misunderstood some way in which this is not a hack that's totally
>      > broken?
> 
>     There is a tradeoff. I think a `this(S)` has to call the destructor of
>     the argument at the end of its scope. This is the right thing to do for
>     normal functions, but you might expect a move constructor to not
>     call it
>     as it already destroys it as part of its normal operation. DIP1040
>     tried
>     to fix this by _special-casing the constructor_. ;)
> 
>     With a syntax based on `ref`, it is clear that there will not be a
>     destructor call.
> 
> 
> But upon very trivial investigation we quickly determined that all arguments need to be cleaned up; it avoids complicated special cases and additional attribution.

`ref` arguments do not need to be cleaned up by the caller, and the callee has more information about whether cleanup is required.

With `this(S)`, you get one or two destructor calls on dummy objects for an explicit move by default, with `=this(ref S)` it is zero or one.

> If there's an opportunity for destructor elision, it would be an advanced optimisation. It shouldn't be the basic semantic for simplicity's sake... and when you accept that, any complexity around the design disappears completely.
> ...

Don't get me wrong, I prefer `this(S)`, `this(ref S)`, `opAssign(S)`, `opAssign(ref S)`. It's just not a strong preference. I also agree it is simpler. Your preference may be more pronounced than mine if you place a higher emphasis on simplicity.

> 
>      > Can you explain to me how every other function call isn't broken
>      > under the special-case-for-move-constructor solution?
> 
>     Move semantics still needs a separate solution, but this thread is
>     about
>     move constructors. Move constructors are not needed for move semantics,
>     they are needed to manually hook moves that involve a transfer of
>     values
>     between different memory locations.
> 
> 
> They're not 'a thing' though, they are just a natural consequence of move semantics. Move construction will work naturally given an rvalue constructor, no special cases, no rules, probably not even one single line of code in the compiler is necessary once move semantics are working generally.
> ...

Well, this is not true. The compiler sometimes moves things implicitly, which may need a call to the move constructor. Of course, the best way to implement this would be to have the compiler use `__move` internally, which is then lowered to a move constructor call if needed.

I have given this example a number of times:

```d
S foo(bool x){
    S a,b;
    if(x) return a;
    return b;
}
```

The return value of this function has its own address, so `a` and `b` need to be moved. The compiler needs to call the move constructor here.


Another thing is:


```d
struct T{
    this(T t){ ... }
}

struct S{
    T t;
    int x;
    // (no move constructor)
}

void main(){
    S s0;
    S s1=move(s0); // must call move constructor for T
}
```

Also, the compiler has to detect that `T` and `S` are non-POD types as those have a different ABI.

> I think this is where it's gotten lost...
> Move semantics don't need a "separate solution", they are actually the conversation we're meant to be having.
> ...

That's the same thing. It's a different conversation. I agree it is more important, but the issue is I think a lot of people think move semantics just means move construction and assignment, where addresses change, when it is also about cases where addresses do not necessarily change, such as forwarding.


> This talk about move constructors is where it's all gotten lost in the woods; If move semantics exist, then move constructors don't need "a solution" at all, they just work naturally with no further intervention.
> ...

Well, this is not true.

> I'm pretty sure that confusion is the basis for the madness going on here... people seem to be obsessed with move construction while completely ignoring or overlooking move semantics in general, or somehow thinking it's separate or secondary?
> ...

Some probably think it is the same thing. Anyway, this thread is actually about move constructors, so it seems natural that people talk about move constructors in this thread.

Martin has actually opened a thread about the required steps to achieve move semantics. For some reason it is getting less attention.

> So, to belabour the point... move semantics is the primary concept here; and there should be no "move constructor" as a distinct feature, it's just a natural consequence of move semantics existing. No conversation about move constructors is necessary... this is all just leading to confusion.
> ...

Well, I don't think this is true. Just like the copy constructor needs a little language support, so does the move constructor. Or it just will not be called in some situations where a call is needed.

> Reframe thought experiments in terms of `void f(ref T)`, and `void f(T)`, hopefully that should eliminate confusion. Select the proper overload in that set when calling f(...), and we're done here.
> ...

Well, that part is important, but it is not everything.

> Please, everyone stop talking about "move constructors"... at least until you've understood what move semantics are. Get your head around move semantics, then you will naturally understand how redundant and misleading this entire conversation about move constructors is...
> ...

Idk if it is redundant. Anyway, there is Martin's thread. I am happy to discuss further there as well.

> 
>      > Overload selection
>      > has to work, it is basically the meat of this whole thing...
>     there's not
>      > really anything else to it.
>      >
>      > Broken calling semantics for every function other than the
>     constructor
>      > is not a 'compromise', it baffles me that people would even
>     consider it.
>      > I mean, you don't 'call' constructors so often, but you do call
>      > everything else.
>      >
> 
>     I am completely with you here.
> 
> 
> I know, but once readers understand and accept this, they will also understand that move constructors aren't "a thing", they're just a normal constructor with a rvalue as argument, and calling semantics/ overload selection is necessarily the same as any other function.
> ...

The language has to specify when and how the constructor is called implicitly. That's about the extent of it, but it still needs to be in the spec.

> I think this confusion and noise will all disappear as soon as people understand this.
> 

Probably.
October 18
On Monday, 14 October 2024 at 02:49:42 UTC, Walter Bright wrote:
> On 10/11/2024 8:44 AM, Manu wrote:
>> 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!
>
> But currently this(S) is an rvalue constructor, not a move constructor. I've said this many times.
>
> Changing it would break existing code.

What about considering these syntax in new Edition that D is advocate for breaking changes?
`this(S)`, `this(ref S)`, `opAssign(S)`, `opAssign(ref S)`
October 19
On Sat, 19 Oct 2024, 09:06 An via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Monday, 14 October 2024 at 02:49:42 UTC, Walter Bright wrote:
> > On 10/11/2024 8:44 AM, Manu wrote:
> >> 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!
> >
> > But currently this(S) is an rvalue constructor, not a move
> > constructor. I've said this many times.
> >
> > Changing it would break existing code.
>
> What about considering these syntax in new Edition that D is
> advocate for breaking changes?
> `this(S)`, `this(ref S)`, `opAssign(S)`, `opAssign(ref S)`
>

We have no case studies; as far as we've determined so far, this is the only potentially breaking change, and we have no evidence yet other than theory that it actually is a breaking change.

Let's worry about this when we actually know of a single case where it
breaks code...
I think it probably doesn't break any code, or that the code in question is
already broken...

>


October 20
On Saturday, October 5, 2024 10:04:28 PM MDT Walter Bright via Digitalmars-d wrote:
> ```
> struct S { ... }
>
> this(ref S) // copy constructor
> this(this)  // postblit
> this(S)     // move constructor
> ~this()     // destructor
>
> alias T = S;
> this(T);    // also move constructor
>
> alias Q = int;
> this(Q);    // regular constructor
> ```
> As the above illustrates, a move constructor cannot be distinguished from a
> regular constructor by syntax alone. It needs semantic analysis.
>
> While this seems simple enough, it isn't I have discovered to my chagrin. The overload rules in the language (including things like rvalue references where sometimes an rvalue becomes an lvalue) that the move constructors get confused with the copy constructors, leading to recursive semantic loops and other problems.
>
> I've struggled with this for days now.
>
> A fix that would simplify the language and the compiler would be to have a unique syntax for a move constructor, instead of the ambiguous one in the proposal. That way, searching for a copy constructor will only yield copy constructors, and searching for a move constructor will only yield move constructors. There will be a sharp distinction between them, even in the source code. (I have seen code so dense with templates it is hard to figure out what kind of constructor it is.)
>
> Something like one of:
> ```
> 1. =this(S)
> 2. this(=S)
> 3. <-this(S)
> ```
> ?
>
> It may take a bit of getting used to. I kinda prefer (1) as it is sorta like
> `~this()`.

I don't know what the current state things is with what you're planning to do with opAssign and moves, but upon thinking further about the issues with move constructors - and dealing with code at Symmetry which has templated opAssigns - it occurs to me that opAssign has the same sort of issues as move constructors do if you try to make opAssign(typeof(this) rhs) do move assignment like the DIP talks about. Existing code could easily break - be it silently or not - and there could be some pretty weird results. I've already had quite a few issues along those lines due to the fact that copy constructors look like normal constructors. That being the case, I'm very much inclined to argue that the move assignment operator should have its own syntax and not just move constructors.

So, either we should probably use something like @move on both move constructors and move assignment operators, or we should have something like opMoveAssign instead of opAssign for move assignment (with =this or whatever for copy constructors). I very much don't want to fight issues with existing code doing wonky things just because move constructors or move assignment operators get added.

On a related note, we need to make sure that move construction and move assignment work correctly with dynamic arrays and AAs. I was shocked to find out recently that dynamic arrays and AAs do _not_ work properly with copy constructors. TypeInfo was never updated to take copy construction into account, and the only druntime hooks that deal with it properly are the ones which have been templated. The result is that using copy constructiors is currently very error-prone, and we need to not do the same thing with move constructors.

- Jonathan M Davis