October 12
On Fri, 11 Oct 2024, 19:21 Jonathan M Davis via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Friday, October 11, 2024 12:48:45 AM MDT Manu via Digitalmars-d wrote:
> > They initialise an S from another S... I'm not sure what you mean by
> "they
> > do different things"?
> >
> > I've been wondering, why do you need to detect that a constructor is a
> copy
> > or move constructor? That concept seems like a big internal hack. Why do normal overload selection semantics fail to determine the proper
> selection?
> > It's not clear why it's useful or important that copy and move
> constructors
> > are separate from all other constructors? They're not special; they just happen to accept a self-type as an init argument... the only reason I can think, is to identify the case where they're not present, and some copy/move code must by synthesised in lieu... what else hangs off that knowledge?
>
> Another example of how they're treated as special is that if you declare no constructors for a struct, you get implicit construction syntax for that type. e.g.
>

You literally gave the example that I gave just one sentence prior. I'm
very aware of this.
I asked for any additional cases... can you think of any?


    struct S
>     {
>         int x;
>         int y;
>     }
>
>     void main()
>     {
>         auto s1 = S(42);
>         auto s2 = S(42, 27);
>     }
>
> As soon as you declare any constructors, you no longer get that implicit
> construction syntax, and you can only construct the type with the
> constructors that you provided - except that copy constructors (and
> eventually move constructors) are not treated as constructors in this
> context. Declaring a copy constructor does not get rid of the implicit
> construction syntax. I've specifically had to deal with this situation when
> wrapping D code in order to create the same constructors that the D type
> has
> but in another language, and the fact that a copy constructor is __ctor
> just
> like all of the other constructors except for postblit constructors makes
> this more of a pain for no good reason.
>
> It's also the case that some metaprogramming needs to know whether a type has a copy constructor or move constructor in order to do the right thing (e.g. the lowerings for assigning slices of arrays to each other have to take that into account as do types like std.typecons.Nullable). Hiding them as normal constructors just makes that harder.
>
> All in all, I don't understand why you want to treat copy constructors or
> move constructors as if they were normal constructors. Unlike normal
> constructors, they get used when you do not call them, and you usually
> don't
> call them at all. They also have a huge impact on the semantics of how a
> type is actually used in many situations in a way that normal constructors
> do not. Treating them as normal constructors makes it harder for the user
> to
> see what's going on, and anyone generating code via metaprogramming then
> has
> a harder time doing that, because detecting them is more complicated.
>
> What benefit do you see in treating copy constructors or move constructors like they're normal constructors instead of explicitly treating them as special - especially given that the compiler already has to treat them as special in order to generate the correct code in many cases?
>

Semantic uniformity. Special casing random things is D's main claim to fame
and the source of almost everything wrong with the language.
The overload selection rules should make the proper choice.

For what it's worth, I sympathize with your concern about template copy
constructors or such; what I do, is declare `this(ref typeof(this))`, and
then beside that declare my other copy-like-constructors with an if(!is(T
== typeof(this)) constraint, or some similar constraint that effectively
excludes the one case represented by the copy constructors.


October 12
On Fri, 11 Oct 2024, 18:36 Jonathan M Davis via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On Friday, October 11, 2024 1:25:53 AM MDT Manu via Digitalmars-d wrote:
> > > Aside from whatever Walter's reasoning is, there are existing
> constructors
> > > in the wild which use
> > >
> > >     this(S)
> >
> > I had a moment to think on this case you present.
> > It's not clear to me how it actually works now; but I think it would be
> > interesting to explore that first; because I think the solution for this
> > case being reinterpreted as move-constructor is related (and equally
> > solved).
> >
> > This function receives it's arg BY-VALUE; so it can happily accept an r-value... but it's not actually clear how this function receives an
> lvalue
> > even in today's language?
> > In order for this function to receive an l-value by-value, it must first
> > make a copy to supply as the function's argument (but the function is
> > allowed to internally mutate its argument)..
> >
> > ...but isn't *this* said to be the copy constructor?
> >
> > This already looks like a chicken/egg problem in today's language... how
> > does it create a copy of an lvalue to pass the argument to this function
> > without calling this function?
> > Whatever answer exists that makes this work equally makes the case work
> > where this is reinterpreted as a move-constructor.
> >
> > The only explanation I can imagine is; because this ISN'T actually a copy constructor, the compiler determined that it was appropriate to
> synthesise
> > one (which would be to just copy all members), and so an implicit copy
> > constructor DOES actually exist beside this constructor, you just didn't
> > write it.
> > And assuming that's the case, when this becomes reinterpreted as a move
> > constructor, the code remains perfectly correct!
> >
> > 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?
>
> No, as far as the language is concerned, it's not a copy constructor, but
> it
> also isn't necessarily a move constructor. In the case that I've described,
>

I think it actually is a move constructor... can you explain how it
compiles and works differently than what I say?
You'll need to show me that my hypothesis is wrong...


it might work as a move constructor, but there's no guarantee that all
> constructors with that signature would.


Yeah no, I think you're just wrong here. It's exactly a move constructor
from a semantic perspective. It receives only rvalues, and the argument
belongs to the callee, which can do whatever it likes with it.
It's literally a move constructor with a retarded calling convention...
unless my hypothesis about how it compiles at all is wrong?


Also, the constructor is question is
> likely to be a template, which will then probably not play well with what
> Walter is trying to do (e.g. copy constructors can't currently be
> templated,
> because that doesn't work unless copy constructors have a unique syntax,
> which they don't). As Razvan explained in this thread, in order for copy
> constructors (or move constructors) to be allowed to be templates, they
> really need a distinct syntax, otherwise the compiler can't know that they
> are in fact copy constructors or move constructors until they're
> instantiated, which causes a variety of issues.
>

They're all copy or move constructor of some flavour; just not the single special case one that the compiler recognises to suppress it's generation of the default one... they can all exist beside each other subject to functioning overload selection rules.

Any constructor that receives any arg by value is a move constructor for all intents and purposes.


Honestly, as a user, I see _zero_ benefit in trying to treat either copy
> constructors or move constructors like they're normal constructors.


Uniformity of semantics. Special cases are bad.

They are
> treated as special by the compiler in a variety of ways,


In exactly one way that we were both able to identify, are there actually other cases?

and I want them to
> be visually distinct so that it's clear that they exist. It would also be nice if the compiler gave me an error when I screwed up a copy constructor or move constructor in a way that it wouldn't work as one for whatever reason (e.g. getting ref wrong by accident). A big part of the problem with the current copy constructors is that it really isn't easy to see at a glance which constructors are copy constructors. And the fact that they can't be templated is a _huge_ problem, particularly with regards to attributes.
>
> While the syntax =this() isn't necessarily great (personally, I'd probably just vote for using @move for move constructors and then change it so that copy constructors should have @copy), I see nothing but problems from not making copy and move constructors distinct and obvious.
>
> - Jonathan M Davis
>

Look, I'm not critically opposed to a special case for the special functions names *if we absolutely do need it and that's just not a lower level design error that we're further compounding*... but I'm absolutely not sold and I think it's a mistake to blindly accept it.

But... and as I mentioned in my response to Walter; reading between the lines, it starts to look like the move semantics somehow uniquely apply to the move constructor and not generally, which is completely insufficient. That's not "move semantics", and it suggests that my reading of the DIP left me with the completely wrong impression.

The move semantic described by the dip should apply to every rvalue everywhere; they're all potential move targets.

Especially so for your weird not-a-move-constructor-but-actually-a-move-constructor that receives T by value; it's the exact definition of a move constructor, and it should be the case that concretely proves that it is also the exact proper syntax for the declaration!

>


October 12
On Thu, 10 Oct 2024, 17:22 Walter Bright via Digitalmars-d, < 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!

>


October 12
On Thu, 10 Oct 2024, 17:10 Walter Bright via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 10/8/2024 10:42 PM, Manu wrote:
> > Can you show us some cases?
>
> I'd get infinite recursion with overload resolution, because the compiler
> will
> try and match the argument to `S` and `ref S`, made even more complicated
> with
> rvalue references enabled.
>

I don't understand; was the argument an rvalue or an lvalue?
It is not at all ambiguous or difficult to select the proper overload
here... one should have been an exact match, the other would have required
a copy or conversion; making it an obviously less preferable match.


The compiler would go into infinite recursion converting a ref to an rvalue
> and
> back to a ref again. There were maybe 10 or more functions in the
> recursion
> stack, looping through the heavy semantic routines.
>

Sounds like a an implementation issue/bug... I don't see a semantic issue
here?
Consult Razvan, or any other DMD experts?
There's a lot of people who have invested deeply into the complexity of
DMD's semantic passes by now who might be willing to help resolve curly
problems?


Another problem was failure to compile libmir:
> ```
> source/mir/rc/ptr.d(395,15): Error: `mir.rc.ptr.castTo` called with
> argument
> types `(mir_rcptr!(C))` matches both:
>    source/mir/rc/ptr.d(275,13):     `mir.rc.ptr.castTo!(I,
> C).castTo(mir_rcptr!(C) context)`
>    and:
>    source/mir/rc/ptr.d(291,25):     `mir.rc.ptr.castTo!(I,
> C).castTo(immutable(mir_rcptr!(C)) context)`
>
> ```
> If libmir breaks, that means other code will break, too.


Lol, classic that it presented in rc-ptr! ;)
Again, a specifically stated goal of this work! mir will rewrite to take
advantage of the new semantics in a heartbeat!

So far the 2 cases on show are two of the poster-child cases demonstrating exactly why we're doing this!

That aside; why is `(mir_rcptr!(C))` trying to match the immutable one when
the exact match is present in the selection set?
The immutable one would require a copy, and construction of an immutable
instance from the given mutable instance; certainly not an exact match!
This is an overload selection bug... this is a bug. Why do you present this
as a case to abandon the DIP?


I eventually decided it
> was not a good idea to modify existing semantics (rvalue constructors
> exist and
> are used already),


...and this will make them work properly!

as who knows how much breakage that would ensue. Adding
> distinct syntax for the new semantics seemed like a much more practical approach.
>

You're fixing it, not breaking it. I'm not clear what "semantic change" is actually going on here? It's basically transparent to the language; that's the whole premise of your design, and the reason why your DIP is genius... and I'm uncharacteristically enthusiastic about it! :P

It seems like you've either discovered a fundamental fault in your DIP, or you're just experiencing implementation challenges; which every other DMD contributor will happily sing in chorus about.


I.e. `this(S)` already is accepted by the compiler and is used.
>

Precisely why this work is even a thing, and also why you designed your DIP the way you did...

There's no change in semantics that I'm aware of; can you show how the
change in calling convention will break these existing calls?
Such examples are the actual point of this work, and they should only
benefit...?


October 12
On Thu, 10 Oct 2024, 17:56 Walter Bright via Digitalmars-d, < digitalmars-d@puremagic.com> wrote:

> On 10/8/2024 11:54 PM, Manu wrote:
> > Hmmmm. Well, that's not and never was a copy constructor...
>
> Well, it is. I don't recall when or why it was included in D.
>

It was because postblit was riddled with problems... (maybe there's a
lesson there?)

Anyway, I thought of an issue with separating the constructor into a bespoke name; what happens when you do this:

=this(S);
this(S);

Which is selected?
What about:

=this(S);
this(T)(T); which overlaps the concrete case?

This questions also exist in today's language with copy constructors, but the rules are clear; regular overload selection semantics.

Selecting the proper function to call is a task for overload resolution,
not something weird and special.
If it behaves different than the way you manage the situation based on
regular overload resolution regarding copy constructors, you're just asking
for a raft of special-case related bugs that follow, and suffer more
annoying edge cases which have to be manually identified and managed by the
user.

We need to see the ways that hit dead-ends on the paths you explored. I want to see the cases that lead to infinite recursions...

>


October 11
On Friday, October 11, 2024 9:19:54 AM MDT Manu via Digitalmars-d wrote:
> The move semantic described by the dip should apply to every rvalue everywhere; they're all potential move targets.
>
> Especially so for your weird not-a-move-constructor-but-actually-a-move-constructor that receives T by value; it's the exact definition of a move constructor, and it should be the case that concretely proves that it is also the exact proper syntax for the declaration!

Just because a constructor such as this(typeof(this)) looks like a move constructor doesn't guarantee that it has those semantics. We have no clue what the programmer chose to do in that case given that it is not currently used as a move constructor and there was zero expectation that it ever would be. While the obvious use case is to construct the new object with the same value as the original, we cannot guarantee that that is what the programmer actually did. They could have simply chosen to take parts of the original and not the entire thing, using that constructor as a way to pass specific parts of the object's state but not all of it, whereas with a move, they would want the object to be moved exactly as-is. Right now, the programmer can rely on that constructor only being called when they've explicitly called it, and they're free to do whatever they want with it even if plenty of other folks would think that what they did was a bad idea. Changing it so that that constructor suddenly got called implicitly in a bunch of cases would potentially break their code - as well as likely hurting performance, since they were presumably fine with the move semantics that they had previously and didn't want their constructor called in those cases.

Another issue here is the refness of the parameter. Move constructors really
should be taking their argument by ref, not by value, since it's not being
copied, and it hasn't been moved yet. However, for better or worse,
this(ref typeof(this)) is already a copy constructor. Having a separate
syntax for move constructors allows us to have it be =this(ref typeof(this))
or @move this(ref typeof(this)) or whatever, and then it's distinct from
copy constructors while still having ref like it really should.
Using this(typeof(this)) while implicitly treating the parameter as ref even
though it isn't just creates a needless special case.

- Jonathan M Davis



October 11

On Friday, 11 October 2024 at 16:14:36 UTC, Manu wrote:

>

... I thought of an issue with separating the constructor into a bespoke name; what happens when you do this:

=this(S);
this(S);

Which is selected?
What about:

=this(S);
this(T)(T); which overlaps the concrete case?

This questions also exist in today's language with copy constructors, but the rules are clear; regular overload selection semantics.

For a move constructor to come into play, the object must first be created by the regular constructor. The move constructor facilitates transferring an already existing object to another location. In this sense, a move constructor can only be invoked if there is already an existing object.

Therefore, the concern about conflicts may be unfounded because the compiler first constructs the object, and if it needs to be moved, it invokes the move constructor.

Steps:

  1. Regular constructor: The object is created.
  2. Move constructor: If the object needs to be transferred to another place, the move constructor is called.

In the current D language with normal overload semantics, the selection rules are straightforward:

  1. Move semantics is preferred when an rvalue (temporary object) is passed.

  2. Copy semantics is preferred when an lvalue (an existing object) is passed.

Thus, there is no actual risk of conflict between the regular and move constructors, as moving can only occur after the object has been created.

SDB@79

October 11
On Friday, 11 October 2024 at 07:35:44 UTC, Manu wrote:
> I press the reply button, type words, and press send.

You replied to your own message and gmail helpfully leaked its gmail id and put in In-Reply-To header too instead of the list id, then jmdavis replied to that gmail id and it all went downhill.
October 11
Also jmdavis obtains gmail id of gmail messages and replies to those - for Manu and Danny Coy.
October 11
On Friday, October 11, 2024 10:14:36 AM MDT Manu via Digitalmars-d wrote:
> On Thu, 10 Oct 2024, 17:56 Walter Bright via Digitalmars-d, <
>
> digitalmars-d@puremagic.com> wrote:
> > On 10/8/2024 11:54 PM, Manu wrote:
> > > Hmmmm. Well, that's not and never was a copy constructor...
> >
> > Well, it is. I don't recall when or why it was included in D.
>
> It was because postblit was riddled with problems... (maybe there's a
> lesson there?)
>
> Anyway, I thought of an issue with separating the constructor into a bespoke name; what happens when you do this:
>
> =this(S);
> this(S);
>
> Which is selected?

That's very clear. Move constructors simply cannot be explicitly called via the normal constructor syntax and do not take part in overload resolution. They have no need to. We already have the move function for moving objects (and Martin Kinke wants to make it an intrinsic, which makes sense). So, if you want to do an explicit move, you call move. And for implicit moves, the compiler knows how to call the move constructor if that's necessary (and it may be changed to just call the lowering for move as a compiler intrinsic in order to put all of the move logic in one place).

So, if you then make an explicit constructor call with an rvalue - e.g. S(getOtherS()) - then that will only work if you've declared a normal constructor such as this(S). And if such a constructor exists, and you make that explicit call with an rvalue, the move constructor would be triggered just like it would for any other function call that took an rvalue that had a move constructor, moving the argument into the constructor's parameter.

In addition, =this(S); should really be changed to =this(ref S); anyway, because the move has not occurred yet, and the parameter needs to be a reference to the object that's being passed in. Using ref explicitly avoids the need for a weird special case with the parameter not being typed as ref while still actually being treated as ref and not running the destructor upon exit. And by giving the move constructor explicit syntax, we can put ref on the parameter without conflicting with copy constructors.

> What about:
>
> =this(S);
> this(T)(T); which overlaps the concrete case?

It's a non-issue as well, because move constructors simply take no part in overload resolution and cannot be called like a normal constructor. So, there's never any ambiguity due to the fact that they exist.

- Jonathan M Davis