October 09
On Tuesday, October 8, 2024 11:42:49 PM MDT Manu via Digitalmars-d wrote:
> On Sun, 6 Oct 2024 at 14:06, Walter Bright via Digitalmars-d <
> 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()`.
>
> It's not right to distinguish move constructors, by-value argument is a
> potential move opportunity.
> Why aren't the regular parameter matching semantics sufficient? Can you
> please give us a set of examples where the regular parameter matching
> semantics are failing or infinite-looping?

Aside from whatever Walter's reasoning is, there are existing constructors in the wild which use

    this(S)

to construct a copy of a struct. This allows code such as

    auto s = S(otherS);

and

    auto s = new S(otherS);

to compile. This is particularly useful in generic code where you're dealing with a variant type, since without that, you have to either use a static if branch every time that you construct it in case the variant type is being passed in, or you have to create the type with a wrapper function that does the static if for you. By just creating a constructor that specifically takes the same type (or having a templated constructor which does the appropriate static if internally), that issue is avoided. And while it can certainly be argued whether that's the best approach, it's an approach that exists in the wild today. So, if

    this(S)

suddenly becomes a move constructor, existing code will have a normal constructor suddenly turned into a move constructor.

It's already problematic enough that copy constructors don't have an explicit identifier of some kind (which IMHO really should be fixed).

- Jonathan M Davis



October 09
For what it's worth, I've been in what I assume to be pretty much the same situation as Weka, looking for a way to @safely provide stack allocated buffers to asynchronous routines. Having this provides a considerable opportunity for performance optimization, particularly for I/O workloads.

Because there was no such way, I went with a central heap allocation scheme instead - it just seemed too unsafe to expose an API like that in an open-source library.

To me the big question is: How important is having an actual move constructor? Do we have any compelling use cases where it is not sufficient to disable moves and fall back to copy/destroy instead? Or is this just about C++ compatibility?

Otherwise it would be sufficient to simply introduce a way to prohibit moving a type and avoid the pile of complications and implications of move constructors (such as the `opOpMove` mentioned by Manu or questions w.r.t. always guaranteeing a valid memory address for a struct).
October 09
On Wed, 9 Oct 2024 at 16:09, Jonathan M Davis via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On Tuesday, October 8, 2024 11:42:49 PM MDT Manu via Digitalmars-d wrote:
> > On Sun, 6 Oct 2024 at 14:06, Walter Bright via Digitalmars-d <
> > 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()`.
> >
> > It's not right to distinguish move constructors, by-value argument is a
> > potential move opportunity.
> > Why aren't the regular parameter matching semantics sufficient? Can you
> > please give us a set of examples where the regular parameter matching
> > semantics are failing or infinite-looping?
>
> Aside from whatever Walter's reasoning is, there are existing constructors in the wild which use
>
>     this(S)
>
> to construct a copy of a struct. This allows code such as
>
>     auto s = S(otherS);
>
> and
>
>     auto s = new S(otherS);
>
> to compile. This is particularly useful in generic code where you're
> dealing
> with a variant type, since without that, you have to either use a static if
> branch every time that you construct it in case the variant type is being
> passed in, or you have to create the type with a wrapper function that does
> the static if for you. By just creating a constructor that specifically
> takes the same type (or having a templated constructor which does the
> appropriate static if internally), that issue is avoided. And while it can
> certainly be argued whether that's the best approach, it's an approach that
> exists in the wild today. So, if
>
>     this(S)
>
> suddenly becomes a move constructor, existing code will have a normal constructor suddenly turned into a move constructor.
>

Hmmmm. Well, that's not and never was a copy constructor... so are we required to accept that that was ever correct code?


It's already problematic enough that copy constructors don't have an
> explicit identifier of some kind (which IMHO really should be fixed).
>

What kind of problem is a result of copy constructors not having an identifier?


October 09
On Wednesday, October 9, 2024 12:54:21 AM MDT Manu via Digitalmars-d wrote:
> On Wed, 9 Oct 2024 at 16:09, Jonathan M Davis via Digitalmars-d <
> > Aside from whatever Walter's reasoning is, there are existing constructors in the wild which use
> >
> >     this(S)
> >
> > to construct a copy of a struct. This allows code such as
> >
> >     auto s = S(otherS);
> >
> > and
> >
> >     auto s = new S(otherS);
> >
> > to compile. This is particularly useful in generic code where you're
> > dealing
> > with a variant type, since without that, you have to either use a static
> > if
> > branch every time that you construct it in case the variant type is being
> > passed in, or you have to create the type with a wrapper function that
> > does
> > the static if for you. By just creating a constructor that specifically
> > takes the same type (or having a templated constructor which does the
> > appropriate static if internally), that issue is avoided. And while it can
> > certainly be argued whether that's the best approach, it's an approach
> > that
> > exists in the wild today. So, if
> >
> >     this(S)
> >
> > suddenly becomes a move constructor, existing code will have a normal constructor suddenly turned into a move constructor.
>
> Hmmmm. Well, that's not and never was a copy constructor... so are we required to accept that that was ever correct code?

It's perfectly legal D. There is not and never has been a requirement that you can't have a constructor that takes the same type as the original type. Copy constructors didn't even used to exist in the language at all. You can certainly argue that such a constructor is a bad idea, but it's perfectly legal, and it exists in the wild. And unlike copy constructors or move constructors, it's just a normal constructor.

> It's already problematic enough that copy constructors don't have an
> > explicit identifier of some kind (which IMHO really should be fixed).
>
> What kind of problem is a result of copy constructors not having an identifier?

Rather than giving the ability to indicate that a constructor is a copy constructor, copy constructors are thoroughly restricted such that they can't even be templated (which is a serious problem, particularly when attributes get involved). And in order to get them to even work in many circumstances, you're forced to put a veritable sea of attributes on them (which of course shouldn't be necessary, but that's a separate issue), making it so that figuring out that something is a copy constructor requires examining the signature fairly closely. It would be _far_ cleaner to have them clearly marked so that they would not only be easy to find, but the compiler could tell you when you got it wrong, whereas right now, you have to hope (and test) that you actually declared a copy constructor that gets used as a copy constructor.

On top of that, the current situation is a royal pain with regards to metaprogramming. Finding the constructor which is the copy constructor requires some very careful metaprogramming to ensure that you correctly determine which one it is - or that you correctly determine whether there's even one there at all. And that's because it's not clearly marked as a copy constructor in any way, shape, or form. You have to know what the exact type is your dealing with and examine the parameters appropriately to figure it out, and it's much harder to do than I would have ever expected, and it really should not be that hard. It would be _so_ much cleaner if the copy constructor were treated as distinct rather than just another constructor, particularly since it really isn't just another constructor even if it looks like one.

Of course, the far bigger problem with copy constructors is that the compiler is utter garbage at generating them when a struct has any member variables with copy constructors. It won't ever generate more than one, and it insists on it being inout, whereas the correct solution would be to generate all of the various combinations that are required based on the struct's member variables (both with regards to type qualifers and attributes). The result is that you quickly get into a disgusting mess if you don't declare copy constructors with a very specific signature that won't work in many situations - and with stuff like ranges where you're calling a function that someone else wrote instead of declaring all of the types yourself, you're just screwed, because those realistically need to have the compiler generating the copy constructors for you instead of the programmer declaring them manually (since whether they should even exist depends on the template arguments, as do which overloads should exist).

As things stand, copy constructors in D are borderline garbage. They only work well in very simple circumstances.

- Jonathan M Davis



October 09
On Wednesday, 9 October 2024 at 08:38:52 UTC, Jonathan M Davis wrote:
> On Wednesday, October 9, 2024 12:54:21 AM MDT Manu via

This PR: https://github.com/dlang/dmd/pull/16429 implements the generation of multiple copy constructors as described by Jonathan. It's green and if merged it would alleviate the issues for fields with copy constructors. However, for the metaprogramming part there really isn't any way to identify whether a templated function is a copy constructor without instantiating the copy constructor, therefore the compiler cannot know whether such a struct has a copy constructor or not.

Marking the copy constructor with some particular syntax will solve the problem, however, you still need to perform some checks upon instantiation to make sure the that instantiated function respects the copy constructor signature.

Also, I agree with Jonathan and Paul that any discussion regarding the syntax of the move constructor should also include the copy constructor since we want to have consistent syntax across special functions. We don't want to have:

```d
struct S
{
    this(ref S s);   // copy constructor
    =this(S s);      // move constructor
      ... or ...
     this.move;
}
```

Regards,
RazvanN
October 09
On Wednesday, 9 October 2024 at 09:17:04 UTC, RazvanN wrote:
>
> Also, I agree with Jonathan and Paul that any discussion regarding the syntax of the move constructor should also include the copy constructor since we want to have consistent syntax across special functions. We don't want to have:
>
> ```d
> struct S
> {
>     this(ref S s);   // copy constructor
>     =this(S s);      // move constructor
>       ... or ...
>      this.move;
> }
> ```
>
> Regards,
> RazvanN

thisMove, thisCopy,

same rationale as opEquals, etc...
October 09
On Monday, 7 October 2024 at 00:16:33 UTC, Richard (Rikki) Andrew Cattermole wrote:
> On 07/10/2024 12:09 PM, Richard (Rikki) Andrew Cattermole wrote:
>> ```d
>> this(ref Thing other) @move {
>> }
>> ```
>
> There is a very good reason to prefer an attribute rather than new syntax, it doesn't break tooling.

Underrated comment. We shouldn't be breaking parsers when we don't need to.

In fact any solution not using the written word 'move' or 'copy' does not have intuitive syntax. I don't understand why anyone is trying to argue that using a sigil in a novel way is intuitive.
October 09

On Wednesday, 9 October 2024 at 06:46:52 UTC, Sönke Ludwig wrote:

>

Otherwise it would be sufficient to simply introduce a way to prohibit moving a type and avoid the pile of complications and implications of move constructors

Prohibition of moving would look nicely as

@disable @move this();
October 10
On 09/10/2024 10:17 PM, RazvanN wrote:
> On Wednesday, 9 October 2024 at 08:38:52 UTC, Jonathan M Davis wrote:
>> On Wednesday, October 9, 2024 12:54:21 AM MDT Manu via
> 
> This PR: https://github.com/dlang/dmd/pull/16429 implements the generation of multiple copy constructors as described by Jonathan. It's green and if merged it would alleviate the issues for fields with copy constructors.

This needs to be talked about at tomorrows meeting (which I might not be at).

It is a huge benefit.
October 10
On Sun, Oct 6, 2024 at 3:06 PM Walter Bright via Digitalmars-d < digitalmars-d@puremagic.com> 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()`.
>
>
 as somebody who had to look up what a move constructor was.
So coming from a newb perspective.

My suggestion for a new syntax would be

*this(S)

as long as that doesn't screw up language analysis in other places, since there is an association between linking to the original data in the move constructor and pointers.

I think the existing syntax is cleaner though if you can make it work.