October 11
On Friday, October 11, 2024 4:17:07 AM MDT Salih Dincer via Digitalmars-d wrote:
> Suppose everything goes well and we continue on our way with the best option. Will we be able to know by looking at a flag that the object has been moved before?

No. A move is supposed to take the object and move it to a new location (which by default would just be a memcpy). Keeping track of whether that has ever happened before would require storing additional information somewhere. You could put a member in your type which got updated when the move constructor was called so that you could count the number of moves - or simply indicate that a move has occurred at some point - but the compiler couldn't keep track of that information without explicitly storing it somewhere separate from the object, and there really isn't any reason why you would normally care about that information.

- Jonathan M Davis



October 11
On Friday, October 11, 2024 9:01:49 AM MDT Manu via Digitalmars-d wrote:
> > 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.

The overload resolution rules don't need to apply to move constructors, because you never need to call them explicitly. That's what the move function is for. And if move constructors have special syntax, then they're clearly distinct from normal constructors, and it's clearer that they're their own thing and not a normal constructor to be called or considered when overloading.

In addition, if we type a move constructor as this(typeof(this)), then we actually introduce more special cases thanks to the fact that the parameter needs to semantically be ref but wouldn't be typed as ref (and typing it as ref would make it conflict with copy constructors). Giving it separate syntax avoids that problem, allowing us to mark it with ref without conflicting with the copy constructor.

- Jonathan M Davis



October 11
On Friday, 11 October 2024 at 17:17:55 UTC, Kagamin wrote:
> 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.

Looks like it's an old good problem of mailman
https://bugs.launchpad.net/mailman/+bug/557955
https://bugs.launchpad.net/mailman/+bug/266263
October 11
On 10/10/2024 11:48 PM, Manu wrote:
> They initialise an S from another S... I'm not sure what you mean by "they do different things"?

One moves the data (destroying the original), the other copies it (both remain valid). If they did the same thing, there'd be no purpose to a move constructor.


> I've been wondering, why do you need to detect that a constructor is a copy or move constructor?

Because they do fundamentally different things.

> That concept seems like a big internal hack. Why do normal overload selection semantics fail to determine the proper selection?

As discussed before, the current compiler allow for "rvalue constructors", which do a copy construction. There's also "rvalue reference" semantics which also conflate move with copy. And don't forget implicit conversions!

> 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...

They already exist and are called rvalue constructors. They don't do a move.

> 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?

They are special. I left C++ before they added move constructors, with their own syntax (rvalue references). I suspect they ran into the same problems.

October 11
On 10/11/2024 12:06 AM, Manu wrote:
> If the overload resolution rules don't prefer an exact match over a conversion, then something is very wrong with the overload selection rules. What situations cause those waters to become murky?

The difference between an rvalue and lvalue is murky, for one. For example,

1. rvalues can be passed by ref under the hood, even though there's no `ref`

2. lvalues can match both a ref and a non-ref parameter

3. rvalues can implicitly convert to lvalues with -preview=rvaluerefparam

4. rvalue constructors exist and are used, and are NOT move constructors


> The fact you're talking about a separate constructor type to identify the move case leads me to suspect that the rvalue semantic only applies to the argument to that particular function and nothing else?

It's that rvalue constructors already exist and are in use.

> That's not really "move semantics", that's just a move constructor... and I think I've completely misunderstood your DIP if this is correct?

You understood the DIP. I did not - it's unimplementable as it stands because of the existing semantics.


> I don't feel like I have these problems in C++, which is far less constrictive. 

C++ has rvalue references and universal references. They never cease to confuse me.

https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

> I have no way to know or assess whether any of the arrangements of rules you considered or tried actually arrange into a nice tight lattice or not... or if they were generally cohesive and well-formed. We're expected to accept that you tried every possible reasonable arrangement and conclusively determined that it's not workable. I have no idea how we can assess that for reality or not... :/

You're starting from a blank sheet of paper. I'm trying to fit it into the existing semantics without breaking existing semantics and complicated user code.

October 11
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.

But writing a function to convert an S to a T, and then set the S to its default initializer, should work.

October 12
On Sat, 12 Oct 2024 at 03:18, Jonathan M Davis via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> 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.


It doesn't matter whether it does or doesn't... what the function does is irrelevant. It's just a function call which can receive a particular category of argument (r-values) more efficiently than the existing code. We're really hung up on move constructors, but this DIP is really about "move semantics", which of course have something to do with move constructors, but move semantics are so much more than a constructor.

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.


That's fine, it's welcome to discard the value, nobody's judging. It's still a constructor, so it must initialise an instance of some kind, and it only accepts an r-values, so it's something like a move constructor, whatever it does.

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.


I'm getting the impression that you don't really know much about move semantics. I get the feeling you've made a lot of assumptions which are all mostly wrong :/

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 -


Wait... what? Why wouldn't that constructor be callable implicitly?

I can do this:
struct S {
  this(int) {}
}
S s = 10;

That calls an arbitrary constructor implicitly... why is your case something different?

Can you show any example to your point? I can't imagine it. I think it's time we understand how this hypothetical thing might get called...?


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.
>

Okay, so I think I see your hypothetical case now; some implicitly
generated move might just blit and not call their function? (specifically
because the function is not blessed, and so some internal compiler
generated thing takes precedence?
This is actually THE EXACT REASON I've given for criticising the idea of
blessing special functions... edge cases, edge cases everywhere! People
lean into weird edge cases, often when they don't even intend to or
understand that they have. The language is super non-uniform and full of
nooks and crannies where "weird shit" hang out.

Anyway, I don't think this thing actually exists; even though it's hypothetically possible, it just doesn't make sense, and it would be brittle as hell because the compiler would select between its own thing and the explicitly written thing based on whatever situational takes precedent. You'll need to show us some cases... if the cases are legit, and not actually just bugs in random user code, then we can study them.


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.
>

This repeats and highlights what I said in my post on the other thread...


October 12
On Sat, 12 Oct 2024 at 03:50, Jonathan M Davis via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> 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


I don't even know where to start on this whole post... every single
sentence is problematic. I'm essentially convinced at this point that:
1. You don't really know about move semantics; the ideas here are just bad,
and you don't seem to know this... I suspect you just have no experience
with move semantics, and have no associated expectations or assumptions.
2. Maybe you haven't read the DIP? Either that, or you've totally missed
the point, and completely missed the merits of the design; because
everything you describe here is in opposition to an efficient and
semantically uniform implementation.
3. You're not actually talking about the DIP in discussion...

I don't think I have the energy to pull apart each sentence, and I doubt you're interested to hear it anyway. Maybe my post on the other thread might help get us on the same page.

On Sat, 12 Oct 2024 at 03:50, Jonathan M Davis via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> 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
>
>
>
>


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

> On 10/11/2024 12:06 AM, Manu wrote:
> > If the overload resolution rules don't prefer an exact match over a
> conversion,
> > then something is very wrong with the overload selection rules. What
> situations
> > cause those waters to become murky?
>
> The difference between an rvalue and lvalue is murky, for one. For example,
>
> 1. rvalues can be passed by ref under the hood, even though there's no `ref`


The semantic distinction in the front-end motivates the overload selection, not the calling convention.

lvalues and rvalues are absolutely distinct in language terms. Yes, they're both ref under the hood, that's irrelevant for overload selection. That's a codegen matter.


2. lvalues can match both a ref and a non-ref parameter
>

So can rvalues, and that's proper and perfectly normal. This is why overload resolution prefers exact-match.


3. rvalues can implicitly convert to lvalues with -preview=rvaluerefparam


Again, perfectly cool and normal, and why exact-match is the highest precedence selection criteria.


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.

How could their selection semantics change?


> The fact you're talking about a separate constructor type to identify the move
> > case leads me to suspect that the rvalue semantic only applies to the
> argument
> > to that particular function and nothing else?
>
> It's that rvalue constructors already exist and are in use.


Now they will receive their rvalues more efficiently.

Again, what change in the selection semantics exists here?

> That's not really "move semantics", that's just a move constructor... and I
> > think I've completely misunderstood your DIP if this is correct?
>
> You understood the DIP. I did not - it's unimplementable as it stands
> because of
> the existing semantics.


I know this is sounding repetitive, but please be specific. Which existing semantics are in conflict?

We need case studies. This is too important to do wavy-hand stuff.


> I don't feel like I have these problems in C++, which is far less constrictive.
>
> C++ has rvalue references and universal references. They never cease to confuse me.
>
> https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers


Yes, I'm extremely impressed with your design! Let's nail it. Don't butcher your best work!

This is the ultimate form of that zen-master perfect engineering, where the answer looks so simple that anyone could have thought of it... This actually moves a bar in the scope of the broader PL landscape. People will notice this.

> I have no way to know or assess whether any of the arrangements of rules you
> > considered or tried actually arrange into a nice tight lattice or not...
> or if
> > they were generally cohesive and well-formed. We're expected to accept
> that you
> > tried every possible reasonable arrangement and conclusively determined
> that
> > it's not workable. I have no idea how we can assess that for reality or
> not... :/
>
> You're starting from a blank sheet of paper. I'm trying to fit it into the existing semantics without breaking existing semantics and complicated user code.


Then enumerate the road blocks, bring each one to their own thread, and we can systematically work through them one by one.

If it's that rvalue constructors might exist and don't create a copy, then
show that's actually true, and we can study if that's actually a thing
which has a purpose... it's probably a bug in user code, or the function is
actually a move constructor already.
We need to understand that case.

Have you found any other such problems? The other stuff you mentioned were implementation challenges, and certainly not showstoppers by any means.

D is a complicated language, and lots of people write stuff that doesn't actually work or make sense. It's easy to make something compile that doesn't actually work. We need to study the breakages and see if we're doing real harm... if the code is already a bug, then don't stress about it.

I think it's important to hold the importance of this in perspective; this is the single biggest massive gaping hole in the foundation of D. A large number of issues and edgy shit stems from this. Look at core.lifetime for evidence. This work will eliminate that entire dumpster fire, and round out the language in a seriously innovative and class-leading way.

This is NOT something you make trivial compromises and hacks with; value ownership and lifetime is the core-est, most fundamental shit that a language does, and edge-ey hacks are completely inappropriate at this level of the language.

Your design is remarkably (surprisingly!) compatible and non-destructive. I reckon you're not going to achieve perfect-non-breakage, why would you even imagine that's possible? This is way too deep and fundamental to assume that's a realistic goal.

We need to convince that the value outweighs the harm, which won't be hard; everyone wants this, even if they don't understand that.

...and that said, we have identified just one single oddity so far, and that oddity may be a red herring (because it's actually a move constructor in waiting), or maybe it's something that really doesn't make sense... What kind of constructor could accept only a self-type as argument, and then initialise an instance but not respect the source material, AND assert that that's the only possible way to express this nonsensicle concept?

We need to assess this case beyond the hypothetical, because if that's all we stand to break, we're in agonisingly good shape.


October 12
On Sat, 12 Oct 2024, 11:00 Walter Bright via Digitalmars-d, < digitalmars-d@puremagic.com> 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.
>

Absolutely does, and it's essential.
Super common and super useful.


But writing a function to convert an S to a T, and then set the S to its
> default
> initializer, should work.
>

Both perspectives are valid, the distinction might regard whether S knows that T exists or vice versa. In my experience, the arrangement where you construct something from some 'upstream' thing is the far more common case.

Or, it might just work out that an API is much more ergonomic one way or the other.

It doesn't matter though, your rvalue design handles this naturally... it shouldn't require a single special line of code in the compiler... that's one of the things that's so compelling about it!

>