www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Move Constructor Syntax

reply Walter Bright <newshound2 digitalmars.com> writes:
```
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()`.
Oct 05
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
I forgot to mention, the C++ syntax is:
```
S(S&&)
```
so it is unambiguously distinguished at parse time.
Oct 05
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Sat, Oct 05, 2024 at 09:04:28PM -0700, Walter Bright via Digitalmars-d wrote:
[...]
 ```
 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()`.
[...] What about .opMove? Since .opXxx have been unofficially reserved for operator overloading, and one could argue that a move ctor is a kind of operator overloading (overloading the assignment operator). I really dislike symbols that are part of the ctor name, like C++'s operator () overloads. It's just needlessly complex syntax. T -- Маленькие детки - маленькие бедки.
Oct 05
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/5/2024 9:14 PM, H. S. Teoh wrote:
 What about .opMove?
I was thinking of using that for the move version of opAssign.
Oct 05
parent reply Mike Shah <mshah.475 gmail.com> writes:
On Sunday, 6 October 2024 at 05:40:40 UTC, Walter Bright wrote:
 On 10/5/2024 9:14 PM, H. S. Teoh wrote:
 What about .opMove?
I was thinking of using that for the move version of opAssign.
Perhaps opAssignMove? Slightly less ambiguous and consistent in syntax for move assignment operator. I agree a unique constructor is the way to go for grepping code. My vote is option 1 or 3, with some thought process below. '=this(S)' probably the cleanest, but might confuse beginners with opEquals, opAssign, or capture syntax in C++. I imagine easiest to implement/maintain in compiler though. Still looks pretty good overall and we're use to the ~ mark on front of destructors. 'this(=S)' Wonder if this opens doors for other symbols to appear in parameters and have meaning? Are there other features we foresee where we'd want to have symbol prefixes? Probably best to avoid, as this would look better with move, but that opens more paths toward attribute soup imo. '->this(S)' conveys what is going on(which I like -- I switched the direction of the arrows). Easily searchable, and C and C++ programmers are used to typing it (but with a different but otherwise clear meaning). Looks a tiny bit ugly though in the code. With any if these strategies, it seems we will then have a __move or move type function otherwise ala std::move as I understand. That seems consistent to me overall (at least for the C++ side of my brain as I understand move semantics)
Oct 06
parent Walter Bright <newshound2 digitalmars.com> writes:
Good thoughts! One reason I like the `=this` syntax is its similarity to the 
existing `~this` syntax. Having `this(=S)` is inconsistent, and would lead to 
awkward questions like "why can't I use `=` on other parameters?"
Oct 06
prev sibling parent Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Sunday, 6 October 2024 at 04:14:16 UTC, H. S. Teoh wrote:
 What about .opMove?  Since .opXxx have been unofficially 
 reserved for operator overloading, and one could argue that a 
 move ctor is a kind of operator overloading (overloading the 
 assignment operator).  I really dislike symbols that are part 
 of the ctor name, like C++'s operator () overloads. It's just 
 needlessly complex syntax.
Yes, and if so deprecate the existing `this(...)` syntax is favor or - `opCreate()` or `opNew()` - `opCopy()` - `opPostDestruct()`, `opPostDelete()`, etc... for conformance. A couple of characters longer but linguistically clearer.
Oct 16
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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.)
Whatever the syntax is, I would definitely say that move constructors need a unique syntax. At work, we've had constructors that took the same type so that code such as auto a = A(otherA); and auto a = new A(otherA); would compile (though honestly, it would be nice if that syntax worked even without an explict constructor when the types match). This comes up in particular with variant types which can hold pretty much anything, in which case, templated constructors hit it quite easily. It also comes up in generic code where you're dealing with multiple types, and it's nice to not have to use static if braches just so that you can make a copy without a constructor call. And when adding copy constructors to some types, having constructors which took the same type caused grief, because for whatever reason, rvalue constructors are disallowed if you have a copy constructor. So, we had to create helper functions for constructing some types instead of being able to reliably do auto a = A(otherA); and we were forced to use static if branches in some cases to distinguish between types. I don't recall at the moment how auto ref interacts with any of this either, but it probably is disallowed with a copy constructor, and it probably will cause further issues if move constructors don't have unique syntax. In any case, we've had constructors that are not necessarily supposed to be move constructors which would match this(S), so changing this(S) to be a move constructor would cause problems for existing code.
 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()`.
Honestly, I'd just argue for slapping move on the constructor, but if I had too much like a mistyped pointer dereference in C++. - Jonathan M Davis
Oct 05
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/5/2024 9:33 PM, Jonathan M Davis wrote:
 Honestly, I'd just argue for slapping  move on the constructor,
Then people would want to slap move on other things.

 too much like a mistyped pointer dereference in C++.
Oct 05
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 06/10/2024 6:38 PM, Walter Bright wrote:
 On 10/5/2024 9:33 PM, Jonathan M Davis wrote:
 
     Honestly, I'd just argue for slapping  move on the constructor,
 
 Then people would want to slap  move on other things.
Does that not have desirable potential additions, once move constructors have been resolved? For now, it can error if seen elsewhere.
Oct 06
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/6/2024 2:58 AM, Richard (Rikki) Andrew Cattermole wrote:
 Does that not have desirable potential additions, once move constructors have 
 been resolved?
 For now, it can error if seen elsewhere.
I know you've proposed move for parameters already, but this seems inconsistent with that.
Oct 06
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 07/10/2024 5:56 AM, Walter Bright wrote:
 On 10/6/2024 2:58 AM, Richard (Rikki) Andrew Cattermole wrote:
 Does that not have desirable potential additions, once move 
 constructors have been resolved?
 For now, it can error if seen elsewhere.
I know you've proposed move for parameters already, but this seems inconsistent with that.
For parameters yes, I'd prefer that a copy constructor turned into a move constructor wasn't some special thing. But if you really want it to be a different overload set, well do it on the function to define it. ```d this(ref Thing other) move { } ```
Oct 06
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 07/10/2024 12:09 PM, Richard (Rikki) Andrew Cattermole wrote:
 On 07/10/2024 5:56 AM, Walter Bright wrote:
 On 10/6/2024 2:58 AM, Richard (Rikki) Andrew Cattermole wrote:
 Does that not have desirable potential additions, once move 
 constructors have been resolved?
 For now, it can error if seen elsewhere.
I know you've proposed move for parameters already, but this seems inconsistent with that.
For parameters yes, I'd prefer that a copy constructor turned into a move constructor wasn't some special thing. But if you really want it to be a different overload set, well do it on the function to define it. ```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.
Oct 06
next sibling parent reply Nick Treleaven <nick geany.org> writes:
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.
Oct 09
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/9/2024 3:52 AM, Nick Treleaven wrote:
 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.
We have special syntax already for constructors, postblits and destructors. None of it is intuitive - we're just used to it.
Oct 09
parent ryuukk_ <ryuukk.dev gmail.com> writes:
On Thursday, 10 October 2024 at 06:41:00 UTC, Walter Bright wrote:
 On 10/9/2024 3:52 AM, Nick Treleaven wrote:
 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.
We have special syntax already for constructors, postblits and destructors. None of it is intuitive -
 we're just used to it.
who is "we"? you don't value new users? you don't like fixing past mistakes to make things more consistent? the only one with special (with a symbol) syntax is the destructor ~this() = 'surf this wave' this() why cement a bad idea with another symbol, when everything else uses words one can read? and more importantly, search for documentation do you blindly trust a C++ user? are you a C++ user? do you expect D users to be retired C++ users? "i am new to D, what is the difference between `~this()` `"a" ~ "b"` same symbol that does different things not only symbols without prefix looks ugly, they are also confusing
Oct 09
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/6/2024 5:16 PM, Richard (Rikki) Andrew Cattermole wrote:
 There is a very good reason to prefer an attribute rather than new syntax, it 
 doesn't break tooling.
True, but an attribute for constructors only seems rather odd.
Oct 09
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 10/10/2024 7:39 PM, Walter Bright wrote:
 On 10/6/2024 5:16 PM, Richard (Rikki) Andrew Cattermole wrote:
 There is a very good reason to prefer an attribute rather than new 
 syntax, it doesn't break tooling.
True, but an attribute for constructors only seems rather odd.
Attributes are related to assumptions and guarantees, and the difference between a copy constructor and move constructor is the assumptions that the compiler is allowed to make. For whatever reason, this feature just keeps giving me the nagging sensation that something isn't right with the motivation.
Oct 10
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/10/2024 11:57 AM, Richard (Rikki) Andrew Cattermole wrote:
 For whatever reason, this feature just keeps giving me the nagging sensation 
 that something isn't right with the motivation.
Something I overlooked. With the original plan, ``` this(ref S) // copy constructor this(S) // move constructor ``` Generally speaking, functions that are overloaded should do the same thing, just with different arguments. But the copy and move overloads do something quite different. This is difference is what has been causing me frustrating problems with implementing it.
Oct 10
next sibling parent reply Manu <turkeyman gmail.com> writes:
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?

On Fri, 11 Oct 2024 at 16:11, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On 10/10/2024 11:57 AM, Richard (Rikki) Andrew Cattermole wrote:
 For whatever reason, this feature just keeps giving me the nagging
sensation
 that something isn't right with the motivation.
Something I overlooked. With the original plan, ``` this(ref S) // copy constructor this(S) // move constructor ``` Generally speaking, functions that are overloaded should do the same thing, just with different arguments. But the copy and move overloads do something quite different. This is difference is what has been causing me frustrating problems with implementing it.
Oct 10
parent Walter Bright <newshound2 digitalmars.com> writes:
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.
Oct 11
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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. 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? - Jonathan M Davis
Oct 11
prev sibling parent Manu <turkeyman gmail.com> writes:
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.
Oct 11
prev sibling next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 [...]
=this, &this or -this?
Oct 06
parent Derek Fawcus <dfawcus+dlang employees.org> writes:
On Sunday, 6 October 2024 at 12:51:02 UTC, Imperatorn wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 [...]
=this, &this or -this?
I've been partial to the idea of adding the sigil after the name. this=(S), this<-(S), this~() - the latter being an alternate form of destructor. but I'm not sure how well that would fit in to the existing D grammar. These 'magical' formations were also one of my quibbles with C++. The other toss up that none of these decorations obviously spring out as copy vs move vs construct/destruct, except possibly those involving assignment and arrow like graphics, and even those could be ambiguous. So I'd actually favour something which somehow explicitly included the words 'move' and 'copy'.
Oct 06
prev sibling next sibling parent reply ryuukk_ <ryuukk.dev gmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright 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()`.
1. Do you think people will read `=this` as `move this` = is move? 2. =S, that's `worry` emoji, look it up 3. for a feature that appeal to C++ programmers, if they ever decide to try D, they'll get confused with `->` ``` move this(S) ```
Oct 06
parent reply Walter Bright <newshound2 digitalmars.com> writes:
People took to `~this` like a duck to water, so I don't think it'll be a big 
deal for `=this` to be a move constructor. After all, it's better than the C++ 
`S(S&&)` by a mile!
Oct 06
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 6 October 2024 at 17:06:10 UTC, Walter Bright wrote:
 People took to `~this` like a duck to water, so I don't think 
 it'll be a big deal for `=this` to be a move constructor. After 
 all, it's better than the C++ `S(S&&)` by a mile!
If we adopt `=this` as the move-constructor syntax, what will we use if we decide to add a dedicated copy-constructor syntax in the future? The `=` symbol is used for both moves and copies, after all.
Oct 06
next sibling parent Dom Disc <dominikus scherkl.de> writes:
On Sunday, 6 October 2024 at 17:26:51 UTC, Paul Backus wrote:
 If we adopt `=this` as the move-constructor syntax, what will 
 we use if we decide to add a dedicated copy-constructor syntax 
 in the future? The `=` symbol is used for both moves and 
 copies, after all.
I would vote for >this as move and =this as copy constructor (the arrow often indicates some kind of move, and equal is often used to indicate similarity - maybe not yet not in C but in the world outside of programming languages).
Oct 06
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/6/2024 10:26 AM, Paul Backus wrote:
 If we adopt `=this` as the move-constructor syntax, what will we use if we 
 decide to add a dedicated copy-constructor syntax in the future?
We haven't needed one yet!
Oct 06
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 7 October 2024 at 06:41:13 UTC, Walter Bright wrote:
 On 10/6/2024 10:26 AM, Paul Backus wrote:
 If we adopt `=this` as the move-constructor syntax, what will 
 we use if we decide to add a dedicated copy-constructor syntax 
 in the future?
We haven't needed one yet!
https://forum.dlang.org/post/uvqyfedjuuerueinikwl forum.dlang.org
Oct 07
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/7/2024 6:24 AM, Paul Backus wrote:
 https://forum.dlang.org/post/uvqyfedjuuerueinikwl forum.dlang.org
That's an orthogonal issue.
Oct 09
parent reply zjh <fqbqrr 163.com> writes:
On Thursday, 10 October 2024 at 06:41:55 UTC, Walter Bright wrote:

 https://forum.dlang.org/post/uvqyfedjuuerueinikwl forum.dlang.org
```d // Function bodies omitted for brevity // mutable original this(ref typeof(this)); this(ref typeof(this)) const; this(ref typeof(this)) immutable; this(ref typeof(this)) inout; // const original this(ref const typeof(this)); this(ref const typeof(this)) const; this(ref const typeof(this)) immutable; this(ref const typeof(this)) inout; // immutable original this(ref immutable typeof(this)); this(ref immutable typeof(this)) const; this(ref immutable typeof(this)) immutable; this(ref immutable typeof(this)) inout; // inout original this(ref inout typeof(this)); this(ref inout typeof(this)) const; this(ref inout typeof(this)) immutable; this(ref inout typeof(this)) inout; // etc. ``` `C++` has a deduced this,like this: ```cpp struct A { template <typename Self> void bar(this Self&& self); }; ```
Oct 09
parent zjh <fqbqrr 163.com> writes:
On Thursday, 10 October 2024 at 06:56:46 UTC, zjh wrote:
 `C++` has a deduced this,like this:
 ```cpp
 struct A {
     template <typename Self>
     void bar(this Self&& self);
 };
 ```
The `reduced this` here should be similar to simplifying countless versions of `this`. The benefits of `deduction` are truly infinite.
Oct 10
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 As the above illustrates, a move constructor cannot be 
 distinguished from a regular constructor by syntax alone. It 
 needs semantic analysis.
It's worth pointing out that this is not just a problem for move constructors; it's also a problem for copy constructors. Specifically, it's what prevents copy constructors from being templates. As a result, if you're writing generic code and want to handle arbitrary combinations of type qualifiers on the original object and the copy, you cannot do the simple, obvious thing and use a template, like this: this(this This, Other)(ref Other other) if (is(immutable Other == immutable typeof(this)) { // copy logic } Instead, you must write individual copy constructor overloads for every combination of qualifiers: // Function bodies omitted for brevity // mutable original this(ref typeof(this)); this(ref typeof(this)) const; this(ref typeof(this)) immutable; this(ref typeof(this)) inout; // const original this(ref const typeof(this)); this(ref const typeof(this)) const; this(ref const typeof(this)) immutable; this(ref const typeof(this)) inout; // immutable original this(ref immutable typeof(this)); this(ref immutable typeof(this)) const; this(ref immutable typeof(this)) immutable; this(ref immutable typeof(this)) inout; // inout original this(ref inout typeof(this)); this(ref inout typeof(this)) const; this(ref inout typeof(this)) immutable; this(ref inout typeof(this)) inout; // etc. So, whatever solution we come up with for helping the compiler identify move constructors, I hope we can apply it to copy constructors too.
Oct 06
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
 So, whatever solution we come up with for helping the compiler identify move 
constructors, I hope we can apply it to copy constructors too. Sure, but I think that would be a separate proposal.
Oct 06
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 6, 2024 11:00:16 AM MDT Walter Bright via Digitalmars-d 
wrote:
  > So, whatever solution we come up with for helping the compiler identify
  > move
 constructors, I hope we can apply it to copy constructors too.

 Sure, but I think that would be a separate proposal.
Sure, but depending on the solution for move constructors, that could affect what we'd want to do with copy constructors. For instance, if we decide that we want to make move constructors be =this(T t) {...} maybe that would imply that we want to update copy constructors to be something like +this(ref T t) {...} But of course, that would mean changing copy constructors. So, if we know that we want to do something with copy constructors similar to what we're discussing here with move constructors with regards to reliably identifying them, then arguably, a solution which didn't require changing the existing copy constructors would be preferable. So, we might want to take that into account with what we decide to do with move constructors. Personally, I'm fine with updating copy constructors to use a new symbol (with an appropriate deprecation period of course), since the issues that Paul is talking about have proven to be a real annoyance with copy constructors. The way that copy constructors deal with attributes and type qualifiers right now is terrible. But if we want to do that, we should probably at least partially consider what we want to do with that at the same time that we work out move constructors so that we can have a clean and consistent solution that we're happy with - and even if we want to go with adding a symbol to the front of this like we do with destructors, if we're going to do that, we should probably think about copy constructors at the same time, since then we potentially are going to want to do the same to copy constructors, and then we're talking about adding two symbols to this, not just =this for move constructors. - Jonathan M Davis
Oct 06
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 6, 2024 10:43:34 AM MDT Paul Backus via Digitalmars-d 
wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 As the above illustrates, a move constructor cannot be
 distinguished from a regular constructor by syntax alone. It
 needs semantic analysis.
It's worth pointing out that this is not just a problem for move constructors; it's also a problem for copy constructors. Specifically, it's what prevents copy constructors from being templates. As a result, if you're writing generic code and want to handle arbitrary combinations of type qualifiers on the original object and the copy, you cannot do the simple, obvious thing and use a template, like this: this(this This, Other)(ref Other other) if (is(immutable Other == immutable typeof(this)) { // copy logic } Instead, you must write individual copy constructor overloads for every combination of qualifiers: // Function bodies omitted for brevity // mutable original this(ref typeof(this)); this(ref typeof(this)) const; this(ref typeof(this)) immutable; this(ref typeof(this)) inout; // const original this(ref const typeof(this)); this(ref const typeof(this)) const; this(ref const typeof(this)) immutable; this(ref const typeof(this)) inout; // immutable original this(ref immutable typeof(this)); this(ref immutable typeof(this)) const; this(ref immutable typeof(this)) immutable; this(ref immutable typeof(this)) inout; // inout original this(ref inout typeof(this)); this(ref inout typeof(this)) const; this(ref inout typeof(this)) immutable; this(ref inout typeof(this)) inout; // etc. So, whatever solution we come up with for helping the compiler identify move constructors, I hope we can apply it to copy constructors too.
My experience thus far has been that I've been forced to use inout (potentially then casting away const to then do anything useful) and that having more than one copy constructor falls flat on its face once you put your type with a copy constructor inside any other type, because the compiler fails to generate all of the necessary copy constructors and just complains that inout doesn't work. There are similar problems with the various function attributes which aren't type qualifiers. Razvan create a PR to try to improve that situation, but it hasn't gone anywhere: https://github.com/dlang/dmd/pull/16429 Really, it would be nice to be able to just templatize the copy constructor and have all of the corresponding copy constructors generated properly in any other types that have that type as member. The current state of things strongly implies that copy constructors were implemented and tested only in fairly simple situations. They're just too much of a mess in practice. - Jonathan M Davis
Oct 07
parent Paul Backus <snarwin gmail.com> writes:
On Monday, 7 October 2024 at 18:56:49 UTC, Jonathan M Davis wrote:
 The current state of things strongly implies that copy 
 constructors were implemented and tested only in fairly simple 
 situations. They're just too much of a mess in practice.
The copy constructor proposal, DIP 1018, was fast-tracked through the DIP process "in the interest of releasing the implementation as soon as possible." [1] The result speaks for itself, I think. [1] https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1018.md#formal-assessment
Oct 07
prev sibling parent reply vit <vit vit.vit> writes:
On Sunday, 6 October 2024 at 16:43:34 UTC, Paul Backus wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 As the above illustrates, a move constructor cannot be 
 distinguished from a regular constructor by syntax alone. It 
 needs semantic analysis.
It's worth pointing out that this is not just a problem for move constructors; it's also a problem for copy constructors. Specifically, it's what prevents copy constructors from being templates. As a result, if you're writing generic code and want to handle arbitrary combinations of type qualifiers on the original object and the copy, you cannot do the simple, obvious thing and use a template, like this: this(this This, Other)(ref Other other) if (is(immutable Other == immutable typeof(this)) { // copy logic } Instead, you must write individual copy constructor overloads for every combination of qualifiers: // Function bodies omitted for brevity // mutable original this(ref typeof(this)); this(ref typeof(this)) const; this(ref typeof(this)) immutable; this(ref typeof(this)) inout; // const original this(ref const typeof(this)); this(ref const typeof(this)) const; this(ref const typeof(this)) immutable; this(ref const typeof(this)) inout; // immutable original this(ref immutable typeof(this)); this(ref immutable typeof(this)) const; this(ref immutable typeof(this)) immutable; this(ref immutable typeof(this)) inout; // inout original this(ref inout typeof(this)); this(ref inout typeof(this)) const; this(ref inout typeof(this)) immutable; this(ref inout typeof(this)) inout; // etc. So, whatever solution we come up with for helping the compiler identify move constructors, I hope we can apply it to copy constructors too.
I have similar problem in implementation of generic container: ```d static if(isCopyConstructable!(typeof(this), typeof(this))) this(ref scope typeof(this) rhs) safe{this(rhs, Forward.init);} else disable this(ref scope typeof(this) rhs) safe; static if(isCopyConstructable!(typeof(this), const typeof(this))) this(ref scope typeof(this) rhs)const safe{this(rhs, Forward.init);} else disable this(ref scope typeof(this) rhs)const safe; static if(isCopyConstructable!(typeof(this), immutable typeof(this))) this(ref scope typeof(this) rhs)immutable safe{this(rhs, Forward.init);} else disable this(ref scope typeof(this) rhs)immutable safe; static if(isCopyConstructable!(typeof(this), shared typeof(this))) this(ref scope typeof(this) rhs)shared safe{this(rhs, Forward.init);} else disable this(ref scope typeof(this) rhs)shared safe; static if(isCopyConstructable!(typeof(this), const shared typeof(this))) this(ref scope typeof(this) rhs)const shared safe{this(rhs, Forward.init);} else disable this(ref scope typeof(this) rhs)const shared safe; ``` Having template copy/move ctor will be nice.
Oct 07
parent Walter Bright <newshound2 digitalmars.com> writes:
Just a thought: having a specific type that supports both shared and unshared 
versions probably isn't going to work. Shared objects are their own beast.
Oct 09
prev sibling next sibling parent reply Lance Bachmeier <no spam.net> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:

 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'm tempted to propose Rust-style syntax using the UTF delivery truck emoji: 🚚this(S). Since that seems problematic, what's wrong with a clear and simple approach like ``` this(move S) ``` Any of the three you've proposed would add considerable overhead to someone learning the language. I'm not a fan of even more syntax.
Oct 06
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/6/2024 11:20 AM, Lance Bachmeier wrote:
 what's wrong with a clear and simple 
 approach like
 
 ```
 this(move S)
 ```
It looks similar to Rikki's move syntax, which is already discussed.
 Any of the three you've proposed would add considerable overhead to someone 
 learning the language.
Unfortunately, a move constructor inevitably adds more to be learned. I don't know a way around that.
 I'm not a fan of even more syntax.
Neither am I. But I failed at finding a way to implement it without more syntax. I tried for several days.
Oct 06
prev sibling next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```d
 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.
 ...
Option 3 is really bad, but I like option 2. In this syntax, the = operator is at the beginning of the parameter. This could mean that the parameter is inherited. In other words, we are transferring the source of the S object to the this object. ```d struct MyStruct { int[] data; this(int size) { data = new int[size]; writeln("Constructor called, data: ", data); } this(=MyStruct other) { data = other.data; other.data = null; writeln("Move constructor called"); } } ``` SDB 79
Oct 06
prev sibling next sibling parent reply Anonymous <nomail nomail.no> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 this(ref S) // copy constructor
 this(this)  // postblit
 this(S)     // move constructor
 ~this()     // destructor
 ...
 ```
 ...
 A fix that would simplify the language and the compiler would 
 be to have a unique syntax for a move constructor,
 ...

 Something like one of:
 ```
 1. =this(S)
 2. this(=S)
 3. <-this(S)
 ```
 ?
this move(...) this copy(...) this(...) ~this(...)
Oct 06
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/6/2024 12:00 PM, Anonymous wrote:
 this move(...)
 this copy(...)
 this(...)
 ~this(...)
Not bad, but we already have copy constructors.
Oct 06
prev sibling parent reply ryuukk_ <ryuukk.dev gmail.com> writes:
On Sunday, 6 October 2024 at 19:00:09 UTC, Anonymous wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 this(ref S) // copy constructor
 this(this)  // postblit
 this(S)     // move constructor
 ~this()     // destructor
 ...
 ```
 ...
 A fix that would simplify the language and the compiler would 
 be to have a unique syntax for a move constructor,
 ...

 Something like one of:
 ```
 1. =this(S)
 2. this(=S)
 3. <-this(S)
 ```
 ?
this move(...) this copy(...) this(...) ~this(...)
this is the cleaniest and most understandable syntax, but please make them attributes copy / move are very common keywords, if they become reserved, i'll riot i have no desire to use RAII, literally 0, don't also remove words i can use in my code i already complained about `destroy` being in `object.d` and top top it all, it being a freaking template... it took me DAYS wondering why my destroy function was never called despite the program compiling
Oct 08
parent reply Anonymous <nomail nomail.no> writes:
On Tuesday, 8 October 2024 at 07:30:08 UTC, ryuukk_ wrote:
 On Sunday, 6 October 2024 at 19:00:09 UTC, Anonymous wrote:
 this move(...)
 this copy(...)
 this(...)
 ~this(...)
 ...
 copy / move are very common keywords, if they become reserved, 
 i'll riot
 ...
Copy and move should not be reserved, they specify 'this'. I'm assuming it's possible to add anything between this and (), without reserving anything new or be in conflict with anything existing. ''' struct S { ~this(...) this(...) // existing variants this.copy (...) // copy constructor this.move (...) // move constructor this.blit (...) // postblit constructor this.future (...) // unknown now copy (...) // normal method move (...) // normal method blit (...) // normal method future (...) // normal method } ... S s; s(...); // existing s.this.copy(...); s.this.move(...); s.this.blit(...); s.copy(...); s.move(...); s.blit(...); s.future(...); '''
Oct 08
parent reply zjh <fqbqrr 163.com> writes:
On Tuesday, 8 October 2024 at 17:57:55 UTC, Anonymous wrote:


Why not:

```d
struct S {
   ~this(...)
   this(...) // existing variants
   _copy (...) // copy constructor
   _move (...) // move constructor
   _blit (...) // postblit constructor
   _future (...) // unknown now
   copy (...)   // normal method
}
```
Oct 08
parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 9 October 2024 at 00:46:08 UTC, zjh wrote:
 On Tuesday, 8 October 2024 at 17:57:55 UTC, Anonymous wrote:


 Why not:

 ```d
 struct S {
   ~this(...)
   this(...) // existing variants
   _copy (...) // copy constructor
   _move (...) // move constructor
   _blit (...) // postblit constructor
   _future (...) // unknown now
   copy (...)   // normal method
 }
 ```
D currently has a rule that constructors are always named `this`. It would be nice if we could avoid breaking that rule, since it would make the language more consistent and easier to learn.
Oct 08
parent zjh <fqbqrr 163.com> writes:
On Wednesday, 9 October 2024 at 01:49:40 UTC, Paul Backus wrote:
 D currently has a rule that constructors are always named 
 `this`.
There is no need to limit to `this` at all.
Oct 08
prev sibling next sibling parent reply Brad Roberts <braddr puremagic.com> writes:
On 10/5/2024 9:04 PM, Walter Bright via Digitalmars-d wrote:
 ```
 struct S { ... }
 
 this(ref S) // copy constructor
 this(this)  // postblit
 //this(S)     // move constructor
 ~this()     // destructor
Not sure this actually would work well or not, but figured I'd add it to the list for consideration: this (move S) // move constructor
Oct 07
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 07/10/2024 8:15 PM, Brad Roberts wrote:
 On 10/5/2024 9:04 PM, Walter Bright via Digitalmars-d wrote:
 ```
 struct S { ... }

 this(ref S) // copy constructor
 this(this)  // postblit
 //this(S)     // move constructor
 ~this()     // destructor
Not sure this actually would work well or not, but figured I'd add it to the list for consideration: this (move S) // move constructor
We have had multiple people suggesting something to this equivalence including this specific syntax.
Oct 07
prev sibling next sibling parent angel <andrey.gelman gmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 this(ref S) // copy constructor
[...] The above seems to invite `this(mov S)` move constructor.
Oct 07
prev sibling next sibling parent reply max haughton <maxhaton gmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 [...]
Within the language today what part of a move operation actually requires a new form of constructor? Isn't the whole point that you're just transferring ownership of a constructed object?
Oct 07
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, October 7, 2024 10:21:50 AM MDT max haughton via Digitalmars-d 
wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 [...]
Within the language today what part of a move operation actually requires a new form of constructor? Isn't the whole point that you're just transferring ownership of a constructed object?
IIRC, the whole reason that we originally got a DIP for a move version of a postblit (which then became a DIP for move constructors once we determined that postblits didn't make sense due to issues with type qualifiers) is because Weka has a use case where they store pointers to structs on the stack in some sort of table somewhere (with other fibers accessing it on some basis IIRC). So, if the struct ever moves, their code breaks - and as things stand, the compiler is allowed to assume that moving a struct isn't a problem (whereas in C++, a move constructor is required for the compiler to move a user-defined object). The result is that Weka has had to be _very_ careful about stuff like RVO to ensure that these particular structs don't get moved, and they care a great deal about which situations result in the compiler decided to move a struct (which, fortunately for them, isn't many at this point), but if they have move constructors, then they can hook the move and tell the table that the address of the struct has changed, and they no longer risks bugs from stray moves. Personally, I've also run into one case where it made sense to pass a pointer to a struct on the stack to another thread, and being able to disable the ability to move that object would be quite nice even if it's not actually required (and it wouldn't fix the situation where the struct gets destroyed before the pointer is gone). Now, obviously, for the vast majority of programs, move constructors are completely unnecessary, and because we made it so that the compiler is allowed to move objects by default, we only need move constructors in cases where moving has to be hooked into or disallowed instead of in all cases where it would be needed at all, but we _do_ need move constructors for any situations where someone needs to know that a move has taken place or needs to entirely disallow the ability to move a particular struct. - Jonathan M Davis
Oct 07
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/7/2024 9:21 AM, max haughton wrote:
 Within the language today what part of a move operation actually requires a
new 
 form of constructor?
I desperately tried to make `this(S)` a move constructor. I could not find a way that did not break existing code and/or require some rather horrific changes to the complicated logic in the compiler. I explained this earlier - the compiler cannot determine if `this(S)` is a move constructor or not until after semantic analysis. But it needs to know it before semantic analysis. And so the carousel of troubles spins up. Having a distinct syntax simplifies things enormously.
Oct 09
prev sibling next sibling parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 It may take a bit of getting used to. I kinda prefer (1) as it 
 is sorta like `~this()`.
Maybe silly idea, but could we name it moveThis() and copyThis() ??
Oct 07
parent reply Guillaume Piolat <guillaume.piolat gmail.com> writes:
On Monday, 7 October 2024 at 17:29:20 UTC, Dejan Lekic wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 It may take a bit of getting used to. I kinda prefer (1) as it 
 is sorta like `~this()`.
Maybe silly idea, but could we name it moveThis() and copyThis() ??
How about reusing one of those shift operators keyword. this<<<(T other)
Oct 07
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Mon, Oct 07, 2024 at 05:42:53PM +0000, Guillaume Piolat via Digitalmars-d
wrote:
 On Monday, 7 October 2024 at 17:29:20 UTC, Dejan Lekic wrote:
 On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 It may take a bit of getting used to. I kinda prefer (1) as it is
 sorta like `~this()`.
Maybe silly idea, but could we name it moveThis() and copyThis() ??
How about reusing one of those shift operators keyword. this<<<(T other)
What about: this(~T t) The ~ being an allusion to ~this, the idea being that after the move the operand t will effectively have been destructed and invalid. T -- Why waste time reinventing the wheel, when you could be reinventing the engine? -- Damian Conway
Oct 07
next sibling parent Daniel N <no public.email> writes:
On Monday, 7 October 2024 at 18:04:58 UTC, H. S. Teoh wrote:
 What about:

 	this(~T t)

 The ~ being an allusion to ~this, the idea being that after the 
 move the operand t will effectively have been destructed and 
 invalid.


 T
I love it! intuitive (unlike most of the other suggestions).
Oct 07
prev sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Monday, 7 October 2024 at 18:04:58 UTC, H. S. Teoh wrote:
 What about:

 	this(~T t)

 The ~ being an allusion to ~this, the idea being that after the 
 move the operand t will effectively have been destructed and 
 invalid.
Before you decide that, consider how often we use the ~ symbol. But isn't this (option 2) more meaningful? **this**(**=MyStruct** name) { ... } **The ~ Symbol in D** * String/Array concatenation * Bitwise complement * Dynamic expanding array * Destructuring functions * Exclusive operator with opOpAssign SDB 79
Oct 07
parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Tue, Oct 08, 2024 at 12:31:44AM +0000, Salih Dincer via Digitalmars-d wrote:
 On Monday, 7 October 2024 at 18:04:58 UTC, H. S. Teoh wrote:
 
 What about:
 
 	this(~T t)
 
 The ~ being an allusion to ~this, the idea being that after the move the
 operand t will effectively have been destructed and invalid.
Before you decide that, consider how often we use the ~ symbol. But isn't this (option 2) more meaningful? **this**(**=MyStruct** name) { ... }
Using = in this case is an exceptionally ugly visual clash with default parameters. Imagine monstrosities like: this(=T t=T(1)); Ick. This looks better: this(~T t=T(1));
 **The ~ Symbol in D**
 
 * String/Array concatenation
 * Bitwise complement
 * Dynamic expanding array
 * Destructuring functions
 * Exclusive operator with opOpAssign
[...] Since the move ctor name is `this`, the use of `~` immediately reminds one of the dtor `~this`. One doesn't normally associate ctors with array concatenation or bitwise complement, so the use of `~` would seem to be the more intuitive meaning in the move ctor, which would be spelled `this(~T t)`. T -- Did you hear about the monkeys who shared an Amazon account? They were Prime mates!
Oct 07
prev sibling next sibling parent reply Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:
 ```
 struct S { ... }

 [...]
Just a question, are move semantics out of the question?
Oct 08
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/8/2024 10:37 PM, Imperatorn wrote:
 Just a question, are move semantics out of the question?
???
Oct 10
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On Sun, 6 Oct 2024 at 14:06, 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.
Yes, I would expect this. 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.
Can you show us some cases? 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? The story you present is incomplete, let me enhance: struct S { ... } struct Other { ... } this(ref S) // copy constructor this(this) // postblit this(S) // move constructor ~this() // destructor this(Other) // move from other (Other is an rvalue here) this(ref Other) // copy from other (Other is obviously a ref) Likewise, I don't understand why opMove would be necessary to distinguish from opAssign? If you introduce opMove, then you'll end up with opIndexMove, opOpMove, etc. Is that something you want? Assuming you want to avoid this; then I also imagine solving for that issue would equally solve for the main constructor case?
Oct 08
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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. 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. 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. I eventually decided it was not a good idea to modify existing semantics (rvalue constructors exist and are used already), as who knows how much breakage that would ensue. Adding distinct syntax for the new semantics seemed like a much more practical approach. I.e. `this(S)` already is accepted by the compiler and is used.
 The story you present is incomplete, let me enhance:
 
 struct S { ... }
 struct Other { ... }
 
 this(ref S) // copy constructor
 this(this)  // postblit
 this(S)     // move constructor
 ~this()     // destructor
It's not that simple. You can have: ``` alias T = S; this(T); // can't tell if move constructor by syntax ```
 this(Other) // move from other (Other is an rvalue here)
 this(ref Other) // copy from other (Other is obviously a ref)
Problems show up when `Other` is implicitly convertible to `S`.
 Likewise, I don't understand why opMove would be necessary to distinguish from 
 opAssign?
Is `a = b` a move or a copy?
 If you introduce opMove, then you'll end up with opIndexMove, opOpMove, etc.
 Is that something you want? Assuming you want to avoid this; then I also
imagine 
 solving for that issue would equally solve for the main constructor case?
This is why I don't like implicit conversions for a struct - you wind up with impossible tangles of meaning. Oh, and rvalue to ref conversions, too.
Oct 10
next sibling parent reply Zoadian <no no.no> writes:
On Thursday, 10 October 2024 at 07:09:34 UTC, Walter Bright wrote:
 this(ref S) // copy constructor
 this(this)  // postblit
 this(S)     // move constructor
 ~this()     // destructor
the fact that you have to add comments shows how bad the syntax actually is. imaging having them readable like this instead so one doesn't have to look up the language documentation to figure out what's going on: ``` constructor() copy_constructor() blit_constructor() move_constructor() destructor() ``` or ``` this() this_copy() this_blit() this_move() ~this() ``` also makes them grepable, which is much harder than saerching for ```this(ref S)```
Oct 10
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Thursday, 10 October 2024 at 09:33:21 UTC, Zoadian wrote:
 the fact that you have to add comments shows how bad the syntax 
 actually is.
 imaging having them readable like this instead so one doesn't 
 have to look up the language documentation to figure out what's 
 going on:
 ```
 constructor()
 copy_constructor()
 blit_constructor()
 move_constructor()
 destructor()
I am surprised by suggestions that would break the code just to make it look melodious... Yes, you are suggesting alternative syntaxes for the move constructor, which does not officially exist in D. These suggestions are quite interesting from a language design perspective and could offer different approaches to usage. Unfortunately, we are not inventing a new programming language. So we can only suggest suggestions that are compatible with the existing one. For technical reasons, `this(S)` will not work. IMO, there's any need to discussion. We have only one option. Just do it and let's not waste time: **Move Constructor Syntax: `=this(S)`** In the upcoming version of D, we are adding a **move constructor** to complement the existing **copy constructor** and **destructor**. Just as destructors are invoked with `~this()`, move constructors will now be expressed using the intuitive and concise syntax `=this(S)`. The `=this(S)` move constructor enables efficient transfer of resources from one object to another without the overhead of copying. When an object is moved, its internal resources (such as memory pointers) are transferred to the new object, and the original object is left in a safe, "moved-from" state, often with nullified or reset values. **Why =this(S)?** **Symmetry with Destructor:** Just like the destructor `~this()`, which frees up resources, the move constructor `=this(S)` transfers ownership of resources. This symmetry makes the syntax easy to understand and use. **Efficiency:** Moving objects is more efficient than copying because it avoids unnecessary memory allocations or deep copies. In the move constructor, resources are transferred directly, leaving the original object in a state where its resources can safely be discarded. Don't you want to have what is written above? Let's increase the performance as soon as possible because this has gone on for too long! I have another question at this stage: **Question About How Destructors and Move Constructors Work Together:** Since the original object is invalidated, does the destructor (`~this()`) know that it has nothing to free when it is called on the moved object? SDB 79
Oct 10
next sibling parent reply Danni Coy <danni.coy gmail.com> writes:
On Fri, Oct 11, 2024 at 5:36=E2=80=AFAM Salih Dincer via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Thursday, 10 October 2024 at 09:33:21 UTC, Zoadian wrote:
 the fact that you have to add comments shows how bad the syntax
 actually is.
 imaging having them readable like this instead so one doesn't
 have to look up the language documentation to figure out what's
 going on:
 ```
 constructor()
 copy_constructor()
 blit_constructor()
 move_constructor()
 destructor()
I am surprised by suggestions that would break the code just to make it look melodious... Yes, you are suggesting alternative syntaxes for the move constructor, which does not officially exist in D. These suggestions are quite interesting from a language design perspective and could offer different approaches to usage. Unfortunately, we are not inventing a new programming language. So we can only suggest suggestions that are compatible with the existing one. For technical reasons, `this(S)` will not work. IMO, there's any need to discussion. We have only one option. Just do it and let's not waste time: **Move Constructor Syntax: `=3Dthis(S)`** In the upcoming version of D, we are adding a **move constructor** to complement the existing **copy constructor** and **destructor**. Just as destructors are invoked with `~this()`, move constructors will now be expressed using the intuitive and concise syntax `=3Dthis(S)`. The `=3Dthis(S)` move constructor enables efficient transfer of resources from one object to another without the overhead of copying. When an object is moved, its internal resources (such as memory pointers) are transferred to the new object, and the original object is left in a safe, "moved-from" state, often with nullified or reset values. **Why =3Dthis(S)?** **Symmetry with Destructor:** Just like the destructor `~this()`, which frees up resources, the move constructor `=3Dthis(S)` transfers ownership of resources. This symmetry makes the syntax easy to understand and use. **Efficiency:** Moving objects is more efficient than copying because it avoids unnecessary memory allocations or deep copies. In the move constructor, resources are transferred directly, leaving the original object in a state where its resources can safely be discarded. Don't you want to have what is written above? Let's increase the performance as soon as possible because this has gone on for too long! I have another question at this stage: **Question About How Destructors and Move Constructors Work Together:** Since the original object is invalidated, does the destructor (`~this()`) know that it has nothing to free when it is called on the moved object? SDB 79
My problem with =3Dthis(S) is that from a linguistic point of view it's ambiguous. the =3D could just as reasonably apply to any type of constructo= r. As a new user of the language the only way you can figure out what it does is to read the documentation and memorise. That's something you want to avoid when creating new syntax if at all possible.
Oct 10
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/10/2024 4:35 PM, Danni Coy wrote:
 As a new user 
 of the language the only way you can figure out what it does is to read the 
 documentation and memorise.
That's true with the entirety of programming languages. Though you don't really need to memorize - using it a few times will do the trick.
 That's something you want to avoid when creating new 
 syntax if at all possible.
D uses ~ for string concatenation. Like a duck to water! In any case, selection of syntax has many axes of constraints. Picking the best one is always a collection of compromises. For example, every feature on your car is a collection of compromises. Even the "safety is our top priority" is inevitably false. Even ``` a = b ``` is not intuitive. Assignment is quite different from algebraic equality. There's nothing inherent there about a copy vs a move, either. It's all just what we learn and get used to.
Oct 10
parent reply Salih Dincer <salihdb hotmail.com> writes:
On Friday, 11 October 2024 at 06:25:02 UTC, Walter Bright wrote:
 In any case, selection of syntax has many axes of constraints. 
 Picking the best one is always a collection of compromises.

 For example, every feature on your car is a collection of 
 compromises. Even the "safety is our top priority" is 
 inevitably false.

 Even
 ```
 a = b
 ```
 is not intuitive. Assignment is quite different from algebraic 
 equality. There's nothing inherent there about a copy vs a 
 move, either.

 It's all just what we learn and get used to.
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? SDB 79
Oct 11
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 11
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, October 10, 2024 5:35:56 PM MDT Danni Coy via Digitalmars-d 
wrote:
 My problem with =this(S) is that from a linguistic point of view it's
 ambiguous. the = could just as reasonably apply to any type of constructor.
 As a new user of the language the only way you can figure out what it does
 is to read the documentation and memorise. That's something you want to
 avoid when creating new syntax if at all possible.
I won't claim that the proposed syntax is intuitive or that it's necessarily our best choice, but ~this isn't intuitive either (and plenty of programmers are perfectly fine with it), and the reality of the matter is that you always need to study up on a new language to understand its syntax. As much as we want the syntax to be intuitive so that it's easier to learn and remember, assuming that you understand exactly what a new language does with its syntax without reading the documentation or a book on the subject or whatnot is just begging to have issues. The bigger issue here is if the syntax is hard to remember, and right now, the only other similar syntax that you'd have to worry about would be ~this, so it's not much of a problem IMHO. That being said, if we fix copy constructors to be consistent with move constructors, and they get their own syntax, then we have a third piece of pretty arbitrary constructor-related syntax to remember, which is definitely not ideal. If we do go with something else for move constructors, I would expect it to be move, because that would be by far the most straightforward and consistent given what we currently have in the language, though Walter clearly is not in favor of it. Either way, naming move constructors something other than this is highly unlikely to happen, because that's what D uses for constructors. It's just a question of how we decorate move constructors to distinguish them from other constructors with a similar signature. And there's certainly no way that we're going to rename constructors in general at this point even if we all agreed that this was a bad name for them (and I really don't think that you're going to find general agreement on that point, much as you might find a few people who agree). Using this has worked quite well for us, and even if it did have some significant problems, it would break too much code for too little gain to rename constructors now. - Jonathan M Davis
Oct 10
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On Thu, 10 Oct 2024 at 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. 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. 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. I eventually decided it was not a good idea to modify existing semantics (rvalue constructors exist and are used already), as who knows how much breakage that would ensue. Adding distinct syntax for the new semantics seemed like a much more practical approach. I.e. `this(S)` already is accepted by the compiler and is used.
 The story you present is incomplete, let me enhance:

 struct S { ... }
 struct Other { ... }

 this(ref S) // copy constructor
 this(this)  // postblit
 this(S)     // move constructor
 ~this()     // destructor
It's not that simple. You can have: ``` alias T = S; this(T); // can't tell if move constructor by syntax ```
 this(Other) // move from other (Other is an rvalue here)
 this(ref Other) // copy from other (Other is obviously a ref)
Problems show up when `Other` is implicitly convertible to `S`.
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? 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? Move constructors can't just be for S s = otherS; that's obviously an important case, but it excludes an enormous range of the "move semantics" landscape. 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? 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?
 Likewise, I don't understand why opMove would be necessary to distinguish
 from
 opAssign?
Is `a = b` a move or a copy?
is `b` an rvalue or an lvalue? (or had some special sauce tag it with a 'last-use' flag or anything like that)
 If you introduce opMove, then you'll end up with opIndexMove, opOpMove,
 etc.
 Is that something you want? Assuming you want to avoid this; then I also
imagine
 solving for that issue would equally solve for the main constructor case?
This is why I don't like implicit conversions for a struct - you wind up with impossible tangles of meaning. Oh, and rvalue to ref conversions, too.
I don't feel like I have these problems in C++, which is far less constrictive. 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... :/
Oct 11
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Oct 11
parent reply Manu <turkeyman gmail.com> writes:
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.
Oct 12
next sibling parent reply Kagamin <spam here.lot> writes:
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?
Oct 12
parent Manu <turkeyman gmail.com> writes:
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!

Oct 12
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 12
parent Manu <turkeyman gmail.com> writes:
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...

Oct 12
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Oct 11
next sibling parent Manu <turkeyman gmail.com> writes:
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!

Oct 12
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 12
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Oct 13
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/14/24 04:47, Walter Bright wrote:
 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.
That's the move constructor vs move assign distinction. I was talking about construction as this thread is about move construction.
Oct 13
prev sibling parent reply Manu <turkeyman gmail.com> writes:
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...?
Oct 11
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Friday, 11 October 2024 at 16:12:39 UTC, Manu wrote:
 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.
```d struct S { this(ref typeof(this)); this(typeof(this)); } void fun(S); void main() { S a; fun(a); } ``` When the fun(a) is called, the compiler will have to check both constructors to see which one is a better match. It first tries the copy constructor and sees that it's an exact match, then it proceeds to the next overload - the move constructor. Now it wants to see if the move constructor is callable in this situation. The move constructor receives its argument by value so the compiler will think that it needs to call the copy constructor (if it exists). Now, since the copy constructor is in the same overload set as the move constructor, both need to be checked to see their matching levels. => infinite recursion. That is how overload resolution works for any kind of function (constructor, destructor, normal function). The way to fix this is to either move the copy constructor and the move constructor into different overload sets or to special case them in the function resolution algorithm. RazvanN
Oct 14
parent reply Manu <turkeyman gmail.com> writes:
On Tue, 15 Oct 2024 at 01:56, RazvanN via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Friday, 11 October 2024 at 16:12:39 UTC, Manu wrote:
 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.
```d struct S { this(ref typeof(this)); this(typeof(this)); } void fun(S); void main() { S a; fun(a); } ``` When the fun(a) is called, the compiler will have to check both constructors to see which one is a better match. It first tries the copy constructor and sees that it's an exact match, then it proceeds to the next overload - the move constructor. Now it wants to see if the move constructor is callable in this situation. The move constructor receives its argument by value so the compiler will think that it needs to call the copy constructor (if it exists).
Isn't this the exact moment that the recursion ends? If the copy ctor was an exact match (we must have been supplied an lvalue), and (therefore) while considering the move constructor it was determined that a copy is necessary, then it is not an exact match... copy ctor wins. Case closed. Now, since the copy constructor is in the same overload
 set as the move constructor, both need to be checked to see their
 matching levels. => infinite recursion.

 That is how overload resolution works for any kind of function
 (constructor, destructor, normal function). The way to fix this
 is to either move the copy constructor and the move
 constructor into different overload sets or to special case them
 in the
 function resolution algorithm.
Yeah sorry, I just don't see it. When the pair are both defined; one is an exact match, and the other is not. Given an rvalue, the move ctor is an exact match, given an lvalue, the copy ctor is an exact match. There is no case where this is ambiguous?
Oct 15
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Tuesday, 15 October 2024 at 09:33:59 UTC, Manu wrote:
 On Tue, 15 Oct 2024 at 01:56, RazvanN via Digitalmars-d < 
 digitalmars-d puremagic.com> wrote:

 On Friday, 11 October 2024 at 16:12:39 UTC, Manu wrote:
 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.
```d struct S { this(ref typeof(this)); this(typeof(this)); } void fun(S); void main() { S a; fun(a); } ``` When the fun(a) is called, the compiler will have to check both constructors to see which one is a better match. It first tries the copy constructor and sees that it's an exact match, then it proceeds to the next overload - the move constructor. Now it wants to see if the move constructor is callable in this situation. The move constructor receives its argument by value so the compiler will think that it needs to call the copy constructor (if it exists).
Isn't this the exact moment that the recursion ends? If the copy ctor was an exact match (we must have been supplied an lvalue), and (therefore) while considering the move constructor it was determined that a copy is necessary, then it is not an exact match... copy ctor wins. Case closed.
The way overload resolution works is that you try to call match each function in the overload set and always save (1) the best matching level up this far, (2) the number of matches and (3)a pointer to the best matching function (and potentially a second pointer to a second function, provided that you have 2 functions that have the same matching level). Once overload resolution is done you inspect these results and either pick a single function or error depending on what you get. This works without any special casings (there are minor special casings for unique constructors, but that's fairly non-invasive). If we were to accept `this(typeof(this))` as a move constructor, we would need to special case the overload resolution mechanism. I'm not saying it's not possible to implement, rather that we need to add this special case to a battle tested algorithm. In contrast, we could leave the overload resolution code untouched and simply give the move constructor a different identifier (what I mean is, you type `this(S)`, but internally the compiler gives the __movector name to the function). When the compiler inserts calls to the move constructor it will then need to do overload resolution using the name __movector. This seems to me like a more desirable alternative: you get the this(S) syntax (provided that Walter accepts the possibility of code breakage or code fix-up as you call it) and the overload resolution code remains intact. Additionally, you get smaller overload resolution penalties given that the overload sets get smaller.
 Now, since the copy constructor is in the same overload
 set as the move constructor, both need to be checked to see 
 their matching levels. => infinite recursion.

 That is how overload resolution works for any kind of function
 (constructor, destructor, normal function). The way to fix this
 is to either move the copy constructor and the move
 constructor into different overload sets or to special case 
 them
 in the
 function resolution algorithm.
Yeah sorry, I just don't see it. When the pair are both defined; one is an exact match, and the other is not. Given an rvalue, the move ctor is an exact match, given an lvalue, the copy ctor is an exact match. There is no case where this is ambiguous?
Before having copy ctors it was allowed to have this(ref S) and this(S) and it worked. So I don't see any opportunity for ambiguity. However, when you add some implicit constructor calls that's when things get a bit messy, however, that's not an unsolvable problem. It just depends on what sort of trade-offs you are willing to make from an implementation stand point. RazvanN
Oct 15
next sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Tuesday, 15 October 2024 at 12:56:35 UTC, RazvanN wrote:

 Isn't this the exact moment that the recursion ends? If the 
 copy ctor was an exact match (we must have been supplied an 
 lvalue), and (therefore) while considering the move 
 constructor it was determined that a copy is necessary, then 
 it is not an exact match... copy ctor wins. Case closed.
Note that today, from the compilers perspective both the move ctor and the copy ctor are exact matches. However, the compiler does a thing called partial ordering where it selects the function that is more specialized. This is where ref gets picked of rvalue because it is deemed more specialized. So, all you need to do is just tweak this and simply add a check for the situation where a copy constructor is preferred over the move constructor.
Oct 15
parent reply Manu <turkeyman gmail.com> writes:
On Tue, 15 Oct 2024, 23:06 RazvanN via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On Tuesday, 15 October 2024 at 12:56:35 UTC, RazvanN wrote:

 Isn't this the exact moment that the recursion ends? If the
 copy ctor was an exact match (we must have been supplied an
 lvalue), and (therefore) while considering the move
 constructor it was determined that a copy is necessary, then
 it is not an exact match... copy ctor wins. Case closed.
Note that today, from the compilers perspective both the move ctor and the copy ctor are exact matches. However, the compiler does a thing called partial ordering where it selects the function that is more specialized. This is where ref gets picked of rvalue because it is deemed more specialized. So, all you need to do is just tweak this and simply add a check for the situation where a copy constructor is preferred over the move constructor.
Okay, but again with the constructor; does that rule generalise to any regular function argument?

Oct 15
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/15/24 18:57, Manu wrote:
 On Tue, 15 Oct 2024, 23:06 RazvanN via Digitalmars-d, <digitalmars- 
 d puremagic.com <mailto:digitalmars-d puremagic.com>> wrote:
 
     On Tuesday, 15 October 2024 at 12:56:35 UTC, RazvanN wrote:
 
      >> Isn't this the exact moment that the recursion ends? If the
      >> copy ctor was an exact match (we must have been supplied an
      >> lvalue), and (therefore) while considering the move
      >> constructor it was determined that a copy is necessary, then
      >> it is not an exact match... copy ctor wins. Case closed.
      >>
 
     Note that today, from the compilers perspective both the move
     ctor and
     the copy ctor are exact matches. However, the compiler does a
     thing called
     partial ordering where it selects the function that is more
     specialized.
     This is where ref gets picked of rvalue because it is deemed more
     specialized.
 
     So, all you need to do is just tweak this and simply add a check
     for the situation
     where a copy constructor is preferred over the move constructor.
 
 
 Okay, but again with the constructor; does that rule generalise to any 
 regular function argument?
 
The recursion issue is more of an implementation detail. How does the compiler determine which of `foo(S)` and `foo(ref S)` is more specialized? Well, I'd expect it tries to call each with the other kind of argument. Then, `foo(ref S)` cannot be called with (rvalue) `S`. We still have to check the converse, as if that does not work, the overloads are ambiguous and the compiler has to error out: `foo(S)` can be called with `ref S`, but requires copy construction. So here, the compiler will check whether `ref S` is copyable. So by default, if you have `this(ref S)` and this(S)` as matches, to pick which one to choose, it will again try to call `this(S)` with a `ref S`. To check whether this call works, it has to be determined whether `S` is copyable, so it will try to find a copy constructor again, etc. Stack overflow. So the situation is: Overload resolution on { this(ref S), this(S) } recurses on itself. Overload resolution on { foo(ref S), foo(S) } recurses on { this(ref S), this(S) }, which then recurses on itself. I think it is hard to answer a question whether the "rule generalizes". Explicit base cases are needed that avoid stack overflow, at least for constructors. Other functions will then indeed work the same way, whether they are special-cased in the implementation as base cases or not. I think this is not an insurmountable problem, just annoying to fix.
Oct 15
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On Tue, 15 Oct 2024, 23:01 RazvanN via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On Tuesday, 15 October 2024 at 09:33:59 UTC, Manu wrote:
 On Tue, 15 Oct 2024 at 01:56, RazvanN via Digitalmars-d <
 digitalmars-d puremagic.com> wrote:

 On Friday, 11 October 2024 at 16:12:39 UTC, Manu wrote:
 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.
```d struct S { this(ref typeof(this)); this(typeof(this)); } void fun(S); void main() { S a; fun(a); } ``` When the fun(a) is called, the compiler will have to check both constructors to see which one is a better match. It first tries the copy constructor and sees that it's an exact match, then it proceeds to the next overload - the move constructor. Now it wants to see if the move constructor is callable in this situation. The move constructor receives its argument by value so the compiler will think that it needs to call the copy constructor (if it exists).
Isn't this the exact moment that the recursion ends? If the copy ctor was an exact match (we must have been supplied an lvalue), and (therefore) while considering the move constructor it was determined that a copy is necessary, then it is not an exact match... copy ctor wins. Case closed.
The way overload resolution works is that you try to call match each function in the overload set and always save (1) the best matching level up this far, (2) the number of matches and (3)a pointer to the best matching function (and potentially a second pointer to a second function, provided that you have 2 functions that have the same matching level). Once overload resolution is done you inspect these results and either pick a single function or error depending on what you get. This works without any special casings (there are minor special casings for unique constructors, but that's fairly non-invasive). If we were to accept `this(typeof(this))` as a move constructor, we would need to special case the overload resolution mechanism. I'm not saying it's not possible to implement, rather that we need to add this special case to a battle tested algorithm.
Can you explain this further? void f(ref T) it not capable with T() at all, so void f(T) is the only possible match for the rvalue case. Today's algorithm works for that case. The lvalue case could conceivably match to T or ref T (but ref T is obviously the superior selection, not requiring a superfluous copy)... but even now, the rvalue constructor already exists... what logic allows these to coexist in today's rules? What is the special case you're describing? Can you show me exactly what the algorithm does now that's incompatible with selecting the version with proper ref-ness, and what change would be necessary? In contrast, we could leave the overload resolution code
 untouched and simply
 give the move constructor a different identifier (what I mean is,
 you type `this(S)`, but internally the compiler gives the
 __movector name to the function). When the compiler inserts calls
 to the move constructor it will then need to do overload
 resolution using the name __movector. This seems to me like a
 more desirable alternative: you get the this(S) syntax (provided
 that Walter accepts the possibility of code breakage or code
 fix-up as you call it) and the overload
 resolution code remains intact. Additionally, you get smaller
 overload resolution
 penalties given that the overload sets get smaller.
Okay, but I've asked people to stop talking about move constructors... it seems to have poisoned everyone's brains with irrelevant focus. We're talking about function arguments in general, we don't need more bizarre edge cases, especially not so deep in the foundations. Run your thought experiment with: void f(T); void f(ref T) The constructor isn't special... don't special-case the constructor.
 Now, since the copy constructor is in the same overload
 set as the move constructor, both need to be checked to see
 their matching levels. =3D> infinite recursion.

 That is how overload resolution works for any kind of function
 (constructor, destructor, normal function). The way to fix this
 is to either move the copy constructor and the move
 constructor into different overload sets or to special case
 them
 in the
 function resolution algorithm.
Yeah sorry, I just don't see it. When the pair are both defined; one is an exact match, and the other is not. Given an rvalue, the move ctor is an exact match, given an lvalue, the copy ctor is an exact match. There is no case where this is ambiguous?
Before having copy ctors it was allowed to have this(ref S) and this(S) and it worked. So I don't see any opportunity for ambiguity. However, when you add some implicit constructor calls that's when things get a bit messy, however, that's not an unsolvable problem. It just depends on what sort of trade-offs you are willing to make from an implementation stand point.
"Trade offs"? Do you have anything in mind? I would be surprised if there are any 'trade-offs'; any change here will probably be fixing language holes or broken edge cases... what actually stands to break? What good-stuff=E2=84=A2 could we possibly be 'trading' aw= ay?
Oct 15
parent RazvanN <razvan.nitu1305 gmail.com> writes:
On Tuesday, 15 October 2024 at 16:54:50 UTC, Manu wrote:

 Okay, but I've asked people to stop talking about move 
 constructors... it seems to have poisoned everyone's brains 
 with irrelevant focus. We're talking about function arguments 
 in general, we don't need more bizarre edge cases, especially 
 not so deep in the foundations.

 Run your thought experiment with:
   void f(T);
   void f(ref T)

 The constructor isn't special... don't special-case the 
 constructor.
I think that Timon did an excellent job at explaining why we are focusing on constructors. Also, as he noted, those are implementation details - modifying the code to fix the current infinite recursion depends largely on how we treat overload resolution with respect to copy/move constructors. I tried to express the same idea, but Timon did it better. So, from my perspective, which is an implementation perspective, it's a matter if we are willing to special case the move/copy constructor base case in the overload resolution mechanism or not.
 "Trade offs"? Do you have anything in mind?
 I would be surprised if there are any 'trade-offs'; any change 
 here will
 probably be fixing language holes or broken edge cases... what 
 actually
 stands to break? What good-stuff™ could we possibly be 
 'trading' away?
I was talking about implementation trade-offs, not user facing trade-offs. I.E. we are modifying the overload resolution algorithm by adding a special (or base) case. It could be that this is desirable in this case, I do not know, but I suspect that this is the reason why Walter is on the "let's add new syntax for this" wagon (but I might be wrong). As for the `this(typeof(this))` being a move constructor - I completely agree with everything you've said this far. I am not counter-arguing anything that you said, I'm just presenting what the situation with respect to the compiler implementation is. RazvanN
Oct 16
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 08
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Oct 10
parent reply Manu <turkeyman gmail.com> writes:
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!

Oct 11
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 12
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
Oct 13
next sibling parent Manu <turkeyman gmail.com> writes:
On Mon, 14 Oct 2024 at 12:54, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> 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.
CASE STUDIES The fact it theoretically exists doesn't make its existence useful or even sensible. It *might *break code; but we really need to identify whether that code IS ALREADY BROKEN. I would give a 75% odds that any code using that pattern is actually broken already, and I give exactly zero cares if we break already broken code in service of correcting the single biggest gaping hole at the heart of D. I would also put a ~60-80% wager on any real-world instance of that pattern *already* being some kind of move constructor, which will only benefit from this change... I would put maybe 90%+ odds on any given instance of this declaration being written by a D amateur, who actually didn't understand what they were writing, and possibly thought it does something that it doesn't actually do; that is, I would wager with almost 100% probability, that if this exists in the wild, it's essentially an accident. (probably a failed attempt at a move constructor) There is NO CONCEIVABLE USE for this pattern as it is today, outside a weird syntactic hack with some very weird calling semantics... so; we need case studies to consider. We're just going to break this code, and you're just going to have to come good with that. I will personally submit a PR to every single repo affected by this change.
Oct 15
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On Mon, 14 Oct 2024 at 12:54, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> 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.
I've also said many times; an rvalue constructor is a move constructor for all intents and purposes. Show me a case study; what might you do with an rvalue constructor if not initialise an instance from an rvalue? Changing it would break existing code.

That's fine. It's already broken; OR, it's already a move constructor.
Oct 15
parent reply Arafel <er.krali gmail.com> writes:
On 15/10/24 11:26, Manu wrote:
 Show me a case study; what might you do with an rvalue constructor if 
 not initialise an instance from an rvalue?
I think one such case might be metaprogramming. Consider: ```d struct S { int i; this(C)(C c) if (is(C : int)) { this.i = c; } alias i this; } void main() { S s1, s2, s3; int i = 1; s1 = S(1); s2 = S(i); s3 = S(s1); // This was most certainly not intended as a move constructor. } ``` This example might seem artificial, and it is, but just imagine any templated constructor that uses DbI, and where the own type matches. The actual inner details (i.e. that `S` is instantiated) might be even unknown to the caller, for instance if it comes from the user side of an API through a templated function using IFTI. Also, you cannot use `ref` because you want it to accept both r- and l-values, and in any case there might be good reasons why this isn't desirable in metaprogramming.
Oct 15
parent reply Manu <turkeyman gmail.com> writes:
On Tue, 15 Oct 2024, 19:56 Arafel via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On 15/10/24 11:26, Manu wrote:
 Show me a case study; what might you do with an rvalue constructor if
 not initialise an instance from an rvalue?
I think one such case might be metaprogramming. Consider: ```d struct S { int i; this(C)(C c) if (is(C : int)) { this.i = c; } alias i this; } void main() { S s1, s2, s3; int i = 1; s1 = S(1); s2 = S(i); s3 = S(s1); // This was most certainly not intended as a move constructor. } ```
Your example is a valid move constructor though... so even if the S(S) case were reinterpreted under the new move semantics, it's fine. It's almost impossible to imagine such an example where this isn't true. This example might seem artificial, and it is, but just imagine any
 templated constructor that uses DbI, and where the own type matches.

 The actual inner details (i.e. that `S` is instantiated) might be even
 unknown to the caller, for instance if it comes from the user side of an
 API through a templated function using IFTI.

 Also, you cannot use `ref` because you want it to accept both r- and
 l-values, and in any case there might be good reasons why this isn't
 desirable in metaprogramming.
That's what auto ref is for, if this specifically is your problem... and as I described before; there's a quite likely chance that instances of this pattern in the wild were actually written by D amateurs, and the code is actually broken, or doesn't actually quite do what they think they were trying to do.

Oct 15
parent reply Arafel <er.krali gmail.com> writes:
On 15/10/24 18:32, Manu wrote:
     void main() {
              S s1, s2, s3;
              int i = 1;
              s1 = S(1);
              s2 = S(i);
              s3 = S(s1); // This was most certainly not intended as a
     move constructor.
     }
     ```
 
 
 Your example is a valid move constructor though... so even if the S(S) 
 case were reinterpreted under the new move semantics, it's fine. It's 
 almost impossible to imagine such an example where this isn't true.
 
AIUI a move constructor invalidates the source (i.e. it has ref semantics). I would certainly expect to be able to use `s1` after `s3` has been constructed, just like `i`.
 A _Move Constructor_ is a struct member constructor that moves, 
rather than copies, the argument corresponding to its first parameter into the object to be constructed. The argument is invalid after this move, and is not destructed. There is also the issue with throwing: what happens if the templated constructor throws?
 
     This example might seem artificial, and it is, but just imagine any
     templated constructor that uses DbI, and where the own type matches.
 
     The actual inner details (i.e. that `S` is instantiated) might be even
     unknown to the caller, for instance if it comes from the user side
     of an
     API through a templated function using IFTI.
 
     Also, you cannot use `ref` because you want it to accept both r- and
     l-values, and in any case there might be good reasons why this isn't
     desirable in metaprogramming.
 
 
 That's what auto ref is for, if this specifically is your problem... and 
 as I described before; there's a quite likely chance that instances of 
 this pattern in the wild were actually written by D amateurs, and the 
 code is actually broken, or doesn't actually quite do what they think 
 they were trying to do.
 
Again the issue I see is that a move constructor invalidates the source. In a generic templated expression that would be a special case: ```d struct S { this(T)(T t) { } // This now has special semantics when T == S } ``` One possible option would be to consider only non-templated constructors as move constructors, but even then what happens if you need both a non-move templated constructor and a templated one with value semantics? In any case, I personally feel a demeaning tone when referring to "D amateurs" that doesn't exactly help. Be it as it may, even if wrong, there can be generic code like this out there that works well enough for its purpose. Curtly ordering these "amateurs" to change it is not what I would call "user-friendly".
Oct 15
parent reply Max Samukha <maxsamukha gmail.com> writes:
On Tuesday, 15 October 2024 at 17:52:43 UTC, Arafel wrote:

 a move constructor invalidates the source.
Only if the source is not used after the construction/assignment. Otherwise, a copy is made. I'd recommend to skim through DIP1040 if only for fun.
Oct 15
parent reply Arafel <er.krali gmail.com> writes:
On 15/10/24 20:29, Max Samukha wrote:
 On Tuesday, 15 October 2024 at 17:52:43 UTC, Arafel wrote:
 
 a move constructor invalidates the source.
Only if the source is not used after the construction/assignment. Otherwise, a copy is made. I'd recommend to skim through DIP1040 if only for fun.
I had a look at it before posting, and according to it [1] (my bold):
 A Move Constructor for struct S is declared as:

   ```d
      this(S s) { ... }
   ```

 [...]

 A _Move Constructor_ is a struct member constructor that moves, 
rather than copies, the argument corresponding to its first parameter into the object to be constructed. **The argument is invalid after this move**, and is not destructed. Also, the examples I could see were about _implicit_ calls to the move constructor ([2]). I found no example of explicit calls to them, nor a description of what to expect in that case, but I might have missed it. In any case, it would be then helpful to clarify it more prominently: what happens if a constructor with the signature of a move constructor is called explicitly like this? ```d struct S { this (int i) { } this (S s) { } } void main() { S s1, s2; s1 = S(1); s2 = S(s1); // Is s1 valid here? } ``` Because this is currently valid D code, even if Walter thinks it shouldn't (as per the bug referenced in DIP1040 itself [3]), and s1 is perfectly valid at the end of the program. [1]: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md#move-constructor [2]: https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md#assignment-after-move [3]: https://issues.dlang.org/show_bug.cgi?id=20424
Oct 15
next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Tuesday, 15 October 2024 at 19:29:11 UTC, Arafel wrote:
 
 ```d
 struct S {
 	this (int i) { }
 	this (S s) { }
 }

 void main() {
 	S s1, s2;
 	s1 = S(1);
 	s2 = S(s1);
 	// Is s1 valid here?
 }
 ```

 Because this is currently valid D code, even if Walter thinks 
 it shouldn't (as per the bug referenced in DIP1040 itself [3]), 
 and s1 is perfectly valid at the end of the program.
The object pointed to by s1 lives, but is destroyed if another object (`S(41)`) is pointed to by s1. Also, when you don't do ` disable this();`, objects s1 and s2 will be created twice and count == 3. For example: ```d import std.stdio; struct S { int i; this (int i) { this.i = i; } S* s; this (ref S s) { this.s = &s; } disable this(); ~this() { ++count; } } int count; enum value = 42; void main() { auto s1 = S(value); auto s2 = S(s1); s1.tupleof.writeln(": ", &s1); //42null: 7FFD6592A190 assert(s2.s.i == value); s1 = S(41); assert(s2.s.i != value); // because value is 41 assert(count == 1); } ``` SDB 79
Oct 15
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/15/24 21:29, Arafel wrote:
 
 I had a look at it before posting, and according to it [1] (my bold):
 
  > A Move Constructor for struct S is declared as:
  >
  >   ```d
  >      this(S s) { ... }
  >   ```
  >
  > [...]
  >
  > A _Move Constructor_ is a struct member constructor that moves, 
 rather than copies, the argument corresponding to its first parameter 
 into the object to be constructed. **The argument is invalid after this 
 move**, and is not destructed.
You are right assuming move constructors work as shown in DIP1040. However, I think this design is not really workable, and I think Manu is arguing from a position of assuming that the argument needs to remain valid and will be destroyed by default. A benefit of `=this(ref S)` syntax or similar is that it supports the design where the argument is not destructed without additional language features to elide the destructor call, and without a special case where destructor elision may be unexpected given the syntax. However, I think it either has to leave the argument in a valid state or explicit moves have to blit `.init` over the argument after calling the move constructor.
Oct 15
next sibling parent reply Arafel <er.krali gmail.com> writes:
On 16/10/24 2:43, Timon Gehr wrote:
 You are right assuming move constructors work as shown in DIP1040. 
 However, I think this design is not really workable, and I think Manu is 
 arguing from a position of assuming that the argument needs to remain 
 valid and will be destroyed by default.
Please excuse the question if it sounds too obvious (no irony here, I'm absolutely no expert in language design), but if the argument remains valid, and can be destroyed or not, doesn't it become a copy constructor?
Oct 15
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/16/24 08:44, Arafel wrote:
 On 16/10/24 2:43, Timon Gehr wrote:
 You are right assuming move constructors work as shown in DIP1040. 
 However, I think this design is not really workable, and I think Manu 
 is arguing from a position of assuming that the argument needs to 
 remain valid and will be destroyed by default.
Please excuse the question
No worries!
 if it sounds too obvious (no irony here, I'm 
 absolutely no expert in language design), but if the argument remains 
 valid, and can be destroyed or not, doesn't it become a copy constructor?
A copy constructor is expected to result in two objects that represent the same value. A move constructor is expected to result in the original object in a new memory location. With Walter's current preferred design, a new dummy object is created in the old memory location. I.e., it has to be valid, but it does not have to contain any data. For example, if you move an array with 3 elements from location A to location B, then location B will contain the original array while location A will contain an empty array. With copy construction, both locations would contain two equal arrays with 3 elements, both equal to the original array. The question of whether a value is _valid_ is distinct from the question whether a value is useful. For example, in D `null` is a valid value for a class reference. This enables moving objects out of memory locations that may still be reachable later, such as a field of a class object. If the dummy object is required to be a value that does not need, yet allows (no-op) destruction, the compiler has a bit of leeway in how it calls destructors. It is also the least surprising behavior for the case where we move a class field, as a class finalizer may never even run. A key difference between `this(S s)` and `=this(ref S)` is that the latter elides the destructor by default, while for the former, the least surprising semantics would be that it calls the destructor of the source at the end of the scope by default. There would then need to be an additional feature to elide the destructor call with manual syntax.
Oct 16
parent reply Arafel <er.krali gmail.com> writes:
On 16.10.24 15:06, Timon Gehr wrote:

 With Walter's current preferred design, a new dummy object is created in the
old memory location. I.e., it has to be valid, but it does not have to contain
any data.
[...]
 A key difference between `this(S s)` and `=this(ref S)` is that the 
 latter elides the destructor by default, while for the former, the least 
 surprising semantics would be that it calls the destructor of the source 
 at the end of the scope by default. There would then need to be an 
 additional feature to elide the destructor call with manual syntax.
Let me go back to my original example, slightly modified to make my point more clear: ```d struct S { int i; this(C)(C c) if (is(C : int)) { this.i = c; } alias i this; } void main() { S s1, s2, s3; int i = 1; s1 = S(1); s2 = S(i); s3 = S(s2); // This was most certainly not intended as a move constructor. assert(s2.i == 1); assert(s3.i == 1); } ``` Would this code still be guaranteed to pass the asserts if the signature for move constructors becomes `this (S s)` (Walter's proposal)? I mean based on assurances by the language itself, not on what the compiler might decide to do (or not) with s2.
Oct 16
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/16/24 15:39, Arafel wrote:
 On 16.10.24 15:06, Timon Gehr wrote:
 
 With Walter's current preferred design, a new dummy object is created 
 in the old memory location. I.e., it has to be valid, but it does not 
 have to contain any data.
[...]
 A key difference between `this(S s)` and `=this(ref S)` is that the 
 latter elides the destructor by default, while for the former, the 
 least surprising semantics would be that it calls the destructor of 
 the source at the end of the scope by default. There would then need 
 to be an additional feature to elide the destructor call with manual 
 syntax.
Let me go back to my original example, slightly modified to make my point more clear: ```d struct S {     int i;     this(C)(C c) if (is(C : int)) {         this.i = c;     }     alias i this; } void main() {     S s1, s2, s3;     int i = 1;     s1 = S(1);     s2 = S(i);     s3 = S(s2); // This was most certainly not intended as a move constructor.     assert(s2.i == 1);     assert(s3.i == 1); } ``` Would this code still be guaranteed to pass the asserts if the signature for move constructors becomes `this (S s)` (Walter's proposal)? I mean based on assurances by the language itself, not on what the compiler might decide to do (or not) with s2.
Yes, I think the sane design is that if you pass an lvalue to an rvalue parameter, that results in a copy (during argument passing), and the copy is destroyed when it goes out of scope in the constructor. So if you explicitly call an rvalue constructor, that would behave the same as previously. Even with the unamended DIP1040, I think assertion failures would not fire in your example. The way DIP1040 might break such code is if `S` has a destructor.
Oct 16
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/16/24 20:21, Timon Gehr wrote:
 
 Yes, I think the sane design is that if you pass an lvalue to an rvalue 
 parameter, that results in a copy (during argument passing), and the 
 copy is destroyed when it goes out of scope in the constructor.
 
 So if you explicitly call an rvalue constructor, that would behave the 
 same as previously.
(Of course, this is all assuming last-use analysis does not insert moves, but it cannot do that in your example because the asserts read the value after the last opportunity for a move.)
Oct 16
prev sibling parent reply Arafel <er.krali gmail.com> writes:
On 16/10/24 20:21, Timon Gehr wrote:
 Yes, I think the sane design is that if you pass an lvalue to an rvalue 
 parameter, that results in a copy (during argument passing), and the 
 copy is destroyed when it goes out of scope in the constructor.
 
 So if you explicitly call an rvalue constructor, that would behave the 
 same as previously.
 
 Even with the unamended DIP1040, I think assertion failures would not 
 fire in your example. The way DIP1040 might break such code is if `S` 
 has a destructor.
I guess that would be workable iff templated constructors are excluded from being considered move constructors, otherwise there could be cases where a move constructor is found that was never meant as such. Would a way of explicitly requesting a move, like `__rvalue`, be available in case the compiler's detection mechanism of last use needs to be overridden? All in all, I still see no such big benefit in changing the meaning of existing valid code, when adding new syntax would be much clearer all around. Of course, it would be different if we were designing the language from zero now, or if it were part of D3... then I might even find it an elegant solution.
Oct 16
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10/16/24 22:06, Arafel wrote:
 On 16/10/24 20:21, Timon Gehr wrote:
 Yes, I think the sane design is that if you pass an lvalue to an 
 rvalue parameter, that results in a copy (during argument passing), 
 and the copy is destroyed when it goes out of scope in the constructor.

 So if you explicitly call an rvalue constructor, that would behave the 
 same as previously.

 Even with the unamended DIP1040, I think assertion failures would not 
 fire in your example. The way DIP1040 might break such code is if `S` 
 has a destructor.
I guess that would be workable iff templated constructors are excluded from being considered move constructors, otherwise there could be cases where a move constructor is found that was never meant as such. ...
I guess the question is what else could `S(s)` reasonably do for a `S s;`. Whether templated or not.
 Would a way of explicitly requesting a move, like `__rvalue`, be 
 available in case the compiler's detection mechanism of last use needs 
 to be overridden?
 ...
Yes, such mechanisms would still be needed. It is an important point as it is easy to think move semantics just means move constructors, but it is more than that. Perfect forwarding for rvalues is important too.
 All in all, I still see no such big benefit in changing the meaning of 
 existing valid code, when adding new syntax would be much clearer all 
 around.
 ...
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.
Oct 16
parent reply Manu <turkeyman gmail.com> writes:
On Thu, 17 Oct 2024, 07:36 Timon Gehr via Digitalmars-d, <
digitalmars-d puremagic.com> wrote:

 On 10/16/24 22:06, Arafel wrote:
 On 16/10/24 20:21, Timon Gehr wrote:
 Yes, I think the sane design is that if you pass an lvalue to an
 rvalue parameter, that results in a copy (during argument passing),
 and the copy is destroyed when it goes out of scope in the constructor.

 So if you explicitly call an rvalue constructor, that would behave the
 same as previously.

 Even with the unamended DIP1040, I think assertion failures would not
 fire in your example. The way DIP1040 might break such code is if `S`
 has a destructor.
I guess that would be workable iff templated constructors are excluded from being considered move constructors, otherwise there could be cases where a move constructor is found that was never meant as such. ...
I guess the question is what else could `S(s)` reasonably do for a `S s;`. Whether templated or not.
 Would a way of explicitly requesting a move, like `__rvalue`, be
 available in case the compiler's detection mechanism of last use needs
 to be overridden?
 ...
Yes, such mechanisms would still be needed. It is an important point as it is easy to think move semantics just means move constructors, but it is more than that. Perfect forwarding for rvalues is important too.
 All in all, I still see no such big benefit in changing the meaning of
 existing valid code, when adding new syntax would be much clearer all
 around.
 ...
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? 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. 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.

Oct 16
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
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.
Oct 16
parent Manu <turkeyman gmail.com> writes:
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?

Oct 17
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 17
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 17
parent Manu <turkeyman gmail.com> writes:
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'.

Oct 17
prev sibling parent reply Manu <turkeyman gmail.com> writes:
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.

Oct 17
parent Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 17
prev sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 16 October 2024 at 00:43:39 UTC, Timon Gehr wrote:
 A benefit of `=this(ref S)` syntax or similar is that it 
 supports the design where the argument is not destructed 
 without additional language features to elide the destructor 
 call, and without a special case where destructor elision may 
 be unexpected given the syntax.
I didn't quite understand, what did you mean? If possible, in short sentences...:) SDB 79
Oct 16
parent Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 16 October 2024 at 07:52:23 UTC, Salih Dincer wrote:
 On Wednesday, 16 October 2024 at 00:43:39 UTC, Timon Gehr wrote:
 A benefit of `=this(ref S)` syntax or similar is that it 
 supports the design where the argument is not destructed 
 without additional language features to elide the destructor 
 call, and without a special case where destructor elision may 
 be unexpected given the syntax.
Destructor Elision! Yes, there is such a thing! Is it officially available in D? SDB 79
Oct 16
prev sibling parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Tuesday, 15 October 2024 at 19:29:11 UTC, Arafel wrote:
 On 15/10/24 20:29, Max Samukha wrote:
 On Tuesday, 15 October 2024 at 17:52:43 UTC, Arafel wrote:
...
 ```d
 struct S {
 	this (int i) { }
 	this (S s) { }
 }

 void main() {
 	S s1, s2;
 	s1 = S(1);
 	s2 = S(s1);
 	// Is s1 valid here?
 }
 ```
unlike Walter/Timon/Razvan/Manu/<add others> I'm also no language design guru but, in the above case: - s1 is an lvalue, so shouldn't a copy ctor be implicitly generated by the compiler (like in C++)..? - ... and be preferred in this case? Something like: ```d S s1, s2, s3; s1 = S(1); // this (int i) s2 = S(s1); // this (ref S s) -> implicit s3 = S(S(2)); // this (S s) // ...and s1 is valid here ```
Oct 16
parent reply Arafel <er.krali gmail.com> writes:
On 16.10.24 12:39, ShadoLight wrote:
 unlike Walter/Timon/Razvan/Manu/<add others> I'm also no language design 
 guru but, in the above case:
 
 - s1 is an lvalue, so shouldn't a copy ctor be implicitly generated by 
 the compiler (like in C++)..?
 - ... and be preferred in this case?
 
 Something like:
 ```d
      S s1, s2, s3;
      s1 = S(1);       // this (int i)
      s2 = S(s1);      // this (ref S s)  -> implicit
          s3 = S(S(2));    // this (S s)
          // ...and s1 is valid here
 ```
That would make sense, but this would in turn mean that the move constructor can never be invoked explicitly. This might well be a design goal, but, in any case, I think that the explicit usage of constructors with a move constructor signature should be specified and clarified as part of the (eventual) DIP. Also, it wouldn't change the fact that perhaps a struct might need at the same time a move constructor, a copy constructor, and a templated constructor that could overlap any or both of these. I'd be all for considering all templated constructors as "normal", even if their signature in a given instantiation would match that of a copy or move constructor.
Oct 16
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Wednesday, 16 October 2024 at 12:00:03 UTC, Arafel wrote:
 On 16.10.24 12:39, ShadoLight wrote:
 unlike Walter/Timon/Razvan/Manu/<add others> I'm also no 
 language design guru but, in the above case:
 
 - s1 is an lvalue, so shouldn't a copy ctor be implicitly 
 generated by the compiler (like in C++)..?
 - ... and be preferred in this case?
 
 Something like:
 ```d
      S s1, s2, s3;
      s1 = S(1);       // this (int i)
      s2 = S(s1);      // this (ref S s)  -> implicit
          s3 = S(S(2));    // this (S s)
          // ...and s1 is valid here
 ```
That would make sense, but this would in turn mean that the move constructor can never be invoked explicitly.
What do you mean? s3 = S(S(2)); ...is invoking the move constructor explicitly.
 This might well be a design goal, but, in any case, I think 
 that the explicit usage of constructors with a move constructor 
 signature should be specified and clarified as part of the 
 (eventual) DIP.
Agreed.
 Also, it wouldn't change the fact that perhaps a struct might 
 need at the same time a move constructor, a copy constructor, 
 and a templated constructor that could overlap any or both of 
 these.
Agreed. But I wonder if some form of precedence may help? AFAICS it would be safe(r) if a copy ctor is always preferred to a move ctor - also in the case if a templated constructor can resolve to either. For example, in Razvan's infinite recursion example, I wonder if it can work if, as Razvan explained, it tries the copy constructor 1st and sees that it's an exact match, then simply stop any further matching and do copy construction. I mean, are any other matches (besides a move constructor) even possible if the copy constructor was a _perfect match_ in the 1st place? Templated constructors may complicate this but, again, just prefer copy construction if any ambiguity.
 I'd be all for considering all templated constructors as 
 "normal", even if their signature in a given instantiation 
 would match that of a copy or move constructor.
Agreed.
Oct 16
parent reply Arafel <er.krali gmail.com> writes:
On 16.10.24 15:09, ShadoLight wrote:
 That would make sense, but this would in turn mean that the move 
 constructor can never be invoked explicitly.
What do you mean?    s3 = S(S(2)); ...is invoking the move constructor explicitly.
I meant the original syntax before the lowering: s3 = S(s2) IIRC, you say that the compiler would lower it to first a copy constructor to create a temporary, then a move constructor on the temporary. Thus, seen from the outside, it would keep the current behavior (I mean, s2 would still be valid). But what if I did want to move s2 into s3? How would I do it? Or is a move something that cannot be forced explicitly? I didn't see this addressed in the DIP either. The problem I see is that you can't have it both ways with the proposed syntax: either you can't call a move constructor manually on an lvalue because an intermediate copy gets inserted, or you are changing the semantics for the existing usages. Again, perhaps move constructors are not meant to be invoked directly, but I think the DIP should be explicit about what happens in that case, or state that it's not allowed.
Oct 16
parent reply ShadoLight <ettienne.gilbert gmail.com> writes:
On Wednesday, 16 October 2024 at 13:23:53 UTC, Arafel wrote:
 On 16.10.24 15:09, ShadoLight wrote:
 That would make sense, but this would in turn mean that the 
 move constructor can never be invoked explicitly.
What do you mean?    s3 = S(S(2)); ...is invoking the move constructor explicitly.
I meant the original syntax before the lowering: s3 = S(s2)
No, I think we are missing each other. Maybe the examples are a bit confusing because of the variable naming. Let's rename them: Something like: ```d S a, b, c; a = S(1); // case(1) -> this (int i) b = S(a); // case(2) -> this (ref S s) -> implicit copy ctor c = S(S(2)); // case(3) -> this (S s) // ...and a is valid here ```
 IIRC, you say that the compiler would lower it to first a copy 
 constructor to create a temporary, then a move constructor on 
 the temporary. Thus, seen from the outside, it would keep the 
 current behavior (I mean, s2 would still be valid).
I'm asking if, in the case where the compiler sees a lvalue being used for construction, but with no copy constructor present (like in your examples) - it is feasible if the compiler creates a copy constructor for you (like in C++)? So no temporary is created in case(2), since this (implicit) copy constructor will be invoked, and normal copy construction proceeds. This also guarantees `a` (in my example, or `s1` in your example) remains valid after the constructor. This is simple and matches what Manu proposes. case(3 ) `c = S(S(2));` on the other hand passes a rvalue instance of S(2) to the move constructor: - the `this (int i)` constructor is first called and a temporary S rvalue is created, - then the temporary rvalue is passsed to `this (S s)` move constructor and 'moved' as per the DIP.
 But what if I did want to move s2 into s3? How would I do it? 
 Or is a move something that cannot be forced explicitly? I 
 didn't see this addressed in the DIP either.
In this case we can simply treat moving an existing `s2` into `s3` as ambiguous (because you cannot, if you want to maintain the normal constructor syntax, differentiate a move (if that is what you want) from a copy of a `s2` lvalue into `s3`. If you just do... `b = S(a); // case(2) -> this (ref S s) -> implicit copy ctor` ... the copy constructor is always preferred. To force a move, you should need to do... `b = S(__rvalue(a));` ... to turn `a` into a rvalue, which then matches on the move constructor. I think this is reasonable and AFAICS this is in line with move semantics. I also don't think this will break code. If a copy of s2 into s3 is done instead of an expected move of s2 into s3 (and s2 is not accessed again afterwards - which is expected because why else would you prefer the move?), then s2 will either be collected by the GC or it's destructor will be called if it is on the stack and it goes out of scope.
 The problem I see is that you can't have it both ways with the 
 proposed syntax: either you can't call a move constructor 
 manually on an lvalue because an intermediate copy gets 
 inserted, or you are changing the semantics for the existing 
 usages.
Right. Which is why I'm proposing that `copy` takes precedence over `move` for lvalues, and `move` takes precedence over `copy` for rvalues. This can then be used to optimize finding the best match in the overload set. I think this is in line with Manu's expectations.
 Again, perhaps move constructors are not meant to be invoked 
 directly, but I think the DIP should be explicit about what 
 happens in that case, or state that it's not allowed.
I don't see why not. Just invoke it with an rvalue.
Oct 16
next sibling parent ShadoLight <ettienne.gilbert gmail.com> writes:
On Wednesday, 16 October 2024 at 15:02:46 UTC, ShadoLight wrote:
 On Wednesday, 16 October 2024 at 13:23:53 UTC, Arafel wrote:
 On 16.10.24 15:09, ShadoLight wrote:
 That would make sense, but this would in turn mean that the 
 move constructor can never be invoked explicitly.
...
 Something like:
 ```d
      S a, b, c;
      a = S(1);      // case(1) -> this (int i)
      b = S(a);      // case(2) -> this (ref S s)  -> implicit 
 copy ctor
      c = S(S(2));   // case(3) -> this (S s)
      // ...and a is valid here
 ```
I know that `c = S(S(2));` is a bit of an silly rvalue example (just do `c = S(2);` for the same result), but it is just to show the principle. For a somewhat better example: ```d struct S { this (int i) { } //ctor this (S s) { } //move ctor //implicitly generated copy ctor if needed } struct X { static int j; static S factoryS() { S s = S(j++); return s; } } void main() { S a, b, c; a = S(1); // ctor b = S(a); // (implicit) copy ctor c = S(X.factoryS()); // move ctor } ```
Oct 16
prev sibling parent Arafel <er.krali gmail.com> writes:
On 16/10/24 17:02, ShadoLight wrote:

 [...] 
I think I agree with you then, I was missing the `__rvalue` possibility (assuming templated constructors are excluded). However, as I mentioned in my other reply, I still don't see the big benefit of not adding new syntax compared to all the confusion it causes, including possibly breaking existing code. I mean, I understand that the language needs to be kept clean and lean, but I would say this is one situation where having specific syntax is merited.
Oct 16
prev sibling parent reply An <d home.com> writes:
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)`
Oct 18
parent Manu <turkeyman gmail.com> writes:
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...

Oct 18
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
Ohhh man, that was a grueling slog getting through the sea of people
casually adding some opinion on the flavour of a new keyword, while
completely skipping over the premise of the proposition...

Sorry Walter, I think it's a very bad idea, and unlike everyone else here
apparently, I want to see clear evidence that this path is the only
reasonable solution before I even consider weighing on the colour of this
bikeshed...

Please show some fail cases so we can actually consider the problem?

Why can't a constructor be selected by normal overload resolution rules?
And why should a copy or a move constructor be special or distinct from a
"regular constructor" as you call it? Can you show some cases where the
distinction is necessary?


On Wed, 9 Oct 2024 at 15:42, Manu <turkeyman gmail.com> wrote:

 On Sun, 6 Oct 2024 at 14:06, 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.
Yes, I would expect this. 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.
Can you show us some cases? 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? The story you present is incomplete, let me enhance: struct S { ... } struct Other { ... } this(ref S) // copy constructor this(this) // postblit this(S) // move constructor ~this() // destructor this(Other) // move from other (Other is an rvalue here) this(ref Other) // copy from other (Other is obviously a ref) Likewise, I don't understand why opMove would be necessary to distinguish from opAssign? If you introduce opMove, then you'll end up with opIndexMove, opOpMove, etc. Is that something you want? Assuming you want to avoid this; then I also imagine solving for that issue would equally solve for the main constructor case?
Oct 08
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/8/2024 11:08 PM, Manu wrote:
 Can you show some cases where the 
 distinction is necessary?
I tried to in the other post you made. Recall that C++ had to invent a whole new type to make it work - rvalue references: ``` S(S&&) ``` I wish to avoid that.
Oct 10
prev sibling next sibling parent reply =?UTF-8?Q?S=C3=B6nke_Ludwig?= <sludwig outerproduct.org> writes:
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).
Oct 08
parent Kagamin <spam here.lot> writes:
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(); ```
Oct 09
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
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?
Oct 08
parent reply Walter Bright <newshound2 digitalmars.com> writes:
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.
 so are we required to accept that that was ever correct code?
Yes. I'm not willing to endure the legions of people saying I broke their code, and the fix for them is not obvious. The way some people use D is terrifyingly (to me) over-complicated with templates and aliases and trampolines and forwards, etc. So I embarked on "how can I make this as simple and understandable as possible".
 What kind of problem is a result of copy constructors not having an identifier?
Being unable to tell if `this(S)` is a copy constructor or not without semantic analysis. BTW, with the prototype implementation of move constructors, move constructors get a separate identifier, `__moveCtor`, so it is usable from system code. The way the symbol table works, and all the overloads and templates, attempting to search all the `__ctor` functions looking for the move constructor is very inefficient. BTW, every reply you make starts a separate subthread. Can you please fix that?
Oct 10
next sibling parent reply Manu <turkeyman gmail.com> writes:
On Thu, 10 Oct 2024 at 17:56, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 BTW, every reply you make starts a separate subthread. Can you please fix
 that?
I use gmail, which is BY FAR the world's most popular email service, by a country mile. I have default settings. I've never even opened the settings window, I wouldn't know where to find it or what it looks like. I press the reply button, type words, and press send. If this doesn't work, then the bug is in the custom forum software. I suggest you address a bug report to the author. I can't seriously be the only person here that uses gmail? ...if that's true; that really says something about the ambient autism score in this room...
Oct 11
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/11/24 09:35, Manu wrote:
 
 If this doesn't work, then the bug is in the custom forum software.
NNTP is not particularly custom.
 I suggest you address a bug report to the author.
FWIW I am getting your replies in threads both in Thunderbird and on forum.dlang.org, so the issue might be related to Walter's viewer specifically.
Oct 11
prev sibling parent reply Kagamin <spam here.lot> writes:
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.
Oct 11
next sibling parent Kagamin <spam here.lot> writes:
Also jmdavis obtains gmail id of gmail messages and replies to 
those - for Manu and Danny Coy.
Oct 11
prev sibling parent Kagamin <spam here.lot> writes:
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
Oct 11
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, October 11, 2024 1:35:44 AM MDT Manu via Digitalmars-d wrote:
 On Thu, 10 Oct 2024 at 17:56, Walter Bright via Digitalmars-d <

 digitalmars-d puremagic.com> wrote:
 BTW, every reply you make starts a separate subthread. Can you please fix
 that?
I use gmail, which is BY FAR the world's most popular email service, by a country mile. I have default settings. I've never even opened the settings window, I wouldn't know where to find it or what it looks like. I press the reply button, type words, and press send. If this doesn't work, then the bug is in the custom forum software. I suggest you address a bug report to the author. I can't seriously be the only person here that uses gmail? ...if that's true; that really says something about the ambient autism score in this room...
Well, gmail is known to screw up mailing lists, and they do not care (in particular, they do not send you your own replies, which can screw up threading - especially when you want to reply to your own messages). So, it wouldn't surprise me at all if gmail is doing something wrong that's screwing things up. I guess that they think that too few people use mailing lists for them to care (and admittedly, it probably is a very small percentage of their users who are on mailing lists). Part of the reason that I stopped using gmail ages ago was because of how badly they handle mailing lists. All that being said, the threading for your replies seems to be working correctly both in my e-mail client and on the forums. So, I don't know why Walter is having issues with them. IIRC, he uses the newsgroup interface via Thunderbird, but since the newsgroup is the actual thing that holds all of the data, and the mailing list and forum are built on top of it, I would think that the newsgroup would be threading things correctly if the other stuff is. So, I have no clue what the problem is. - Jonathan M Davis
Oct 11
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
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...

Oct 11
parent Salih Dincer <salihdb hotmail.com> writes:
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
Oct 11
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 11
prev sibling parent Manu <turkeyman gmail.com> writes:
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
Oct 11
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 09
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
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
Oct 09
next sibling parent claptrap <clap trap.com> writes:
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...
Oct 09
prev sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
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.
Oct 09
parent RazvanN <razvan.nitu1305 gmail.com> writes:
On Wednesday, 9 October 2024 at 20:20:02 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 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.
I've already put it on Mike's agenda for the meeting.
Oct 09
prev sibling next sibling parent Danni Coy <danni.coy gmail.com> writes:
On Sun, Oct 6, 2024 at 3:06=E2=80=AFPM 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 =3D S;
 this(T);    // also move constructor

 alias Q =3D 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. =3Dthis(S)
 2. this(=3DS)
 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.
Oct 09
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
 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?
Oct 11
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
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.)
Oct 12
parent reply Manu <turkeyman gmail.com> writes:
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.

Oct 12
parent Timon Gehr <timon.gehr gmx.ch> writes:
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.
Oct 12
prev sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Friday, 11 October 2024 at 07:25:53 UTC, Manu 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!
The only problem is that in today's language you cannot have a copy constructor and a move constructor defined. It's an error! When I implemented the copy constructor I discovered this problem: if the copy constructor is in the same overload set as the rest of the constructors then the overload resolution would enter in an infinite loop, so my proposition was to keep the syntax, but simply place the copy constructor in a different overload set (__copyctor as opposed to __ctor). However, Walter was against it and preferred that we disallow the definition of both copy ctor and rvalue ctor for an object. However, I think this can be fixed if we keep the same syntax (`this(typeof(this))`) for a move constructor, but simply place it in a different overload set (__movector). That way, if we have __copyctor, __movector and __ctor the compiler can reason on which one should be called depending on the circumstances and thus infinite loops can be avoided.
 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?
Oct 14
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Monday, 14 October 2024 at 16:03:15 UTC, RazvanN wrote:

 overload set (__movector). That way, if we have __copyctor, 
 __movector and __ctor the compiler can reason on which one 
 should be called depending on the circumstances and thus 
 infinite loops can be avoided.
That doesn't fix the templated copy/move constructor issue, which the new syntax will probably fix.
Oct 14
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/14/24 18:14, RazvanN wrote:
 On Monday, 14 October 2024 at 16:03:15 UTC, RazvanN wrote:
 
 overload set (__movector). That way, if we have __copyctor, __movector 
 and __ctor the compiler can reason on which one should be called 
 depending on the circumstances and thus infinite loops can be avoided.
That doesn't fix the templated copy/move constructor issue, which the new syntax will probably fix.
I think it's too hacky of a fix in general, what you discussed with Manu regarding adding base cases seems more promising.
Oct 15
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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, it might work as a move constructor, but there's no guarantee that all constructors with that signature would. 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. Honestly, as a user, I see _zero_ benefit in trying to treat either copy constructors or move constructors like they're normal constructors. They are treated as special by the compiler in a variety of ways, 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
Oct 11
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
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!

Oct 11
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 11
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 11
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
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...
Oct 11
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Oct 20
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
I've actually been working on this behind the scenes, it hasn't been dropped at 
all. Will be coming up with a better proposal soon.
Oct 28
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 29/10/2024 10:41 AM, Walter Bright wrote:
 I've actually been working on this behind the scenes, it hasn't been 
 dropped at all. Will be coming up with a better proposal soon.
I'm looking forward to it!
Oct 28
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On Tue, 29 Oct 2024 at 07:46, Walter Bright via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 I've actually been working on this behind the scenes, it hasn't been
 dropped at
 all. Will be coming up with a better proposal soon.
I really wish you'd try the original proposal through to proof of concept (or proof of failure) before inventing something way off on a tangent that nobody asked for >_<
Oct 30
parent Walter Bright <newshound2 digitalmars.com> writes:
On 10/30/2024 10:56 AM, Manu wrote:
 I really wish you'd try the original proposal through to proof of concept (or 
 proof of failure) before inventing something way off on a tangent that nobody 
 asked for >_<
Everything I tried along those lines broke existing code.
Oct 31