digitalmars.D - Move Constructor Syntax
- Walter Bright (34/34) Oct 05 ```
- Walter Bright (5/5) Oct 05 I forgot to mention, the C++ syntax is:
- H. S. Teoh (11/20) Oct 05 [...]
- Walter Bright (2/3) Oct 05 I was thinking of using that for the move version of opAssign.
- Mike Shah (24/27) Oct 06 Perhaps opAssignMove? Slightly less ambiguous and consistent in
- Walter Bright (3/3) Oct 06 Good thoughts! One reason I like the `=this` syntax is its similarity to...
- Per =?UTF-8?B?Tm9yZGzDtnc=?= (8/14) Oct 16 Yes, and if so deprecate the existing `this(...)` syntax is favor
- Jonathan M Davis (34/69) Oct 05 Whatever the syntax is, I would definitely say that move constructors ne...
- Walter Bright (3/6) Oct 05 I just threw in #3 'cause I needed a third :-/
- Richard (Rikki) Andrew Cattermole (4/9) Oct 06 Does that not have desirable potential additions, once move constructors...
- Walter Bright (3/6) Oct 06 I know you've proposed @move for parameters already, but this seems inco...
- Richard (Rikki) Andrew Cattermole (8/15) Oct 06 For parameters yes, I'd prefer that a copy constructor turned into a
- Richard (Rikki) Andrew Cattermole (3/20) Oct 06 There is a very good reason to prefer an attribute rather than new
- Nick Treleaven (7/14) Oct 09 Underrated comment. We shouldn't be breaking parsers when we
- Walter Bright (3/6) Oct 09 We have special syntax already for constructors, postblits and destructo...
- ryuukk_ (17/25) Oct 09 who is "we"?
- Walter Bright (2/4) Oct 09 True, but an attribute for constructors only seems rather odd.
- Richard (Rikki) Andrew Cattermole (6/11) Oct 10 Attributes are related to assumptions and guarantees, and the difference...
- Walter Bright (10/12) Oct 10 Something I overlooked. With the original plan,
- Manu (13/29) Oct 10 They initialise an S from another S... I'm not sure what you mean by "th...
- Walter Bright (10/22) Oct 11 One moves the data (destroying the original), the other copies it (both ...
- Jonathan M Davis (42/53) Oct 11 Another example of how they're treated as special is that if you declare...
- Manu (14/75) Oct 11 You literally gave the example that I gave just one sentence prior. I'm
- Imperatorn (2/5) Oct 06 =this, &this or -this?
- Derek Fawcus (13/19) Oct 06 I've been partial to the idea of adding the sigil after the name.
- ryuukk_ (8/47) Oct 06 1. Do you think people will read `=this` as `move this` = is move?
- Walter Bright (3/3) Oct 06 People took to `~this` like a duck to water, so I don't think it'll be a...
- Paul Backus (5/8) Oct 06 If we adopt `=this` as the move-constructor syntax, what will we
- Dom Disc (5/9) Oct 06 I would vote for >this as move and =this as copy constructor (the
- Walter Bright (2/4) Oct 06 We haven't needed one yet!
- Paul Backus (2/7) Oct 07 https://forum.dlang.org/post/uvqyfedjuuerueinikwl@forum.dlang.org
- Walter Bright (2/3) Oct 09 That's an orthogonal issue.
- Paul Backus (41/44) Oct 06 It's worth pointing out that this is not just a problem for move
- Walter Bright (2/3) Oct 06 constructors, I hope we can apply it to copy constructors too.
- Jonathan M Davis (29/33) Oct 06 Sure, but depending on the solution for move constructors, that could af...
- Jonathan M Davis (19/63) Oct 07 My experience thus far has been that I've been forced to use inout
- Paul Backus (6/9) Oct 07 The copy constructor proposal, DIP 1018, was fast-tracked through
- vit (33/77) Oct 07 I have similar problem in implementation of generic container:
- Walter Bright (2/2) Oct 09 Just a thought: having a specific type that supports both shared and uns...
- Lance Bachmeier (10/19) Oct 06 I'm tempted to propose Rust-style syntax using the UTF delivery
- Walter Bright (6/15) Oct 06 Unfortunately, a move constructor inevitably adds more to be learned. I ...
- Salih Dincer (20/35) Oct 06 Option 3 is really bad, but I like option 2. In this syntax, the
- Anonymous (5/24) Oct 06 this move(...)
- Walter Bright (2/6) Oct 06 Not bad, but we already have copy constructors.
- ryuukk_ (11/37) Oct 08 this is the cleaniest and most understandable syntax, but please
- Anonymous (29/38) Oct 08 Copy and move should not be reserved, they specify 'this'.
- zjh (13/13) Oct 08 On Tuesday, 8 October 2024 at 17:57:55 UTC, Anonymous wrote:
- Paul Backus (4/17) Oct 08 D currently has a rule that constructors are always named `this`.
- zjh (2/4) Oct 08 There is no need to limit to `this` at all.
- Brad Roberts (4/11) Oct 07 Not sure this actually would work well or not, but figured I'd add it to...
- Richard (Rikki) Andrew Cattermole (3/16) Oct 07 We have had multiple people suggesting something to this equivalence
- angel (3/6) Oct 07 [...]
- max haughton (4/7) Oct 07 Within the language today what part of a move operation actually
- Jonathan M Davis (31/39) Oct 07 IIRC, the whole reason that we originally got a DIP for a move version o...
- Walter Bright (8/10) Oct 09 I desperately tried to make `this(S)` a move constructor. I could not fi...
- Dejan Lekic (3/5) Oct 07 Maybe silly idea, but could we name it moveThis() and copyThis()
- Guillaume Piolat (3/8) Oct 07 How about reusing one of those shift operators keyword.
- H. S. Teoh (8/18) Oct 07 What about:
- Daniel N (2/8) Oct 07 I love it! intuitive (unlike most of the other suggestions).
- Salih Dincer (11/16) Oct 07 Before you decide that, consider how often we use the ~ symbol.
- H. S. Teoh (16/36) Oct 07 Using = in this case is an exceptionally ugly visual clash with default
- Imperatorn (2/5) Oct 08 Just a question, are move semantics out of the question?
- Walter Bright (2/3) Oct 10 ???
- Manu (26/66) Oct 08 Yes, I would expect this.
- Walter Bright (31/48) Oct 10 I'd get infinite recursion with overload resolution, because the compile...
- Zoadian (23/27) Oct 10 the fact that you have to add comments shows how bad the syntax
- Salih Dincer (43/54) Oct 10 I am surprised by suggestions that would break the code just to
- Danni Coy (8/62) Oct 10 My problem with =3Dthis(S) is that from a linguistic point of view it's
- Walter Bright (15/20) Oct 10 That's true with the entirety of programming languages. Though you don't...
- Salih Dincer (5/18) Oct 11 Suppose everything goes well and we continue on our way with the
- Jonathan M Davis (12/15) Oct 11 No. A move is supposed to take the object and move it to a new location
- Jonathan M Davis (31/36) Oct 10 I won't claim that the proposed syntax is intuitive or that it's necessa...
- Manu (24/84) Oct 11 If the overload resolution rules don't prefer an exact match over a
- Walter Bright (13/27) Oct 11 The difference between an rvalue and lvalue is murky, for one. For examp...
- Manu (66/105) Oct 12 The semantic distinction in the front-end motivates the overload selecti...
- Kagamin (3/9) Oct 12 You mean rvalue constructors that duplicate their argument are
- Manu (4/16) Oct 12 No, that's not what I mean. It's just "a constructor", and you're the
- Timon Gehr (22/28) Oct 12 Just as long as the semantics of the actual declaration does not change....
- Manu (31/59) Oct 12 Yes, that's the case in question. We need to see some case studies; I ha...
- Walter Bright (4/6) Oct 11 I'm not sure moving a T to an S even makes sense.
- Manu (13/20) Oct 12 Absolutely does, and it's essential.
- Timon Gehr (4/11) Oct 12 It seems to me that moving a `T` to an `S` and moving a `T` into an
- Walter Bright (3/6) Oct 13 Moving into an existing object is not the same as moving into an object ...
- Timon Gehr (3/10) Oct 13 That's the move constructor vs move assign distinction. I was talking
- Manu (42/69) Oct 11 I don't understand; was the argument an rvalue or an lvalue?
- RazvanN (32/52) Oct 14 ```d
- Manu (11/65) Oct 15 Isn't this the exact moment that the recursion ends? If the copy ctor wa...
- RazvanN (45/124) Oct 15 The way overload resolution works is that you try to call match
- RazvanN (12/18) Oct 15 Note that today, from the compilers perspective both the move
- Manu (4/22) Oct 15 Okay, but again with the constructor; does that rule generalise to any
- Timon Gehr (24/53) Oct 15 The recursion issue is more of an implementation detail.
- Manu (27/154) Oct 15 Can you explain this further?
- RazvanN (26/43) Oct 16 I think that Timon did an excellent job at explaining why we are
- Jonathan M Davis (23/50) Oct 08 Aside from whatever Walter's reasoning is, there are existing constructo...
- Walter Bright (2/8) Oct 10 Yup. Kaboom.
- Manu (4/12) Oct 11 No that's wrong; this is EXACTLY the situation that move semantics exist...
- Timon Gehr (17/35) Oct 12 I agree that it would be desirable to have:
- Walter Bright (4/6) Oct 13 But currently this(S) is an rvalue constructor, not a move constructor. ...
- Manu (25/34) Oct 15 CASE STUDIES
- Manu (8/16) Oct 15 I've also said many times; an rvalue constructor is a move constructor f...
- Arafel (26/28) Oct 15 I think one such case might be metaprogramming. Consider:
- Manu (11/39) Oct 15 Your example is a valid move constructor though... so even if the S(S) c...
- Arafel (25/61) Oct 15 AIUI a move constructor invalidates the source (i.e. it has ref
- Max Samukha (4/5) Oct 15 Only if the source is not used after the construction/assignment.
- Arafel (31/45) Oct 15 rather than copies, the argument corresponding to its first parameter
- Salih Dincer (30/46) Oct 15 The object pointed to by s1 lives, but is destroyed if another
- Timon Gehr (12/27) Oct 15 You are right assuming move constructors work as shown in DIP1040.
- Arafel (4/8) Oct 15 Please excuse the question if it sounds too obvious (no irony here, I'm
- Timon Gehr (27/37) Oct 16 A copy constructor is expected to result in two objects that represent
- Arafel (27/33) Oct 16 Let me go back to my original example, slightly modified to make my
- Timon Gehr (9/54) Oct 16 Yes, I think the sane design is that if you pass an lvalue to an rvalue
- Timon Gehr (4/11) Oct 16 (Of course, this is all assuming last-use analysis does not insert
- Arafel (13/23) Oct 16 I guess that would be workable iff templated constructors are excluded
- Timon Gehr (12/36) Oct 16 I guess the question is what else could `S(s)` reasonably do for a `S
- Manu (18/54) Oct 16 Special-casing the constructor is just admitting that the overload
- Paul Backus (21/35) Oct 16 The reason overload selection is broken for constructors but not
- Manu (19/53) Oct 17 I can't imagine any other way.
- Timon Gehr (13/47) Oct 17 There is a tradeoff. I think a `this(S)` has to call the destructor of
- Timon Gehr (7/16) Oct 17 Maybe here part of your question was why special-casing move
- Manu (19/34) Oct 17 I understand it's an implementation challenge; Razvan talked about it a
- Manu (40/89) Oct 17 But upon very trivial investigation we quickly determined that all
- Timon Gehr (61/190) Oct 17 `ref` arguments do not need to be cleaned up by the caller, and the
- Salih Dincer (4/9) Oct 16 I didn't quite understand, what did you mean? If possible, in
- Salih Dincer (4/11) Oct 16 Destructor Elision! Yes, there is such a thing! Is it officially
-
ShadoLight
(15/29)
Oct 16
unlike Walter/Timon/Razvan/Manu/
I'm also no language - Arafel (12/27) Oct 16 That would make sense, but this would in turn mean that the move
- ShadoLight (18/47) Oct 16 What do you mean?
- Arafel (17/23) Oct 16 I meant the original syntax before the lowering:
- ShadoLight (53/77) Oct 16 No, I think we are missing each other. Maybe the examples are a
- ShadoLight (25/39) Oct 16 I know that `c = S(S(2));` is a bit of an silly rvalue example
- Arafel (9/10) Oct 16 I think I agree with you then, I was missing the `__rvalue` possibility
- An (4/11) Oct 18 What about considering these syntax in new Edition that D is
- Manu (9/22) Oct 18 We have no case studies; as far as we've determined so far, this is the
- Manu (13/89) Oct 08 Ohhh man, that was a grueling slog getting through the sea of people
- Walter Bright (7/9) Oct 10 I tried to in the other post you made.
- =?UTF-8?Q?S=C3=B6nke_Ludwig?= (16/16) Oct 08 For what it's worth, I've been in what I assume to be pretty much the
- Kagamin (5/8) Oct 09 Prohibition of moving would look nicely as
- Manu (7/62) Oct 08 Hmmmm. Well, that's not and never was a copy constructor... so are we
- Walter Bright (15/18) Oct 10 Yes. I'm not willing to endure the legions of people saying I broke thei...
- Manu (12/14) Oct 11 I use gmail, which is BY FAR the world's most popular email service, by ...
- Timon Gehr (5/8) Oct 11 FWIW I am getting your replies in threads both in Thunderbird and on
- Kagamin (5/6) Oct 11 You replied to your own message and gmail helpfully leaked its
- Jonathan M Davis (18/32) Oct 11 Well, gmail is known to screw up mailing lists, and they do not care (in
- Manu (23/26) Oct 11 It was because postblit was riddled with problems... (maybe there's a
- Salih Dincer (23/34) Oct 11 For a move constructor to come into play, the object must first
- Jonathan M Davis (26/42) Oct 11 That's very clear. Move constructors simply cannot be explicitly called ...
- Manu (17/115) Oct 11 I don't even know where to start on this whole post... every single
- Jonathan M Davis (48/86) Oct 09 It's perfectly legal D. There is not and never has been a requirement th...
- RazvanN (29/30) Oct 09 This PR: https://github.com/dlang/dmd/pull/16429 implements the
- claptrap (3/18) Oct 09 thisMove, thisCopy,
- Richard (Rikki) Andrew Cattermole (4/11) Oct 09 This needs to be talked about at tomorrows meeting (which I might not be...
- RazvanN (3/15) Oct 09 I've already put it on Mike's agenda for the meeting.
- Danni Coy (11/53) Oct 09 a
- Manu (30/33) Oct 11 I had a moment to think on this case you present.
- Timon Gehr (14/20) Oct 12 DIP1040 suggests to pass the argument implicitly by reference and to not...
- Manu (16/35) Oct 12 I feel like we resolved this; we agreed that the no destructor call in 1...
- Timon Gehr (15/70) Oct 12 Yes, I think that's the best resolution, but you asked "how can
- RazvanN (21/75) Oct 14 The only problem is that in today's language you cannot have a
- RazvanN (4/8) Oct 14 That doesn't fix the templated copy/move constructor issue, which
- Timon Gehr (3/12) Oct 15 I think it's too hacky of a fix in general, what you discussed with Manu...
- Jonathan M Davis (29/63) Oct 11 No, as far as the language is concerned, it's not a copy constructor, bu...
- Manu (40/112) Oct 11 I think it actually is a move constructor... can you explain how it
- Jonathan M Davis (28/35) Oct 11 Just because a constructor such as this(typeof(this)) looks like a move
- Jonathan M Davis (14/21) Oct 11 The overload resolution rules don't need to apply to move constructors,
- Manu (46/85) Oct 11 It doesn't matter whether it does or doesn't... what the function does i...
- Jonathan M Davis (28/63) Oct 20 I don't know what the current state things is with what you're planning ...
- Walter Bright (2/2) Oct 28 I've actually been working on this behind the scenes, it hasn't been dro...
- Richard (Rikki) Andrew Cattermole (2/4) Oct 28 I'm looking forward to it!
- Manu (5/8) Oct 30 I really wish you'd try the original proposal through to proof of concep...
- Walter Bright (2/5) Oct 31 Everything I tried along those lines broke existing code.
``` 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
I forgot to mention, the C++ syntax is: ``` S(S&&) ``` so it is unambiguously distinguished at parse time.
Oct 05
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
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
On Sunday, 6 October 2024 at 05:40:40 UTC, Walter Bright wrote:On 10/5/2024 9:14 PM, H. S. Teoh wrote: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)What about .opMove?I was thinking of using that for the move version of opAssign.
Oct 06
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
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
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
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
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
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
On 07/10/2024 5:56 AM, Walter Bright wrote:On 10/6/2024 2:58 AM, Richard (Rikki) Andrew Cattermole wrote: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 { } ```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
On 07/10/2024 12:09 PM, Richard (Rikki) Andrew Cattermole wrote:On 07/10/2024 5:56 AM, Walter Bright wrote:There is a very good reason to prefer an attribute rather than new syntax, it doesn't break tooling.On 10/6/2024 2:58 AM, Richard (Rikki) Andrew Cattermole wrote: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 { } ```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
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: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.```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 09
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
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
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
On 10/10/2024 7:39 PM, Walter Bright wrote:On 10/6/2024 5:16 PM, Richard (Rikki) Andrew Cattermole wrote: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.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 10
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
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 naggingsensationthat 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
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
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
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: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 SThey initialise an S from another S... I'm not sure what you mean by"theydo different things"? I've been wondering, why do you need to detect that a constructor is acopyor move constructor? That concept seems like a big internal hack. Why do normal overload selection semantics fail to determine the properselection?It's not clear why it's useful or important that copy and moveconstructorsare 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.{ 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
On Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:``` struct S { ... } [...]=this, &this or -this?
Oct 06
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: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'.``` struct S { ... } [...]=this, &this or -this?
Oct 06
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
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
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
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
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
On Monday, 7 October 2024 at 06:41:13 UTC, Walter Bright wrote:On 10/6/2024 10:26 AM, Paul Backus wrote:https://forum.dlang.org/post/uvqyfedjuuerueinikwl forum.dlang.orgIf 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 07
On 10/7/2024 6:24 AM, Paul Backus wrote:https://forum.dlang.org/post/uvqyfedjuuerueinikwl forum.dlang.orgThat's an orthogonal issue.
Oct 09
On Thursday, 10 October 2024 at 06:41:55 UTC, Walter Bright wrote:```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); }; ```https://forum.dlang.org/post/uvqyfedjuuerueinikwl forum.dlang.org
Oct 09
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
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
So, whatever solution we come up with for helping the compiler identify moveconstructors, I hope we can apply it to copy constructors too. Sure, but I think that would be a separate proposal.
Oct 06
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
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: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 DavisAs 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 07
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
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: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.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 07
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
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
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
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
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
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
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: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``` 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 08
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
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
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
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
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() // destructorNot 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
On 07/10/2024 8:15 PM, Brad Roberts wrote:On 10/5/2024 9:04 PM, Walter Bright via Digitalmars-d wrote:We have had multiple people suggesting something to this equivalence including this specific syntax.``` struct S { ... } this(ref S) // copy constructor this(this) // postblit //this(S) // move constructor ~this() // destructorNot 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
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
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
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: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``` 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
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
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
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:How about reusing one of those shift operators keyword. this<<<(T other)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
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: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 ConwayOn Sunday, 6 October 2024 at 04:04:28 UTC, Walter Bright wrote:How about reusing one of those shift operators keyword. this<<<(T other)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
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. TI love it! intuitive (unlike most of the other suggestions).
Oct 07
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
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: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));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[...] 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
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
On 10/8/2024 10:37 PM, Imperatorn wrote:Just a question, are move semantics out of the question????
Oct 10
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 aunique 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
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() // destructorIt'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
On Thursday, 10 October 2024 at 07:09:34 UTC, Walter Bright 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() ``` or ``` this() this_copy() this_blit() this_move() ~this() ``` also makes them grepable, which is much harder than saerching for ```this(ref S)```this(ref S) // copy constructor this(this) // postblit this(S) // move constructor ~this() // destructor
Oct 10
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
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: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.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
Oct 10
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
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
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
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
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: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?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() // destructorIt'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 fromis `b` an rvalue or an lvalue? (or had some special sauce tag it with a 'last-use' flag or anything like that)opAssign?Is `a = b` a move or a copy?If you introduce opMove, then you'll end up with opIndexMove, opOpMove, etc.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... :/Is that something you want? Assuming you want to avoid this; then I alsoimaginesolving 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 11
On 10/11/2024 12:06 AM, Manu wrote:If the overload resolution rules don't prefer an exact match over a conversion, then something is very wrong with the overload selection rules. What situations cause those waters to become murky?The difference between an rvalue and lvalue is murky, for one. For example, 1. rvalues can be passed by ref under the hood, even though there's no `ref` 2. lvalues can match both a ref and a non-ref parameter 3. rvalues can implicitly convert to lvalues with -preview=rvaluerefparam 4. rvalue constructors exist and are used, and are NOT move constructorsThe 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-meyersI 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
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: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 parameterIf the overload resolution rules don't prefer an exact match over aconversion,then something is very wrong with the overload selection rules. Whatsituationscause 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`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 moveNow they will receive their rvalues more efficiently. Again, what change in the selection semantics exists here?case leads me to suspect that the rvalue semantic only applies to theargumentto 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 II 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.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-meyersYes, 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 youThen 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.considered or tried actually arrange into a nice tight lattice or not...or ifthey were generally cohesive and well-formed. We're expected to acceptthat youtried every possible reasonable arrangement and conclusively determinedthatit's not workable. I have no idea how we can assess that for reality ornot... :/ 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 12
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
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: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!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
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
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: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 being4. rvalue constructors exist and are used, and are NOT moveconstructorsThey 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).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 maynot 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
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
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: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 itsBut, I think you actually missed my point here; I provided a ref andnon-refoverload for Other... how do I move-construct from some OTHER type?I'm not sure moving a T to an S even makes sense.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
On 10/12/24 02:57, Walter Bright wrote:On 10/11/2024 12:06 AM, Manu 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.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. ...
Oct 12
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
On 10/14/24 04:47, Walter Bright wrote:On 10/12/2024 3:12 PM, Timon Gehr wrote:That's the move constructor vs move assign distinction. I was talking about construction as this thread is about move construction.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
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: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 rvalueCan 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.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 itwas 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. Addingdistinct 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
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:```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. RazvanNOn 10/8/2024 10:42 PM, Manu wrote: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.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.
Oct 14
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: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 overloadOn Thu, 10 Oct 2024, 17:10 Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:```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).On 10/8/2024 10:42 PM, Manu wrote: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.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.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
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: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.On Friday, 11 October 2024 at 16:12:39 UTC, Manu 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.On Thu, 10 Oct 2024, 17:10 Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:```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).On 10/8/2024 10:42 PM, Manu wrote: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.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.Now, since the copy constructor is in the same overloadBefore 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. RazvanNset 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
On Tuesday, 15 October 2024 at 12:56:35 UTC, RazvanN wrote: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.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.
Oct 15
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:Okay, but again with the constructor; does that rule generalise to any regular function argument?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.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.
Oct 15
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
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: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 codeOn Tue, 15 Oct 2024 at 01:56, RazvanN via Digitalmars-d < digitalmars-d puremagic.com> wrote: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.On Friday, 11 October 2024 at 16:12:39 UTC, Manu 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.On Thu, 10 Oct 2024, 17:10 Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote:```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).On 10/8/2024 10:42 PM, Manu wrote: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.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.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"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?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.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?
Oct 15
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
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 aAside 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 Davisunique 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?
Oct 08
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
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: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!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 11
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
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
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: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.No that's wrong; this is EXACTLY the situation that move semantics existtoaddress. Move constructor like this should ACTUALLY BE a moveconstructor! 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 15
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: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.No that's wrong; this is EXACTLY the situation that move semantics existtoaddress. Move constructor like this should ACTUALLY BE a moveconstructor! But currently this(S) is an rvalue constructor, not a move constructor. I've said this many times.That's fine. It's already broken; OR, it's already a move constructor.
Oct 15
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
On Tue, 15 Oct 2024, 19:56 Arafel via Digitalmars-d, < digitalmars-d puremagic.com> wrote:On 15/10/24 11:26, Manu wrote: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 anyShow 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. } ```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
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
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
On 15/10/24 20:29, Max Samukha wrote:On Tuesday, 15 October 2024 at 17:52:43 UTC, Arafel wrote:I had a look at it before posting, and according to it [1] (my bold):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.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
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
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
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
On 10/16/24 08:44, Arafel wrote:On 16/10/24 2:43, Timon Gehr wrote:No worries!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 questionif 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
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
On 10/16/24 15:39, Arafel wrote:On 16.10.24 15:06, 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.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
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
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
On 10/16/24 22:06, Arafel wrote:On 16/10/24 20:21, Timon Gehr wrote:I guess the question is what else could `S(s)` reasonably do for a `S s;`. Whether templated or not.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? ...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
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: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.On 16/10/24 20:21, Timon Gehr wrote:I guess the question is what else could `S(s)` reasonably do for a `S s;`. Whether templated or not.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? ...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
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
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: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 includeSpecial-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.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 ofthese 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
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
On 10/17/24 15:11, Timon Gehr wrote: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.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.
Oct 17
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: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 itMaybe 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.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.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
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: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.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 fromrvalue",corresponding to copy and move respectively would be cleaner. But overall I don't have a strong preference, as dedicated syntaxalsohas 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 brokenThey'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...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 selectionI 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.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
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
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
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:Destructor Elision! Yes, there is such a thing! Is it officially available in D? SDB 79A 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.
Oct 16
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
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
On Wednesday, 16 October 2024 at 12:00:03 UTC, Arafel wrote:On 16.10.24 12:39, ShadoLight wrote:What do you mean? s3 = S(S(2)); ...is invoking the move constructor explicitly.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.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
On 16.10.24 15:09, ShadoLight wrote: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.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.
Oct 16
On Wednesday, 16 October 2024 at 13:23:53 UTC, Arafel wrote:On 16.10.24 15:09, ShadoLight wrote: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 ```I meant the original syntax before the lowering: s3 = S(s2)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.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
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
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
On Monday, 14 October 2024 at 02:49:42 UTC, Walter Bright wrote:On 10/11/2024 8:44 AM, Manu wrote: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)`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 18
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: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...On 10/11/2024 8:44 AM, Manu wrote: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)`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 18
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 aunique 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
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
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
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 constructorsProhibition of moving would look nicely as ``` disable move this(); ```
Oct 09
On Wed, 9 Oct 2024 at 16:09, Jonathan M Davis via Digitalmars-d < digitalmars-d puremagic.com> wrote:On Tuesday, October 8, 2024 11:42:49 PM MDT Manu via Digitalmars-d wrote: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 anOn 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 haveatheunique syntax for a move constructor, instead of the ambiguous one incopyproposal. That way, searching for a copy constructor will only yieldtheconstructors, and searching for a move constructor will only yield move constructors. There will be a sharp distinction between them, even inoutsource code. (I have seen code so dense with templates it is hard to figureAside 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.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?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
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
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
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
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
Also jmdavis obtains gmail id of gmail messages and replies to those - for Manu and Danny Coy.
Oct 11
On Friday, 11 October 2024 at 17:17:55 UTC, Kagamin wrote:On Friday, 11 October 2024 at 07:35:44 UTC, Manu wrote:Looks like it's an old good problem of mailman https://bugs.launchpad.net/mailman/+bug/557955 https://bugs.launchpad.net/mailman/+bug/266263I 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
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: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 DavisBTW, 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
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: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...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.
Oct 11
On Friday, 11 October 2024 at 16:14:36 UTC, Manu wrote:... I thought of an issue with separating the constructor into a bespoke name; what happens when you do this: =this(S); this(S); Which is selected? What about: =this(S); this(T)(T); which overlaps the concrete case? This questions also exist in today's language with copy constructors, but the rules are clear; regular overload selection semantics.For a move constructor to come into play, the object must first be created by the regular constructor. The move constructor facilitates transferring an already existing object to another location. In this sense, a move constructor can only be invoked if there is already an existing object. Therefore, the concern about conflicts may be unfounded because the compiler first constructs the object, and if it needs to be moved, it invokes the move constructor. Steps: 1. Regular constructor: The object is created. 2. Move constructor: If the object needs to be transferred to another place, the move constructor is called. In the current D language with normal overload semantics, the selection rules are straightforward: 1. Move semantics is preferred when an rvalue (temporary object) is passed. 2. Copy semantics is preferred when an lvalue (an existing object) is passed. Thus, there is no actual risk of conflict between the regular and move constructors, as moving can only occur after the object has been created. SDB 79
Oct 11
On Friday, October 11, 2024 10:14:36 AM MDT Manu via Digitalmars-d wrote:On Thu, 10 Oct 2024, 17:56 Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote: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.On 10/8/2024 11:54 PM, Manu wrote: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?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.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
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: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 Thu, 10 Oct 2024, 17:56 Walter Bright via Digitalmars-d, < digitalmars-d puremagic.com> wrote: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.On 10/8/2024 11:54 PM, Manu wrote: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?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.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 DavisOn 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: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.On 10/8/2024 11:54 PM, Manu wrote: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?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.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
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 <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.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 anRather 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 Davisexplicit 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 09
On Wednesday, 9 October 2024 at 08:38:52 UTC, Jonathan M Davis wrote:On Wednesday, October 9, 2024 12:54:21 AM MDT Manu viaThis 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
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, RazvanNthisMove, thisCopy, same rationale as opEquals, etc...
Oct 09
On 09/10/2024 10:17 PM, RazvanN wrote:On Wednesday, 9 October 2024 at 08:38:52 UTC, Jonathan M Davis wrote:This needs to be talked about at tomorrows meeting (which I might not be at). It is a huge benefit.On Wednesday, October 9, 2024 12:54:21 AM MDT Manu viaThis 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.
Oct 09
On Wednesday, 9 October 2024 at 20:20:02 UTC, Richard (Rikki) Andrew Cattermole wrote:On 09/10/2024 10:17 PM, RazvanN wrote:I've already put it on Mike's agenda for the meeting.On Wednesday, 9 October 2024 at 08:38:52 UTC, Jonathan M Davis wrote:This needs to be talked about at tomorrows meeting (which I might not be at). It is a huge benefit.On Wednesday, October 9, 2024 12:54:21 AM MDT Manu viaThis 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.
Oct 09
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 =aunique 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
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
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
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 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 moveI 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.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-PODstruct 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
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
On Friday, 11 October 2024 at 07:25:53 UTC, Manu wrote: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.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 14
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
On 10/14/24 18:14, RazvanN wrote:On Monday, 14 October 2024 at 16:03:15 UTC, RazvanN wrote:I think it's too hacky of a fix in general, what you discussed with Manu regarding adding base cases seems more promising.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 15
On Friday, October 11, 2024 1:25:53 AM MDT Manu via Digitalmars-d wrote: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 DavisAside 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
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: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 allconstructorsAside from whatever Walter's reasoning is, there are existinglvaluein 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 aneven 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 tosynthesiseone (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,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 islikely 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 copyconstructors or move constructors like they're normal constructors.Uniformity of semantics. Special cases are bad. They aretreated 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 tobe 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 DavisLook, 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
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
On Friday, October 11, 2024 9:01:49 AM MDT Manu via Digitalmars-d wrote: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 DavisWhat 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.
Oct 11
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: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 clueThe 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 syntaxforthe declaration!Just because a constructor such as this(typeof(this)) looks like a move constructor doesn't guarantee that it has those semantics.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 samevalue 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 programmercan 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 reallyshould 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
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
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
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
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
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