digitalmars.D - Discussion: Rvalue refs and a Move construtor for D
- Suleyman (46/46) Aug 28 2019 Hello everybody.
- Mike Franklin (6/22) Aug 28 2019 There are some of us that would really like implicit constructors
- Manu (5/29) Aug 28 2019 @implicit constructors should have been part of the copy-ctor
- Mike Franklin (6/9) Aug 28 2019 The proposals presented seem to focus primarily on move
- Suleyman (6/11) Aug 28 2019 I discussed this with Manu which is pro rvalue ref and he made a
- Manu (11/23) Aug 28 2019 It's a _part_ of perfect forwarding; the part that allows a function
- Atila Neves (3/25) Aug 29 2019 What's wrong with the current `move` and `forward`
- Manu (14/40) Aug 29 2019 Open core.lifetime and look at the text. If you're not terrified, then
- kinke (88/89) Aug 29 2019 I fully agree and it's been bothering me for years too.
- kinke (2/8) Aug 29 2019 This should have been: `callee(move(s))`.
- Suleyman (9/14) Aug 31 2019 I made a POC for the implementation without rvalue ref.
- Les De Ridder (18/26) Sep 01 2019 I realise you need to differentiate between copy constructors and
- Suleyman (12/20) Sep 01 2019 I'm just demonstrating the possibilities. Likely only one of them
- Manu (70/90) Sep 01 2019 So, I think this PR is almost there WRT generalised rvalue ref's aswell.
- Suleyman (20/20) Sep 02 2019 I updated the POC.
- Manu (5/25) Sep 02 2019 Nice work!
- Eduard Staniloiu (3/12) Sep 03 2019 Nice job!
- Suleyman (4/18) Sep 03 2019 I'm still demanding a use case for rvalue ref other than move
- Suleyman (3/4) Sep 03 2019 I'm still demanding a use case for rvalue ref other than for move
- Manu (4/8) Sep 03 2019 That's it; move semantics. That's not a minor thing...
- kinke (12/18) Sep 03 2019 Because just detecting move-construction/-assignment/'argument
- Exil (8/27) Sep 03 2019 How would it work with multi-function passing though? With a
- Manu (4/33) Sep 03 2019 Yes, this thing. The excessive memcpy's at every level of the
- Suleyman (5/8) Sep 04 2019 That aspect of rvalue ref is simply assigning to a temporary then
- Manu (3/11) Sep 04 2019 And it comes for free with Andrei's DIP `-preview`...
- kinke (5/13) Sep 04 2019 Yes, by changing the ABI details to the C++ way in this regard,
- Manu (4/18) Sep 04 2019 rvalue ref could be implicit, but it still need to be attributed on
- Exil (46/60) Sep 04 2019 So you change the ABI to pass by a pointer to the object on the
- kinke (13/21) Sep 04 2019 Yes, the assumption being that people do use `move` when they
- Eduard Staniloiu (18/39) Sep 05 2019 So you are saying that Exil's example
- Les De Ridder (13/19) Sep 05 2019 I believe that's what kinke is proposing too?
- kinke (16/21) Sep 05 2019 Yes, in my latest post, after destructing the moved-from lvalue
- Suleyman (41/84) Sep 05 2019 It looks like correct behavior to me. An lvalue remains usable
- kinke (17/25) Sep 05 2019 Yes, that's what C++ does, and is not as efficient as can be.
- Suleyman (6/19) Sep 05 2019 That sounds like an optional optimization for the compiler.
- kinke (6/11) Sep 05 2019 It could be, but I'm more interested in allowing this with an
- Exil (13/38) Sep 05 2019 There's still similar to the problem we have now. You're still
- kinke (10/14) Sep 05 2019 Resetting the moved-from instance to T.init or something similar
- kinke (10/18) Sep 05 2019 Maybe I should put more emphasis on the advantage: saving an
- Suleyman (30/35) Sep 05 2019 His initial point about the advantage of rvalue still remains
- Suleyman (2/3) Sep 05 2019 rvalue ref*
- kinke (52/65) Sep 05 2019 I know that rvalue refs in the language would enable the same
- Manu (22/30) Sep 05 2019 We lose by-val calling semantics, which are more efficient for small
- kinke (9/18) Sep 05 2019 No, I've explicitly stated that this obviously only affects
- Manu (9/27) Sep 05 2019 You dismissed the second half of my sentence.
- kinke (26/46) Sep 05 2019 Didn't seem relevant, as these are all ABI details, and I am
- Suleyman (30/31) Sep 06 2019 Seems workable. It only affects the case of moving an lvalue to
- kinke (15/29) Sep 06 2019 The problem here is the extended lifetime of the by-value
- Manu (14/45) Sep 06 2019 There's another case where you can attribute a method with `@rvalue`
- kinke (14/26) Sep 06 2019 Oh wow, this might be able to convince me, as returning an rvalue
- Suleyman (6/35) Sep 06 2019 I'm sorry but this looks like a bad idea. Moving selective parts
- Manu (3/40) Sep 06 2019 S is an rvalue; there are no other references to S. That's the point.
- Suleyman (3/5) Sep 06 2019 With rvalue ref there is a reference. That's the point of rvalue
- Manu (5/10) Sep 06 2019 The whole point is to destroy the source value though, because there's
- Manu (3/45) Sep 06 2019 This doesn't come up very often, but when you need it, it has been a
- Suleyman (5/17) Sep 06 2019 I see no problem with that. That is valid behavior with rvalue
- kinke (45/68) Sep 07 2019 I never meant to make such a huge breaking change to existing
- kinke (14/21) Sep 07 2019 In C++ terms:
- Suleyman (4/6) Sep 07 2019 I thought what you proposed only affected the output of the
- Suleyman (9/15) Sep 07 2019 This is a really just an awkward way of doing `void foo(@rvalue
- kinke (7/22) Sep 07 2019 It's not because of dangerousness or such, but firstly because I
- Suleyman (10/15) Sep 07 2019 That still doesn't justify hiding it under auto ref. If it is
- Manu (14/39) Sep 07 2019 My experience with D has shown me time and time again, when something
- Manu (15/17) Sep 07 2019 These concepts are mostly orthogonal. `auto ref` is an umbrella over
- Suleyman (7/9) Sep 07 2019 At this point we should consider having an rvalue pointer type.
- Manu (17/26) Sep 07 2019 I think the reason is that, in C++, pointers would be eliminated from
- Suleyman (13/32) Sep 07 2019 There is pointer to pointer but there is no ref to ref. That is
- Manu (11/43) Sep 07 2019 I understand where you're coming from, but this opens up an enormous
- Suleyman (5/16) Sep 07 2019 I didn't intend to just copy C++ blindly. If we did we wouldn't
- Les De Ridder (2/8) Sep 07 2019 Copy constructors got in...
- Suleyman (5/6) Sep 07 2019 Ideally the fact that copy constructors are build upon implicit
- Manu (9/26) Sep 07 2019 Actually, why doesn't it naturally work with pointers?
- Suleyman (8/19) Sep 08 2019 I didn't mean it should be like scope. I meant it should be like
- Suleyman (3/4) Sep 08 2019 Then having `cast(rvalue)` instead of the `__move` intrinsic
- Suleyman (4/10) Sep 07 2019 This can be achieved with either an rvalue type modifier like
- Manu (6/31) Sep 06 2019 It's relevant because the calling convention needs to be defined, and
- kinke (8/10) Sep 05 2019 Sry, I haven't focused on that - yes, rvalue refs would be even
- Manu (9/17) Sep 05 2019 I agree the default might do that, but it's equally valid to do
- Suleyman (20/28) Sep 04 2019 I think you scored a valid point here. This is where having
- Manu (7/26) Sep 03 2019 Move semantics aren't a feature of the move constructor, they are USED
- Suleyman (9/17) Sep 04 2019 If you can get the move constructor and move assignment in
- Suleyman (4/15) Sep 04 2019 We don't need to expose rvalue ref as a language feature to do
- a11e99z (34/41) Sep 04 2019 I want to tell something to u, Manu, WB... "u do something wrong
- RazvanN (9/29) Sep 05 2019 Why not define the move constructor like this:
- Suleyman (5/13) Sep 05 2019 You and kinke are orbiting around the same idea. Which is
- Atila Neves (7/44) Sep 03 2019 I've opened it before. I don't know how much of it is accidental
- Manu (13/60) Sep 03 2019 You have to look no further than a call to `memcpy` in any of those
- Les De Ridder (10/21) Aug 29 2019 Please don't forget to explicitly deprecate DIP 1014 in your DIP
- Olivier FAURE (5/8) Aug 29 2019 An alternative solution to rvalue refs would be uniqueness
- RazvanN (7/12) Aug 29 2019 I would like to point out that I have been working on a move
- kinke (20/22) Aug 29 2019 It does, the classical example being passing an rvalue argument
- Per =?UTF-8?B?Tm9yZGzDtnc=?= (18/21) Aug 29 2019 Strongly related is my wish to make code like
Hello everybody. Recently D adpoted the copy constructor, the postblit is deprecated, and the postmove (DIP 1014) is also deprecated since it has the same problems as the postblit. I am planning in this SOAC of 2019 to work on an alternative to the postmove (DIP 1014) which will be similar to C++' move constructor and move assignment operator. But in C++ these take an rvalue ref argument which we don't have in D (yet). Move operations are already done by the compiler whether we have rvalue ref or not. The move constructor and the move assignment operator can be made in D with or without rvalue refs. Here a short comparison of both scenarios. Example: ``` struct S { this( rvalue ref S) { /* move constructor */ } auto opAssign( rvalue ref S) { /* move op assign */ } } ``` * Pros: - seems natural: doesn't smell like compiler magic (but it still is compiler magic, at least until D adopts implicit constructors) - overloadable with other constructors and opAssign declarations *Cons - requires adding rvalue ref to the language Example: ``` struct S { __move_ctor(ref S) { /* move constructor */ } auto opMoveAssign(ref S) { /* move op assign */ } } ``` * Pros: - doesn't require extra features in the language * Cons: - ugly constructor name: otherwise it would clash with the copy constructor. - doesn't overload with regular constructor or regular opAssign Give me your thoughts on which way you prefer. And most importantly I want you to convince me why adding rvalue refs is good for D because there aren't many use cases that I know of.
Aug 28 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:The move constructor and the move assignment operator can be made in D with or without rvalue refs. Here a short comparison of both scenarios. Example: ``` struct S { this( rvalue ref S) { /* move constructor */ } auto opAssign( rvalue ref S) { /* move op assign */ } } ``` * Pros: - seems natural: doesn't smell like compiler magic (but it still is compiler magic, at least until D adopts implicit constructors)There are some of us that would really like implicit constructors (done right; not like C++) for other use cases. How would implicit constructors help this situation, and should it be included as part of this proposal? Mike
Aug 28 2019
On Wed, Aug 28, 2019 at 5:15 PM Mike Franklin via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:implicit constructors should have been part of the copy-ctor proposal, which I argued at the time... but it wasn't so it's effectively an unrelated DIP now.The move constructor and the move assignment operator can be made in D with or without rvalue refs. Here a short comparison of both scenarios. Example: ``` struct S { this( rvalue ref S) { /* move constructor */ } auto opAssign( rvalue ref S) { /* move op assign */ } } ``` * Pros: - seems natural: doesn't smell like compiler magic (but it still is compiler magic, at least until D adopts implicit constructors)There are some of us that would really like implicit constructors (done right; not like C++) for other use cases. How would implicit constructors help this situation, and should it be included as part of this proposal?
Aug 28 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:Give me your thoughts on which way you prefer. And most importantly I want you to convince me why adding rvalue refs is good for D because there aren't many use cases that I know of.The proposals presented seem to focus primarily on move semantics, but one of the other issues rvalue references aimed to solve was perfect forwarding. How does D do perfect forwarding with your proposal or without rvalue references? Mike
Aug 28 2019
On Thursday, 29 August 2019 at 01:01:36 UTC, Mike Franklin wrote:The proposals presented seem to focus primarily on move semantics, but one of the other issues rvalue references aimed to solve was perfect forwarding. How does D do perfect forwarding with your proposal or without rvalue references? MikeI discussed this with Manu which is pro rvalue ref and he made a lot of good points, the most important thing is that rvalue ref would drastically simplify the implementation of `core.lifetime.move()`. But he pointed out that auto ref already does perfect forwarding.
Aug 28 2019
On Wed, Aug 28, 2019 at 8:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 29 August 2019 at 01:01:36 UTC, Mike Franklin wrote:It's a _part_ of perfect forwarding; the part that allows a function to receive forwarded arguments. The other part is a `forward` implementation passes them forward (and actually works), and that depends on a `move` implementation that works. Perfect forwarding in D would be a combination of `auto ref` and `forward`. We have all the concepts we need, so I don't think that's actually related to this topic. If we define non-trivial move, those forwarding concepts should certainly map, or the proposal would fail instantly.The proposals presented seem to focus primarily on move semantics, but one of the other issues rvalue references aimed to solve was perfect forwarding. How does D do perfect forwarding with your proposal or without rvalue references? MikeI discussed this with Manu which is pro rvalue ref and he made a lot of good points, the most important thing is that rvalue ref would drastically simplify the implementation of `core.lifetime.move()`. But he pointed out that auto ref already does perfect forwarding.
Aug 28 2019
On Thursday, 29 August 2019 at 04:38:03 UTC, Manu wrote:On Wed, Aug 28, 2019 at 8:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:What's wrong with the current `move` and `forward` implementations?On Thursday, 29 August 2019 at 01:01:36 UTC, Mike Franklin wrote:It's a _part_ of perfect forwarding; the part that allows a function to receive forwarded arguments. The other part is a `forward` implementation passes them forward (and actually works), and that depends on a `move` implementation that works.[...]I discussed this with Manu which is pro rvalue ref and he made a lot of good points, the most important thing is that rvalue ref would drastically simplify the implementation of `core.lifetime.move()`. But he pointed out that auto ref already does perfect forwarding.
Aug 29 2019
On Thu, Aug 29, 2019 at 2:45 AM Atila Neves via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 29 August 2019 at 04:38:03 UTC, Manu wrote:Open core.lifetime and look at the text. If you're not terrified, then you're a braver man than me. Then also consider that the file has edge cases and bugs which I've run into on numerous occasions. We don't have move semantics, we have copy-elision and a bunch of hacks that unnecessarily call memcpy a lot of times (and don't actually work). Our move semantics are slow (lots of copying, not actually moving), and occasionally broken. For reference, here's the C++ implementation of that entire file: template <class T> T&& move(T& val) { return (T&&)val; } Our implementation would/should be similar.On Wed, Aug 28, 2019 at 8:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:What's wrong with the current `move` and `forward` implementations?On Thursday, 29 August 2019 at 01:01:36 UTC, Mike Franklin wrote:It's a _part_ of perfect forwarding; the part that allows a function to receive forwarded arguments. The other part is a `forward` implementation passes them forward (and actually works), and that depends on a `move` implementation that works.[...]I discussed this with Manu which is pro rvalue ref and he made a lot of good points, the most important thing is that rvalue ref would drastically simplify the implementation of `core.lifetime.move()`. But he pointed out that auto ref already does perfect forwarding.
Aug 29 2019
On Thursday, 29 August 2019 at 19:04:45 UTC, Manu wrote:Our move semantics are slow (lots of copyingI fully agree and it's been bothering me for years too. C++: void callee(NoPOD s); /* Low-level, this is actually `void callee(NoPOD &&s)`. * The caller is to allocate & set up the instance before the call, * pass a pointer to it and then destruct it after the call. */ void caller() { // rvalue case: callee(NoPOD()); /* actually: { NoPOD temp; callee((NoPOD &&) &temp); } // destruct temp */ // lvalue case: NoPOD lvalueArg; callee(lvalueArg); /* actually: { NoPOD temp = lvalueArg; // full copy callee((NoPOD &&) &temp); } // destruct temp */ // manual moving: callee(std::move(lvalueArg)); /* actually: callee((NoPOD &&) &lvalueArg); */ } D: If we adopted the C++ ABI in this regard (avoid the stack for non-PODs, possibly for large PODs too, and use rvalue refs under the hood) and made `move` an intrinsic, the compiler could use it to elide the lvalue copy for arguments passed by value. Similary, if `forward` was an intrinsic, the compiler could use it to propagate the lvalue-ness of `auto ref` parameters as arguments: void callee(ref NoPOD s); // for lvalues void callee(NoPOD s); // for rvalues, actually: ` rvalue ref NoPOD s` void wrapper()(auto ref NoPOD s) { callee(forward(s)); /* actually: static if (__traits(isRef, s)) // void wrapper(ref NoPOD s) { // call lvalue overload, perfectly forwarding the `s` reference callee(s); } else // void wrapper( rvalue ref NoPOD s) { // call rvalue overload, perfectly forwarding the `s` rvalue reference callee(s); } } void caller() { // rvalue case: wrapper(NoPOD()); /* actually: { NoPOD temp; wrapper(cast( rvalue ref) temp); // just forwards the pointer to rvalue-version of callee } // destruct temp */ // lvalue case: NoPOD lvalueArg; wrapper(lvalueArg); // just forwards the pointer to lvalue-version of callee // manual moving: wrapper(move(lvalueArg)); /* actually: wrapper(cast( rvalue ref) lvalueArg); // forwards the pointer to rvalue-version of callee */ } I hope that covers enough use cases, so that we could get away with ABI change (which btw would also fix C++ interop wrt. passing non-PODs by value) + move/forward as intrinsics, without having to touch the language and introducing ugly ` rvalue ref`.
Aug 29 2019
On Thursday, 29 August 2019 at 20:26:10 UTC, kinke wrote:else // void wrapper( rvalue ref NoPOD s) { // call rvalue overload, perfectly forwarding the `s` rvalue reference callee(s); }This should have been: `callee(move(s))`.
Aug 29 2019
I made a POC for the implementation without rvalue ref. Here it is: https://github.com/dlang/dmd/pull/10383. It compiles. And works just like the rvalue ref implementation would. On Thursday, 29 August 2019 at 19:04:45 UTC, Manu wrote:For reference, here's the C++ implementation of that entire file: template <class T> T&& move(T& val) { return (T&&)val; } Our implementation would/should be similar.I also added a `cast(rvalue)` which does the same as the `(T&&)` in C++ but returns an rvalue. And an intrinsic `__move()` and a `__traits(getRvalue)` which are simply syntactic flavors of `cast(rvalue)`.
Aug 31 2019
On Saturday, 31 August 2019 at 20:15:22 UTC, Suleyman wrote:I made a POC for the implementation without rvalue ref. Here it is: https://github.com/dlang/dmd/pull/10383. It compiles. And works just like the rvalue ref implementation would.I realise you need to differentiate between copy constructors and move constructors somehow, but I'm not sure calling move constructors `__move_ctor` is the right solution. The average user of D should neverhave to use a `__` symbol, but it's not unlikely they might want to write a struct with move semantics. Maybe a solution with a UDA is possible?I also added a `cast(rvalue)` which does the same as the `(T&&)` in C++ but returns an rvalue. And an intrinsic `__move()` and a `__traits(getRvalue)` which are simply syntactic flavors of `cast(rvalue)`.I'm not sure why there are three different ways to 'cast to an rvalue'? Is there a technical reason? I also noticed you're casting lvalues to 'rvalues' and then binding them to a `ref` param. Is that not an rvalue reference? Why can't we have generalised rvalue references instead?
Sep 01 2019
On Sunday, 1 September 2019 at 08:59:08 UTC, Les De Ridder wrote:[...] Maybe a solution with a UDA is possible?Possible.I'm not sure why there are three different ways to 'cast to an rvalue'? Is there a technical reason?I'm just demonstrating the possibilities. Likely only one of them will be picked.I also noticed you're casting lvalues to 'rvalues' and then binding them a `ref` param. Is that not an rvalue reference?Yes but I would argue this is just a perk of rvalue ref and not what the essence of it is. You can do the same thing by assigning to a temporary then passing it by ref. This all the compiler does here.Why can't we have generalised rvalue references instead?What do you need if for? Rvalue ref comes with it's own overloading and implicit conversion rules. Generalized rvalue ref is not absolutely needed for move semantics. What else comes to mind other than move semantics as a use case?
Sep 01 2019
On Sun, Sep 1, 2019 at 7:10 AM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Sunday, 1 September 2019 at 08:59:08 UTC, Les De Ridder wrote:So, I think this PR is almost there WRT generalised rvalue ref's aswell. This approach is a twofer; at least, with the tweak to use an ` rvalue` attribute instead of a weird name. If `this( rvalue T)` only accepts rvalues, then as that interacts with Andrei's DIP which he presented at dconf (available via `-preview` today) which can pass rvalues to ref by implicit temporary, then this naturally gives rvalue references by coincidence. It's encouraging when language features fit together naturally. So, I guess this is the suite of constructor-like functions that would be technically possible: A: this(T) B: this( rvalue T) C: this(ref T) D: this( rvalue ref T) So, consider what each of these functions can do: A: arg in an rvalue by-value - can accept an rvalue or an lvalue (by copying) B: arg is an rvalue by-value - I *guess* that this syntax would only allow the function to accept rvalues as arguments unlike A C: arg is an lvalue by-ref - can accept lvalue, and with Andrei's DIP `-preview`, can accept rvalue by implicit temporary D: arg is an rvalue by-ref - can accept rvalues only, by implicit temporary as per Andrei's DIP. C is the only case where the argument received is an lvalue, and as such, we recognise this as a copy constructor. What's interesting, is that all the others are theoretically valid forms of move constructor... Hmm. D is the most-desirable form of move constructor, but I don't see any reason to *require* that form, they're all functionally valid. Other interesting observations, is that A and B are potentially redundant, but maybe useful in a rare case where you want your API to explicitly reject lvalues. I can see some use on that; which we describe that today with ` disable` tricks, but I think this is much more obvious. There are some ambiguities, or overload selection preferences here that would need to be spec-ed: Today, A and C can overload, with the rule that an lvalue prefers C and an rvalue prefers A. I'll try and expand the complete set, cases are where combinations of the functions above exist: A: rvalue -> A, lvalue -> A B: rvalue -> B, lvalue -> ERROR (not accept lvalue) C: rvalue -> C*, lvalue -> C (* note: rvalue's may call by implicit temp as-per Andrei's DIP, but this is NOT a move operation, just a convenience) D: rvalue -> D, lvalue -> ERROR (not accept lvalue) AB: rvalue -> B, lvalue -> A AC: rvalue -> A, lvalue -> C (**existing rules today**) AD: rvalue -> D, lvalue -> A BC: rvalue -> B, lvalue -> C BD: rvalue -> ERROR (ambiguous B/D), lvalue -> ERROR (no overload accepts lvalues) CD: rvalue -> D, lvalue -> C ABC: rvalue -> B, lvalue -> C ABD: rvalue -> ERROR (ambiguous B/D), lvalue -> A ACD: rvalue -> D, lvalue -> C ABCD: rvalue -> ERROR (ambiguous B/D), lvalue -> C So the apparent rules from that table are: * With respect to lvalues, the above table shows no change from existing rules (good) * rvalues prefer explicit ` rvalue`, otherwise fall back to existing rules (seems right) * B & D are ambiguous overloads, and are an error if they both exist. No change in existing rules is observed, so no chance of breakage there. One additional observation, is that in the sets ABC, ACD, ABCD, while the A overload is present, it's not selected in either the lvalue or rvalue cases... so is it a redundant overload? This case needs to be understood. TL;DR, we don't need to define rvalue ref semantics explicitly here, because they just emerge naturally in conjunction with Andrei's DIP.[...] Maybe a solution with a UDA is possible?Possible.I'm not sure why there are three different ways to 'cast to an rvalue'? Is there a technical reason?I'm just demonstrating the possibilities. Likely only one of them will be picked.I also noticed you're casting lvalues to 'rvalues' and then binding them a `ref` param. Is that not an rvalue reference?Yes but I would argue this is just a perk of rvalue ref and not what the essence of it is. You can do the same thing by assigning to a temporary then passing it by ref. This all the compiler does here.Why can't we have generalised rvalue references instead?What do you need if for? Rvalue ref comes with it's own overloading and implicit conversion rules. Generalized rvalue ref is not absolutely needed for move semantics. What else comes to mind other than move semantics as a use case?
Sep 01 2019
I updated the POC. You can now declare the move constructor & move opAssing like the following: ``` struct S { this(ref S) move {} // move constructor opAssign(ref S) move {} // move opAssign } ``` Or with rvalue ref: ``` struct S { this( rvalue ref S) {} // move constructor opAssign( rvalue ref S) {} // move opAssign } ``` All implementations use rvalue ref internally. It's just a matter of exposing in the language it or not.
Sep 02 2019
On Mon., 2 Sep. 2019, 6:10 pm Suleyman via Digitalmars-d, < digitalmars-d puremagic.com> wrote:I updated the POC. You can now declare the move constructor & move opAssing like the following: ``` struct S { this(ref S) move {} // move constructor opAssign(ref S) move {} // move opAssign } ``` Or with rvalue ref: ``` struct S { this( rvalue ref S) {} // move constructor opAssign( rvalue ref S) {} // move opAssign } ``` All implementations use rvalue ref internally. It's just a matter of exposing in the language it or not.Nice work! I definitely think it needs to be on the argument and not on the method, or it can't be used on functions that take more than one argument.
Sep 02 2019
On Tuesday, 3 September 2019 at 01:50:20 UTC, Manu wrote:On Mon., 2 Sep. 2019, 6:10 pm Suleyman via Digitalmars-d, < digitalmars-d puremagic.com> wrote:Nice job! I agree with Manu.I updated the POC. [...]Nice work! I definitely think it needs to be on the argument and not on the method, or it can't be used on functions that take more than one argument.
Sep 03 2019
On Tuesday, 3 September 2019 at 11:20:43 UTC, Eduard Staniloiu wrote:On Tuesday, 3 September 2019 at 01:50:20 UTC, Manu wrote:I'm still demanding a use case for rvalue ref other than move semantics.On Mon., 2 Sep. 2019, 6:10 pm Suleyman via Digitalmars-d, < digitalmars-d puremagic.com> wrote:Nice job! I agree with Manu.I updated the POC. [...]Nice work! I definitely think it needs to be on the argument and not on the method, or it can't be used on functions that take more than one argument.
Sep 03 2019
On Tuesday, 3 September 2019 at 01:50:20 UTC, Manu wrote:it can't be used on functions that take more than one argument.I'm still demanding a use case for rvalue ref other than for move semantics.
Sep 03 2019
On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Tuesday, 3 September 2019 at 01:50:20 UTC, Manu wrote:That's it; move semantics. That's not a minor thing... Why?it can't be used on functions that take more than one argument.I'm still demanding a use case for rvalue ref other than for move semantics.
Sep 03 2019
On Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Because just detecting move-construction/-assignment/'argument moval' can get away with special identifiers for constructor/assignment operator or some special move UDA, as Suleyman has proposed so far, instead of fully extending the language by rvalue refs, with mangling additions and new overload rules etc. It's clear that this would restrict C++ interop, but that's the point - do we want to adopt the C++ approach fully, or keep things simple & tidy at the expense of not being able to represent C++ functions with rvalue refs (except for move constructor and assignment op)?I'm still demanding a use case for rvalue ref other than for move semantics.That's it; move semantics. That's not a minor thing... Why?
Sep 03 2019
On Wednesday, 4 September 2019 at 00:16:06 UTC, kinke wrote:On Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference, until the contents of the value are moved. So you can pass it through N functions and it won't ever to do a move/copy. Would you effectively be relying on the compiler to optimize out the un-necessary moves, or would they be unavoidable, as they effectively are now?On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Because just detecting move-construction/-assignment/'argument moval' can get away with special identifiers for constructor/assignment operator or some special move UDA, as Suleyman has proposed so far, instead of fully extending the language by rvalue refs, with mangling additions and new overload rules etc. It's clear that this would restrict C++ interop, but that's the point - do we want to adopt the C++ approach fully, or keep things simple & tidy at the expense of not being able to represent C++ functions with rvalue refs (except for move constructor and assignment op)?I'm still demanding a use case for rvalue ref other than for move semantics.That's it; move semantics. That's not a minor thing... Why?
Sep 03 2019
On Tue, Sep 3, 2019 at 5:35 PM Exil via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Wednesday, 4 September 2019 at 00:16:06 UTC, kinke wrote:Yes, this thing. The excessive memcpy's at every level of the callstack are a major problem.On Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference, until the contents of the value are moved. So you can pass it through N functions and it won't ever to do a move/copy. Would you effectively be relying on the compiler to optimize out the un-necessary moves, or would they be unavoidable, as they effectively are now?On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Because just detecting move-construction/-assignment/'argument moval' can get away with special identifiers for constructor/assignment operator or some special move UDA, as Suleyman has proposed so far, instead of fully extending the language by rvalue refs, with mangling additions and new overload rules etc. It's clear that this would restrict C++ interop, but that's the point - do we want to adopt the C++ approach fully, or keep things simple & tidy at the expense of not being able to represent C++ functions with rvalue refs (except for move constructor and assignment op)?I'm still demanding a use case for rvalue ref other than for move semantics.That's it; move semantics. That's not a minor thing... Why?
Sep 03 2019
On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference.That aspect of rvalue ref is simply assigning to a temporary then passing it by ref. C++ example: https://cpp.godbolt.org/z/PRmkjd D equivalent: https://d.godbolt.org/z/idPqIN
Sep 04 2019
On Wed, Sep 4, 2019 at 1:25 AM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:And it comes for free with Andrei's DIP `-preview`...How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference.That aspect of rvalue ref is simply assigning to a temporary then passing it by ref. C++ example: https://cpp.godbolt.org/z/PRmkjd D equivalent: https://d.godbolt.org/z/idPqIN
Sep 04 2019
On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference, until the contents of the value are moved. So you can pass it through N functions and it won't ever to do a move/copy. Would you effectively be relying on the compiler to optimize out the un-necessary moves, or would they be unavoidable, as they effectively are now?Yes, by changing the ABI details to the C++ way in this regard, making move/forward intrinsics and detecting them in argument expressions. Laid out earlier in this thread in https://forum.dlang.org/post/jogsaeqxouxaeflmgzcc forum.dlang.org. We don't need rvalue refs in the language for that at all, just use a value parameter - rvalue args will be passed by ref under the hood, just like explicitly moved lvalue args.
Sep 04 2019
Wed, Sep 4, 2019 at 2:25 AM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:rvalue ref could be implicit, but it still need to be attributed on the argument... isn't that what's being resisted here?How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference, until the contents of the value are moved. So you can pass it through N functions and it won't ever to do a move/copy. Would you effectively be relying on the compiler to optimize out the un-necessary moves, or would they be unavoidable, as they effectively are now?Yes, by changing the ABI details to the C++ way in this regard, making move/forward intrinsics and detecting them in argument expressions. Laid out earlier in this thread in https://forum.dlang.org/post/jogsaeqxouxaeflmgzcc forum.dlang.org. We don't need rvalue refs in the language for that at all, just use a value parameter - rvalue args will be passed by ref under the hood, just like explicitly moved lvalue args.
Sep 04 2019
On Wednesday, 4 September 2019 at 09:20:05 UTC, kinke wrote:On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:So you change the ABI to pass by a pointer to the object on the stack. In cases where we use some "move" intrinsic, a pointer to a lvalue (passed in to the move()) is passed in place of the pointer to the object on the stack? If I understand this correctly then this can happen: struct Foo { int value; } void bar(Foo foo) // for rvalues, actually: ` rvalue ref Foo foo` { // how do we know we are just changing the value of a temporary // that won't exist past this scope? foo.value = 10; } void main() { Foo lvalue; lvalue.value = 0; bar(move(lvalue)); // intrinsic move assert(lvalue.value == 10); // passes } Because of this, even though the function doesn't use a reference. It could unknowingly change state outside of the scope of the function. That'll entirely depend on the use if they mistakenly use `move()` where they didn't mean to. This would mean basically any current use of rvalue parameters would need to be changed to be "const". And any function that actually does need a copy would have to do so manually. They don't know what is being passed to them, so it could very well be making a copy twice. void test(Foo foo) { Foo actualFoo = foo; // make copy just in case parameter was `move`d in actualFoo.value = 10; } Foo lvalue; lvalue.value = 0; test(lvalue); // makes 2 copies assert(lvalue.value == 0); // ok test(move(lvalue)); // makes 1 copy assert(lvalue.value == 0); // okHow would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference, until the contents of the value are moved. So you can pass it through N functions and it won't ever to do a move/copy. Would you effectively be relying on the compiler to optimize out the un-necessary moves, or would they be unavoidable, as they effectively are now?Yes, by changing the ABI details to the C++ way in this regard, making move/forward intrinsics and detecting them in argument expressions. Laid out earlier in this thread in https://forum.dlang.org/post/jogsaeqxouxaeflmgzcc forum.dlang.org. We don't need rvalue refs in the language for that at all, just use a value parameter - rvalue args will be passed by ref under the hood, just like explicitly moved lvalue args.
Sep 04 2019
On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:So you change the ABI to pass by a pointer to the object on the stack. In cases where we use some "move" intrinsic, a pointer to a lvalue (passed in to the move()) is passed in place of the pointer to the object on the stack?Yes.Because of this, even though the function doesn't use a reference. It could unknowingly change state outside of the scope of the function. That'll entirely depend on the use if they mistakenly use `move()` where they didn't mean to.Yes, the assumption being that people do use `move` when they mean to (it's explicit after all) and are fine with not being able to make any assumptions about its state after the move. Re-assigning would still be possible. But as stated further up, the crux is probably the lifetime for moved lvalues, i.e., the by-value parameter not being destroyed right before/after returning, but when the moved-from lvalue goes out of scope. Maybe we could destruct the moved-from lvalue after the call and reset it to `T.init`. It would be destroyed a 2nd time when going out of scope.
Sep 04 2019
On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:So you are saying that Exil's example ``` void main() { Foo lvalue; lvalue.value = 0; bar(move(lvalue)); // intrinsic move assert(lvalue.value == 10); // passes } ``` would be valid and it's ok? I'm sorry, but, in my humble opinion, this would be horrible. I would expect the contents of the moved lvalue to be reset to `Foo.init`. I think the behavior shown above would be error prone and a big source of bugs. EdiSo you change the ABI to pass by a pointer to the object on the stack. In cases where we use some "move" intrinsic, a pointer to a lvalue (passed in to the move()) is passed in place of the pointer to the object on the stack?Yes.Because of this, even though the function doesn't use a reference. It could unknowingly change state outside of the scope of the function. That'll entirely depend on the use if they mistakenly use `move()` where they didn't mean to.Yes, the assumption being that people do use `move` when they mean to (it's explicit after all) and are fine with not being able to make any assumptions about its state after the move. Re-assigning would still be possible. But as stated further up, the crux is probably the lifetime for moved lvalues, i.e., the by-value parameter not being destroyed right before/after returning, but when the moved-from lvalue goes out of scope. Maybe we could destruct the moved-from lvalue after the call and reset it to `T.init`. It would be destroyed a 2nd time when going out of scope.
Sep 05 2019
On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu wrote:On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:I believe that's what kinke is proposing too? In C++, objects that are moved from should be left in an unspecified but valid state. It is up to the move constructor to guarantee that this rule is actually satisfied. As I understand it, because we generally try to avoid the C++ convention of only guaranteeing things by ... convention, it could make sense to reset moved-from lvalues to `T.init` implicitly.On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:[...] I would expect the contents of the moved lvalue to be reset to `Foo.init`.
Sep 05 2019
On Thursday, 5 September 2019 at 13:03:04 UTC, Les De Ridder wrote:On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu wrote:Yes, in my latest post, after destructing the moved-from lvalue right after the call (primarily to allow for a 2nd destruction). I haven't gotten round to think a lot about corner cases, but the intention is to make high-level by-value passing as efficient as possible, without ugly rvalue ref complications, and not to bother the language user about what's going on under the hood. One corner case would be: T callee(T param) { return param; } lval = callee(move(lval)); `lval` in memory would both be input and output at the same time, firstly leading to an invalid memcpy, and secondly, it would be destructed and reset to T.init after the call. This restriction could be described as rule 'do not access the lvalue to be moved anywhere else in the statement'.I would expect the contents of the moved lvalue to be reset to `Foo.init`.I believe that's what kinke is proposing too?
Sep 05 2019
On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu wrote:On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:It looks like correct behavior to me. An lvalue remains usable after move. The state may change depending on what the move constructor does. But move doesn't end its lifetime, destruction happens only at the end of the scope when the lifetime of the variable actually ends. Practical example: ``` struct S { void* bigMem; void* end; disable this(); this(size_t size) { import core.stdc.stdlib : malloc; bigMem = malloc(size); end = bigMem + size; } invariant() { assert((bigMem is null) == (end is null)); } this(ref S rhs) move { // steal resources from rhs bigMem = rhs.bigMem; end = bigMem + rhs.length; rhs.bigMem = null; rhs.end = null; } property size_t length() { return bigMem is null ? 0 : end - bigMem; } } enum _1GB = 1024^^3; void bar(S s) { assert(s.length == _1GB); } void main() { auto s = S(_1GB); bar(__move(s)); // calls move ctor assert(s.length == 0); // s is still usable } ```On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:So you are saying that Exil's example ``` void main() { Foo lvalue; lvalue.value = 0; bar(move(lvalue)); // intrinsic move assert(lvalue.value == 10); // passes } ``` would be valid and it's ok? I'm sorry, but, in my humble opinion, this would be horrible. I would expect the contents of the moved lvalue to be reset to `Foo.init`. I think the behavior shown above would be error prone and a big source of bugs. EdiSo you change the ABI to pass by a pointer to the object on the stack. In cases where we use some "move" intrinsic, a pointer to a lvalue (passed in to the move()) is passed in place of the pointer to the object on the stack?Yes.Because of this, even though the function doesn't use a reference. It could unknowingly change state outside of the scope of the function. That'll entirely depend on the use if they mistakenly use `move()` where they didn't mean to.Yes, the assumption being that people do use `move` when they mean to (it's explicit after all) and are fine with not being able to make any assumptions about its state after the move. Re-assigning would still be possible. But as stated further up, the crux is probably the lifetime for moved lvalues, i.e., the by-value parameter not being destroyed right before/after returning, but when the moved-from lvalue goes out of scope. Maybe we could destruct the moved-from lvalue after the call and reset it to `T.init`. It would be destroyed a 2nd time when going out of scope.
Sep 05 2019
On Thursday, 5 September 2019 at 15:32:31 UTC, Suleyman wrote:Practical example: [...] void main() { auto s = S(_1GB); bar(__move(s)); // calls move ctor assert(s.length == 0); // s is still usable }Yes, that's what C++ does, and is not as efficient as can be. What I'm looking for is that there's no actual moving (move ctor call etc.) at all for move/__move in an argument expression. In your code example: void main() { auto s = S(_1GB); // does NOT call move ctor, just passes `s` by ref directly instead of a moved-to // temporary bar(__move(s)); // after the call, destruct `s` and reset to T.init assert(s.length == 0); // s is still usable // this now does call the move ctor: auto s2 = __move(s); } // `s` goes out of scope and is destructed again
Sep 05 2019
On Thursday, 5 September 2019 at 15:49:37 UTC, kinke wrote:[...] void main() { auto s = S(_1GB); // does NOT call move ctor, just passes `s` by ref directly instead of a moved-to // temporary bar(__move(s)); // after the call, destruct `s` and reset to T.initThat sounds like an optional optimization for the compiler. That's if the compiler finds that s is never used after the call to bar.assert(s.length == 0); // s is still usable // this now does call the move ctor: auto s2 = __move(s); } // `s` goes out of scope and is destructed againCalling the destructor twice is not more efficient than one call to the move constructor and one call to the destructor.
Sep 05 2019
On Thursday, 5 September 2019 at 17:53:29 UTC, Suleyman wrote:That sounds like an optional optimization for the compiler. That's if the compiler finds that s is never used after the call to bar.It could be, but I'm more interested in allowing this with an explicit move (incl. allowed reuse of that lvalue later). Then I don't see any reason for rvalue refs in D.Calling the destructor twice is not more efficient than one call to the move constructor and one call to the destructor.C++/your current proposal ends in 2 destructions too (moved-to temporary and moved-from lvalue), so there's no overhead at all.
Sep 05 2019
On Thursday, 5 September 2019 at 15:49:37 UTC, kinke wrote:On Thursday, 5 September 2019 at 15:32:31 UTC, Suleyman wrote:There's still similar to the problem we have now. You're still doing a memcpy() each time with S.init. And you are calling the destructor now too, which entirely depends on what it is. void foo(S value, int n = 0) { if ( n > 32 ) { return; } foo( move(value), n + 1); } S lvalue; foo( move(lvalue) ); // "lvalue" destructed and set to S.init 32 timesPractical example: [...] void main() { auto s = S(_1GB); bar(__move(s)); // calls move ctor assert(s.length == 0); // s is still usable }Yes, that's what C++ does, and is not as efficient as can be. What I'm looking for is that there's no actual moving (move ctor call etc.) at all for move/__move in an argument expression. In your code example: void main() { auto s = S(_1GB); // does NOT call move ctor, just passes `s` by ref directly instead of a moved-to // temporary bar(__move(s)); // after the call, destruct `s` and reset to T.init assert(s.length == 0); // s is still usable // this now does call the move ctor: auto s2 = __move(s); } // `s` goes out of scope and is destructed again
Sep 05 2019
On Thursday, 5 September 2019 at 18:46:59 UTC, Exil wrote:There's still similar to the problem we have now. You're still doing a memcpy() each time with S.init.Resetting the moved-from instance to T.init or something similar is what you'd do in the move ctor anyway (besides blitting the previous contents into the new instance and maybe doing some more adjustments), and definitely what the default implementation of the move ctor would do.And you are calling the destructor now too, which entirely depends on what it is.As stated above, there's no extra destruction - the first one is for the by-value parameter (which would otherwise be the moved-from temporary), and the second is the regular destruction of the moved-from lvalue.
Sep 05 2019
On Thursday, 5 September 2019 at 18:59:50 UTC, kinke wrote:On Thursday, 5 September 2019 at 18:46:59 UTC, Exil wrote:Maybe I should put more emphasis on the advantage: saving an allocation of a temporary and move-constructing it. That's peanuts for Suleyman's struct, but if we are talking about a 1KB struct or static array, then the savings may have a significant impact. Additionally, the extra logic in the move ctor can be elided. DIP 1014 (opPostMove) was born, IIRC, because Weka needs to keep track of the addresses of live instances in some outer code, so saving such re-registering may boost performance as well.There's still similar to the problem we have now. You're still doing a memcpy() each time with S.init.Resetting the moved-from instance to T.init or something similar is what you'd do in the move ctor anyway (besides blitting the previous contents into the new instance and maybe doing some more adjustments), and definitely what the default implementation of the move ctor would do.
Sep 05 2019
On Thursday, 5 September 2019 at 18:59:50 UTC, kinke wrote:Resetting the moved-from instance to T.init or something similar is what you'd do in the move ctor anyway (besides blitting the previous contents into the new instance and maybe doing some more adjustments), and definitely what the default implementation of the move ctor would do.His initial point about the advantage of rvalue still remains unchallenged. Example: ``` void foo( rvalue ref S value, int n = 0) { if (n > 32) return; foo(__move(value), n + 1); } struct S { long[10] a; import core.stdc.stdio : puts; this( rvalue ref S) { puts("mc"); } this(ref S) { puts("cc"); } auto opAssign( rvalue ref S) { puts("m="); } auto opAssign(ref S) { puts("c="); } ~this() { puts("~"); } } void main() { S lvalue; foo(__move(lvalue)); } ``` You can try this with the POC. The whole program only calls the destructor for the lvalue, and only once. You need a competitive alternative.
Sep 05 2019
On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:His initial point about the advantage of rvalue [...]rvalue ref*
Sep 05 2019
On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:His initial point about the advantage of rvalue still remains unchallenged. Example: ``` void foo( rvalue ref S value, int n = 0) { if (n > 32) return; foo(__move(value), n + 1); } [...] The whole program only calls the destructor for the lvalue, and only once. You need a competitive alternative.I know that rvalue refs in the language would enable the same thing, but that's exactly what I'd like to avoid to keep things nice and simple. When using some 3rd-party code, you don't want to depend on them providing the required (and ugly) rvalue ref signatures when coming along with a complex 1KB struct. And there are no rvalue refs in existing code as of now, so existing code bases would have to be uglified to exploit the potential. This is more or less how I'd imagine it at this time: struct S { // only called for: `auto s = move(rhs)` moveThis(ref S rhs); ~this(); // only called for: `s = move(rhs)` opMoveAssign(ref S rhs); } // forwarding `auto ref` parameter - unchanged: void callee(ref S lvalArg); void callee(S rvalArg); void foo()(auto ref S s) // either `ref S` reference or `S` value { // ref case: pass along `s` ref // value case: `move(s)` (no actual moving, rather imagine forwarding an rvalue ref when coming from C++) callee(forward(s)); } void foo(S param); // NEW D ABI: non-PODs and large PODs passed by ref, not on the stack. // Already the case for D on Win64 and how C++ seems to do it generally. // Also new: `foo` doesn't destruct `param` anymore, that is to be done by the // caller. That's how C++ does it and currently a hurdle for C++ interop. void caller(S s) { foo(S()); // construct temporary and pass by ref, then destruct foo(s); // copy-construct temporary and pass by ref, then destruct foo(move(s)); // pass `s` directly by ref, then destruct and reset to S.init } Potential problems: * Don't access the moved lvalue anywhere else in the statement. * An lvalue moved in an argument expression is destructed twice, i.e., 2 times at the same address. * Cannot represent C++ functions with rvalue refs (except for move ctor and move-assignment operator). * Some more, I'm sure. ;)
Sep 05 2019
On Thu, Sep 5, 2019 at 1:30 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:[...] Potential problems: * Don't access the moved lvalue anywhere else in the statement. * An lvalue moved in an argument expression is destructed twice, i.e., 2 times at the same address. * Cannot represent C++ functions with rvalue refs (except for move ctor and move-assignment operator). * Some more, I'm sure. ;)We lose by-val calling semantics, which are more efficient for small struct's (most things), and certain classes of wide-registers in various architectures (impossible to codify the proper rules in the language). I have pondered this same line of thinking to try and preserve simplicity, but I shot it dead very quickly before I allowed it beyond my brain ;) You talk about C++ rval references as if they're complex, but they're really not. They're easily the best part of C++11. Their design is a great success, and it is very very well thrught through. Rejecting ideas from C++ just because they're in C++ is a bad basis for any decision; C++ has a VERY rigorous process for allowing language changes. Rval references weren't an initial fault in C++ early design, they were a recent language change, and required a torturous process to find their way in. I agree that it is language complexity, and if there's a way to avoid it, we should absolutely seek it out, but don't flatly disregard the state of the art. If we can't do BETTER than rval references, then we shouldn't NOT do rval references just because it's what C++ does (very successfully).
Sep 05 2019
On Thursday, 5 September 2019 at 21:31:59 UTC, Manu wrote:We lose by-val calling semantics, which are more efficient for small struct's (most things), and certain classes of wide-registers in various architectures (impossible to codify the proper rules in the language).No, I've explicitly stated that this obviously only affects non-PODs and large PODs. On Win64, `large` is already anything > 8 bytes. Of course we don't want to pass an int by ref.You talk about C++ rval references as if they're complex, but they're really not.For people with C++ background they probably aren't. I doubt [And a great design IMO doesn't include `T&&` meaning different things for a templated function and a regular function, but that's off-topic.]
Sep 05 2019
On Thu, Sep 5, 2019 at 2:55 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 5 September 2019 at 21:31:59 UTC, Manu wrote:You dismissed the second half of my sentence.We lose by-val calling semantics, which are more efficient for small struct's (most things), and certain classes of wide-registers in various architectures (impossible to codify the proper rules in the language).No, I've explicitly stated that this obviously only affects non-PODs and large PODs. On Win64, `large` is already anything > 8 bytes. Of course we don't want to pass an int by ref.You don't need to use this. `this(T byVal)` is a valid move constructor if you don't care for the complexity. But in the event you DO want or need this, then it should be comprehensive, not compromised.You talk about C++ rval references as if they're complex, but they're really not.For people with C++ background they probably aren't. I doubt[And a great design IMO doesn't include `T&&` meaning different things for a templated function and a regular function, but that's off-topic.]We have `auto ref`, and I expect that would express that case in a more clear way.
Sep 05 2019
On Thursday, 5 September 2019 at 22:28:44 UTC, Manu wrote:On Thu, Sep 5, 2019 at 2:55 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:Didn't seem relevant, as these are all ABI details, and I am aware of this. My generalization wrt. > 8 bytes on Win64 wasn't totally accurate; vectors > 8 bytes are indeed passed in a vector register. [I have implemented this for LDC - still not fully 100% __vectorcall compatible.]On Thursday, 5 September 2019 at 21:31:59 UTC, Manu wrote:You dismissed the second half of my sentence.We lose by-val calling semantics, which are more efficient for small struct's (most things), and certain classes of wide-registers in various architectures (impossible to codify the proper rules in the language).No, I've explicitly stated that this obviously only affects non-PODs and large PODs. On Win64, `large` is already anything8 bytes. Of course we don't want to pass an int by ref.We have `auto ref`, and I expect that would express that case in a more clear way.Yes, definitely better. Brings me to this further refinement of my sketch, staying with Suleyman's recursive function: void foo()(auto ref S value, int n = 0) { if (n > 32) return; // forwarding `auto ref` params in the value case could be optimized to // 'just forward the [rvalue] ref, don't destruct and reset to T.init afterwards' foo(forward(value), n + 1); // still calls the value version of foo } void main() { S lvalue; foo(move(lvalue)); // pass rvalue directly by ref, then destruct and reset to S.init } => 2 destructions, 1 reset to S.init.
Sep 05 2019
On Thursday, 5 September 2019 at 22:57:27 UTC, kinke wrote:[...]Seems workable. It only affects the case of moving an lvalue to an value parameter. But here are my observations: 1. `move` should always do what `forward` did in your example. It shouldn't touch the lvalue, for simply removing a call to the move constructor, because T.init is not always considered a valid state and resetting to T.init is a "move" operation but an uncontrolled one which leaves the object in an invalid state. Which why making __move behave just like rvalue ref is more sensible like your first attempt did. Ex: ``` S lvalue; lvalue.i = 1; foo(__move(lvalue)); assert(lvalue.i == ??); // could be still 1 if wasn't consumed inside foo. this is valid behavior with rvalue ref. ``` This behavior is better than swapping lvalue with T.init. 2. What you have proposed so far only replaces rvalue ref parameters. You haven't tackled rvalue ref returns. What do you propose as an alternative. Ex: ``` rvalue ref get( rvalue ref S arg) { return arg; } // receives pointer and returns it void foo( rvalue ref S); S lvalue; foo(get(lvalue)); // no copy or move happens only pointers being passed ``` What is your alternative for this.
Sep 06 2019
On Friday, 6 September 2019 at 15:18:24 UTC, Suleyman wrote:On Thursday, 5 September 2019 at 22:57:27 UTC, kinke wrote:The problem here is the extended lifetime of the by-value parameter. Example: T global; void foo(T param) { /* make T own a huge buffer */ } void caller() { foo(move(global)); // without destructing and resetting to T.init, `param` continues to live // and keeps the huge buffer alive, until `global` is reassigned or destructed }[...]Seems workable. It only affects the case of moving an lvalue to an value parameter. But here are my observations: 1. `move` should always do what `forward` did in your example. It shouldn't touch the lvalue, for simply removing a call to the move constructor, because T.init is not always considered a valid state and resetting to T.init is a "move" operation but an uncontrolled one which leaves the object in an invalid state. Which why making __move behave just like rvalue ref is more sensible like your first attempt did.2. What you have proposed so far only replaces rvalue ref parameters. You haven't tackled rvalue ref returns. What do you propose as an alternative.I never thought about that, I don't think I've ever returned an rvalue ref in C++. Give me some time to think about it.
Sep 06 2019
On Fri, Sep 6, 2019 at 11:00 AM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Friday, 6 September 2019 at 15:18:24 UTC, Suleyman wrote:There's another case where you can attribute a method with ` rvalue` to apply to the `this` reference. This should naturally work in D under the ` rvalue` proposal. Ie: struct S { Thing member; ref Thing fun() { return member; } Thing fun() rvalue { return move(member); } } This can be very valuable, and even more so in D where we use a lot of UFCS chains.On Thursday, 5 September 2019 at 22:57:27 UTC, kinke wrote:The problem here is the extended lifetime of the by-value parameter. Example: T global; void foo(T param) { /* make T own a huge buffer */ } void caller() { foo(move(global)); // without destructing and resetting to T.init, `param` continues to live // and keeps the huge buffer alive, until `global` is reassigned or destructed }[...]Seems workable. It only affects the case of moving an lvalue to an value parameter. But here are my observations: 1. `move` should always do what `forward` did in your example. It shouldn't touch the lvalue, for simply removing a call to the move constructor, because T.init is not always considered a valid state and resetting to T.init is a "move" operation but an uncontrolled one which leaves the object in an invalid state. Which why making __move behave just like rvalue ref is more sensible like your first attempt did.2. What you have proposed so far only replaces rvalue ref parameters. You haven't tackled rvalue ref returns. What do you propose as an alternative.I never thought about that, I don't think I've ever returned an rvalue ref in C++. Give me some time to think about it.
Sep 06 2019
On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:There's another case where you can attribute a method with ` rvalue` to apply to the `this` reference. This should naturally work in D under the ` rvalue` proposal. Ie: struct S { Thing member; ref Thing fun() { return member; } Thing fun() rvalue { return move(member); } } This can be very valuable, and even more so in D where we use a lot of UFCS chains.Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness): struct S { Thing member; ref Thing fun() { return member; } rvalue ref Thing fun() rvalue { return member; } // maybe we could even represent both at once via something like: // auto ref Thing fun()() { return member; } }
Sep 06 2019
On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:I'm sorry but this looks like a bad idea. Moving selective parts of S simply because it's all marked for move is bad. What happens when the member is moved separately thus leaving an "empty" spot inside S then S itself is moved. That would result is a half valid object.There's another case where you can attribute a method with ` rvalue` to apply to the `this` reference. This should naturally work in D under the ` rvalue` proposal. Ie: struct S { Thing member; ref Thing fun() { return member; } Thing fun() rvalue { return move(member); } } This can be very valuable, and even more so in D where we use a lot of UFCS chains.Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness): struct S { Thing member; ref Thing fun() { return member; } rvalue ref Thing fun() rvalue { return member; } // maybe we could even represent both at once via something like: // auto ref Thing fun()() { return member; } }
Sep 06 2019
On Fri, Sep 6, 2019 at 2:15 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:S is an rvalue; there are no other references to S. That's the point.On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:I'm sorry but this looks like a bad idea. Moving selective parts of S simply because it's all marked for move is bad. What happens when the member is moved separately thus leaving an "empty" spot inside S then S itself is moved. That would result is a half valid object.There's another case where you can attribute a method with ` rvalue` to apply to the `this` reference. This should naturally work in D under the ` rvalue` proposal. Ie: struct S { Thing member; ref Thing fun() { return member; } Thing fun() rvalue { return move(member); } } This can be very valuable, and even more so in D where we use a lot of UFCS chains.Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness): struct S { Thing member; ref Thing fun() { return member; } rvalue ref Thing fun() rvalue { return member; } // maybe we could even represent both at once via something like: // auto ref Thing fun()() { return member; } }
Sep 06 2019
On Friday, 6 September 2019 at 22:32:55 UTC, Manu wrote:S is an rvalue; there are no other references to S. That's the point.With rvalue ref there is a reference. That's the point of rvalue ref passing rvalue around by reference.
Sep 06 2019
On Fri, Sep 6, 2019 at 3:55 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Friday, 6 September 2019 at 22:32:55 UTC, Manu wrote:The whole point is to destroy the source value though, because there's no external reference to it. If you receive an rvalue ref, it's yours to do as you like with it.S is an rvalue; there are no other references to S. That's the point.With rvalue ref there is a reference. That's the point of rvalue ref passing rvalue around by reference.
Sep 06 2019
On Saturday, 7 September 2019 at 06:03:38 UTC, Manu wrote:On Fri, Sep 6, 2019 at 3:55 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Yes you can do anything including rendering the object invalid and even without this feature. You don't need this feature because it generally doesn't make sense and you can do without it.On Friday, 6 September 2019 at 22:32:55 UTC, Manu wrote:The whole point is to destroy the source value though, because there's no external reference to it. If you receive an rvalue ref, it's yours to do as you like with it.S is an rvalue; there are no other references to S. That's the point.With rvalue ref there is a reference. That's the point of rvalue ref passing rvalue around by reference.
Sep 07 2019
On Sat, Sep 7, 2019 at 1:30 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Saturday, 7 September 2019 at 06:03:38 UTC, Manu wrote:I don't know what you mean. It absolutely makes sense, and it's very useful.On Fri, Sep 6, 2019 at 3:55 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Yes you can do anything including rendering the object invalid and even without this feature. You don't need this feature because it generally doesn't make sense and you can do without it.On Friday, 6 September 2019 at 22:32:55 UTC, Manu wrote:The whole point is to destroy the source value though, because there's no external reference to it. If you receive an rvalue ref, it's yours to do as you like with it.S is an rvalue; there are no other references to S. That's the point.With rvalue ref there is a reference. That's the point of rvalue ref passing rvalue around by reference.
Sep 07 2019
On Saturday, 7 September 2019 at 20:39:19 UTC, Manu wrote:I don't know what you mean. It absolutely makes sense, and it's very useful.Moving an object doesn't means it for dump it just means change it's memory location, the object is still expected to be fully functional.
Sep 07 2019
On Fri, Sep 6, 2019 at 3:32 PM Manu <turkeyman gmail.com> wrote:On Fri, Sep 6, 2019 at 2:15 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:This doesn't come up very often, but when you need it, it has been a massive performance opportunity for us.On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:S is an rvalue; there are no other references to S. That's the point.On Friday, 6 September 2019 at 19:25:57 UTC, Manu wrote:I'm sorry but this looks like a bad idea. Moving selective parts of S simply because it's all marked for move is bad. What happens when the member is moved separately thus leaving an "empty" spot inside S then S itself is moved. That would result is a half valid object.There's another case where you can attribute a method with ` rvalue` to apply to the `this` reference. This should naturally work in D under the ` rvalue` proposal. Ie: struct S { Thing member; ref Thing fun() { return member; } Thing fun() rvalue { return move(member); } } This can be very valuable, and even more so in D where we use a lot of UFCS chains.Oh wow, this might be able to convince me, as returning an rvalue ref from a method invoked on an rvalue instance would allow absolutely perfect forwarding of members (no copy/move and preserving rvalue-ness): struct S { Thing member; ref Thing fun() { return member; } rvalue ref Thing fun() rvalue { return member; } // maybe we could even represent both at once via something like: // auto ref Thing fun()() { return member; } }
Sep 06 2019
On Friday, 6 September 2019 at 17:59:11 UTC, kinke wrote:The problem here is the extended lifetime of the by-value parameter. Example: T global; void foo(T param) { /* make T own a huge buffer */ } void caller() { foo(move(global)); // without destructing and resetting to T.init, `param` continues to live // and keeps the huge buffer alive, until `global` is reassigned or destructed }I see no problem with that. That is valid behavior with rvalue ref. You're trying to get the benefit of rvalue ref you can't get the full benefit without the draw backs. There is no in middle solution, either pass by ref or call the move constructor.
Sep 06 2019
On Friday, 6 September 2019 at 20:24:24 UTC, Suleyman wrote:On Friday, 6 September 2019 at 17:59:11 UTC, kinke wrote:I never meant to make such a huge breaking change to existing semantics (by-val param possibly not destructed when going out of scope), just to optimize passing moved lvalues to by-value params by only doing an implicit 'half move' (no allocated temporary, no move ctor call, just destruct and then reset to T.init). I'm not sure this 'middle solution' is really feasible either, that's why we are discussing. Wrt. rvalue ref params and return 'values', maybe we could get away without introducing ` rvalue ref` by changing the current `auto ref` semantics for params (and return 'values') to what you proposed - an l/rvalue ref, i.e., a reference in both cases, just annotated with rvalue-ness, and never a value anymore. Then, your exampleThe problem here is the extended lifetime of the by-value parameter. Example: T global; void foo(T param) { /* make `param` own a huge buffer */ } void caller() { foo(move(global)); // without destructing and resetting to T.init, `param` continues to live // and keeps the huge buffer alive, until `global` is reassigned or destructed }I see no problem with that. That is valid behavior with rvalue ref. You're trying to get the benefit of rvalue ref you can't get the full benefit without the draw backs. There is no in middle solution, either pass by ref or call the move constructor.rvalue ref get( rvalue ref S arg) { return arg; } void foo( rvalue ref S);can be expressed as: ``` auto ref get()(auto ref S arg) { return forward(arg); } void foo()(auto ref S s); ``` In the (unlikely?) case you really only want to be called with an rvalue ref, you could use a template constraint like ``` void foo()(auto ref S s) if (__traits(isRvalueRef, s)); ``` The semantics of an `auto ref` param would still need to be clarified in the rvalue case - is it seen as an rvalue ref (breaking change!) or as a value? I.e.: ``` void sink(S s); void seenAsRvalueRef()(auto ref S s) { // considering rvalue case only: sink(s); // automatically moved sink(s); // automatically moved again (but probably reset to S.init above) } void seenAsValue()(auto ref S s) { sink(s); // passed by value, i.e., copy sink(forward(s)); // explicitly moved } ``` I'd prefer the latter value-view, as that's compatible with current semantics.
Sep 07 2019
On Saturday, 7 September 2019 at 12:04:49 UTC, kinke wrote:Wrt. rvalue ref params and return 'values', maybe we could get away without introducing ` rvalue ref` by changing the current `auto ref` semantics for params (and return 'values') to what you proposed - an l/rvalue ref, i.e., a reference in both cases, just annotated with rvalue-ness, and never a value anymore. [...]In C++ terms: struct S { S(S&&); // D: moveThis(ref S); S& operator=(S&&); // D: ref S opMoveAssign(ref S); } // universal reference: template <typename T> void foo(T&&); // D: void foo(T)(auto ref T); // rvalue reference: void bar(S&&); // D: void bar()(auto ref S s) if (__traits(isRvalueRef, s)); move/forward working just like in C++, but intrinsics.
Sep 07 2019
On Saturday, 7 September 2019 at 12:04:49 UTC, kinke wrote:I never meant to make such a huge breaking change to existing semanticsI thought what you proposed only affected the output of the __move intrinsic. The __move intrinsic doesn't exist in the language yet so there is no existing semantics affected.
Sep 07 2019
On Saturday, 7 September 2019 at 12:04:49 UTC, kinke wrote:``` void foo()(auto ref S s) if (__traits(isRvalueRef, s)); ```This is a really just an awkward way of doing `void foo( rvalue ref S s)`.I'd prefer the latter value-view, as that's compatible with current semantics.At this point you have essentially admitted rvalue ref in the language but you're fighting to keep it hidden from the user as much as possible. I personally don't thing rvalue ref is that much dangerous to be kept away from the programmers hand. Hiding rvalue ref under auto ref is simply discouraging people from using it.
Sep 07 2019
On Saturday, 7 September 2019 at 21:09:45 UTC, Suleyman wrote:On Saturday, 7 September 2019 at 12:04:49 UTC, kinke wrote:Yes.``` void foo()(auto ref S s) if (__traits(isRvalueRef, s)); ```This is a really just an awkward way of doing `void foo( rvalue ref S s)`.I'd prefer the latter value-view, as that's compatible with current semantics.At this point you have essentially admitted rvalue ref in the language but you're fighting to keep it hidden from the user as much as possible.I personally don't thing rvalue ref is that much dangerous to be kept away from the programmers hand. Hiding rvalue ref under auto ref is simply discouraging people from using it.It's not because of dangerousness or such, but firstly because I think pure rvalue refs (disallowing lvalue args) are used extremely seldomly, and secondly because I want to avoid further syntactic complexity. We have ref, auto ref, scope ref, return ref, now we're discussing rvalue ref...
Sep 07 2019
On Saturday, 7 September 2019 at 21:41:16 UTC, kinke wrote:It's not because of dangerousness or such, but firstly because I think pure rvalue refs (disallowing lvalue args) are used extremely seldomly, and secondly because I want to avoid further syntactic complexity. We have ref, auto ref, scope ref, return ref, now we're discussing rvalue ref...That still doesn't justify hiding it under auto ref. If it is rarely used then even if you add the attribute to the language it will be rarely used. Instead of explaining the new rvalue keyword, we will be explaining the unnamed auto ref expansion. There is a difference between being naturally rarely used and inconveniencing people off of it. When you have a bloat of names then fix the naming conventions. Hiding this under existing stuff like auto ref is like brushing it under the carpet.
Sep 07 2019
On Sat, Sep 7, 2019 at 2:45 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Saturday, 7 September 2019 at 21:09:45 UTC, Suleyman wrote:My experience with D has shown me time and time again, when something exists in the language that has no actual language to interact with it, that is the *real* complexity in D. Everything that sucks the most in D is the things that have no proper expression in the language. D is a power-user language, and that ship sailed a very long time ago. We still have D's current default move semantics, and they will not disappear; your average user will not be affected by this. For the power user that needs it though, direct expression is *MUCH* simpler than making them jump through hoops to interact with a language feature indirectly. We've done that a lot before, and it's a disaster every single time.On Saturday, 7 September 2019 at 12:04:49 UTC, kinke wrote:Yes.``` void foo()(auto ref S s) if (__traits(isRvalueRef, s)); ```This is a really just an awkward way of doing `void foo( rvalue ref S s)`.I'd prefer the latter value-view, as that's compatible with current semantics.At this point you have essentially admitted rvalue ref in the language but you're fighting to keep it hidden from the user as much as possible.I personally don't thing rvalue ref is that much dangerous to be kept away from the programmers hand. Hiding rvalue ref under auto ref is simply discouraging people from using it.It's not because of dangerousness or such, but firstly because I think pure rvalue refs (disallowing lvalue args) are used extremely seldomly, and secondly because I want to avoid further syntactic complexity. We have ref, auto ref, scope ref, return ref, now we're discussing rvalue ref...
Sep 07 2019
On Sat, Sep 7, 2019 at 2:45 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:We have ref, auto ref, scope ref, return ref, now we're discussing rvalue ref...These concepts are mostly orthogonal. `auto ref` is an umbrella over `ref` and TBD `rvalue ref`. `scope` is orthogonal, and `return` too, although that's slightly related to `scope`. These things work together, but their interaction is not complexity; the fact they interact naturally is a consequence of simple design. Complex design is when they DON'T interact naturally, and edge cases emerge or hard-coded interactions which are individually speced exist. We haven't shown that here. As I see it, the conversation that's avoiding ` rvalue ref` is the stuff that's just trying to work semantics into special-case rule definitions, whereas ` rvalue ref` is the purest definition that happens to exist orthogonally to all this stuff, and naturally interacts with existing language to produce all the desired semantics.
Sep 07 2019
On Saturday, 7 September 2019 at 21:41:16 UTC, kinke wrote:We have ref, auto ref, scope ref, return ref, now we're discussing rvalue ref...At this point we should consider having an rvalue pointer type. We have concluded that preserving the rvalueness of a ref (which is simply a pointer) is valuable, we could take it further and save the rvalueness in pointer types as well. I find it strange that you cant declare a pointer to an rvalue ref in C++ for example `int&&*`.
Sep 07 2019
On Sat, Sep 7, 2019 at 5:50 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Saturday, 7 September 2019 at 21:41:16 UTC, kinke wrote:I think the reason is that, in C++, pointers would be eliminated from the language if they could get away with the deviation from legacy. In C++, ref's are part of the type, and this works immeasurably better than this 'storage class' idea we have in D. So when they added rval ref's, they extended the expression for pointers that they would prefer you invest in, which is references in general. I think the advantage of using references for rvalues, is that you can't assign references, you can't store them off somewhere, which is the implication that you take from being handed a pointer. If we introduce rval pointers, then you'd be able to assign them, make struct members, all that stuff, which you can't do with ref's. I find it a bit weird to think about an rvalue pointer, because the natural conclusion is that I can create one on the stack and freely assign to/from it, or as a member of a struct... that gets strange very quickly.We have ref, auto ref, scope ref, return ref, now we're discussing rvalue ref...At this point we should consider having an rvalue pointer type. We have concluded that preserving the rvalueness of a ref (which is simply a pointer) is valuable, we could take it further and save the rvalueness in pointer types as well. I find it strange that you cant declare a pointer to an rvalue ref in C++ for example `int&&*`.
Sep 07 2019
On Sunday, 8 September 2019 at 01:04:35 UTC, Manu wrote:I think the reason is that, in C++, pointers would be eliminated from the language if they could get away with the deviation from legacy. In C++, ref's are part of the type, and this works immeasurably better than this 'storage class' idea we have in D. So when they added rval ref's, they extended the expression for pointers that they would prefer you invest in, which is references in general.There is pointer to pointer but there is no ref to ref. That is an unjustified limitation. Just because certain aspects of pointers can be replaced by doesn't mean ref is good and pointer is bad.I think the advantage of using references for rvalues, is that you can't assign references, you can't store them off somewhere, which is the implication that you take from being handed a pointer.I'm not sure what you mean. You certainly can store rvalue ref in C++ everywhere, even inside structs. And when you take a pointer it's just the same underground except that you lose type information unjustifiably.If we introduce rval pointers, then you'd be able to assign them, make struct members, all that stuff, which you can't do with ref's.You can already do all of that in C++ https://cpp.godbolt.org/z/ZfdOeuI find it a bit weird to think about an rvalue pointer, because the natural conclusion is that I can create one on the stack and freely assign to/from it, or as a member of a struct... that gets strange very quickly.Ref in C++ is unjustifiably limited. And saving rvalueness has nothing to do in the ref vs pointer battle.
Sep 07 2019
On Sat, Sep 7, 2019 at 6:35 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Sunday, 8 September 2019 at 01:04:35 UTC, Manu wrote:I understand where you're coming from, but this opens up an enormous can of worms and widens the scope of the conversation enormously... I think we have about a 15% chance of being successful with an rvalue ref proposal as is, and I feel like if we try and expand that to a type-constructor and make it applicable to pointers, we fall to around 0.01% change of success. I spend too much time and energy here as it is. I would advise against doing something that cuts your chances of success by 1000x, and I couldn't personally make the time to argue in favour :/I think the reason is that, in C++, pointers would be eliminated from the language if they could get away with the deviation from legacy. In C++, ref's are part of the type, and this works immeasurably better than this 'storage class' idea we have in D. So when they added rval ref's, they extended the expression for pointers that they would prefer you invest in, which is references in general.There is pointer to pointer but there is no ref to ref. That is an unjustified limitation. Just because certain aspects of pointers can be replaced by doesn't mean ref is good and pointer is bad.I think the advantage of using references for rvalues, is that you can't assign references, you can't store them off somewhere, which is the implication that you take from being handed a pointer.I'm not sure what you mean. You certainly can store rvalue ref in C++ everywhere, even inside structs. And when you take a pointer it's just the same underground except that you lose type information unjustifiably.If we introduce rval pointers, then you'd be able to assign them, make struct members, all that stuff, which you can't do with ref's.You can already do all of that in C++ https://cpp.godbolt.org/z/ZfdOeuI find it a bit weird to think about an rvalue pointer, because the natural conclusion is that I can create one on the stack and freely assign to/from it, or as a member of a struct... that gets strange very quickly.Ref in C++ is unjustifiably limited. And saving rvalueness has nothing to do in the ref vs pointer battle.
Sep 07 2019
On Sunday, 8 September 2019 at 01:46:11 UTC, Manu wrote:I understand where you're coming from, but this opens up an enormous can of worms and widens the scope of the conversation enormously... I think we have about a 15% chance of being successful with an rvalue ref proposal as is, and I feel like if we try and expand that to a type-constructor and make it applicable to pointers, we fall to around 0.01% change of success. I spend too much time and energy here as it is. I would advise against doing something that cuts your chances of success by 1000x, and I couldn't personally make the time to argue in favour :/I didn't intend to just copy C++ blindly. If we did we wouldn't have D we would still be using C++. If it's a complete solution it has more chance to make it in D. If it's just a knock off of C++ it probably won't.
Sep 07 2019
On Sunday, 8 September 2019 at 02:06:54 UTC, Suleyman wrote:On Sunday, 8 September 2019 at 01:46:11 UTC, Manu wrote:Copy constructors got in...[...]I didn't intend to just copy C++ blindly. If we did we wouldn't have D we would still be using C++. If it's a complete solution it has more chance to make it in D. If it's just a knock off of C++ it probably won't.
Sep 07 2019
On Sunday, 8 September 2019 at 02:56:00 UTC, Les De Ridder wrote:Copy constructors got in...Ideally the fact that copy constructors are build upon implicit constructors should have been considered. But I'm still fine with that. I don't think whether it is marked implicit or not should make a difference for copy and move constructors.
Sep 07 2019
On Sat, Sep 7, 2019 at 7:10 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Sunday, 8 September 2019 at 01:46:11 UTC, Manu wrote:Actually, why doesn't it naturally work with pointers? The same way `scope` applied to pointers or ref's or anything with references, shouldn't ` rvalue` have the same attribution semantics as `scope` for instance? I guess you're right; if it didn't naturally apply to pointers the same way `scope` does, it would be a weird kind of edge case. The application of the attribute should behave the same as `scope`.I understand where you're coming from, but this opens up an enormous can of worms and widens the scope of the conversation enormously... I think we have about a 15% chance of being successful with an rvalue ref proposal as is, and I feel like if we try and expand that to a type-constructor and make it applicable to pointers, we fall to around 0.01% change of success. I spend too much time and energy here as it is. I would advise against doing something that cuts your chances of success by 1000x, and I couldn't personally make the time to argue in favour :/I didn't intend to just copy C++ blindly. If we did we wouldn't have D we would still be using C++. If it's a complete solution it has more chance to make it in D. If it's just a knock off of C++ it probably won't.
Sep 07 2019
On Sunday, 8 September 2019 at 03:21:06 UTC, Manu wrote:Actually, why doesn't it naturally work with pointers? The same way `scope` applied to pointers or ref's or anything with references, shouldn't ` rvalue` have the same attribution semantics as `scope` for instance? I guess you're right; if it didn't naturally apply to pointers the same way `scope` does, it would be a weird kind of edge case. The application of the attribute should behave the same as `scope`.I didn't mean it should be like scope. I meant it should be like const an shared. For example the address of an rvalue ref is pointer to an rvalue type `is(typeof(&arg) == rvalue(S)*)` this way rvalueness is preserved when the adress is taken, thus you can have something like `rvalue(S)***`. Dereferencing such such pointers returns an rvalue ref thus automatically calls the move constructor instead of the copy constructor.
Sep 08 2019
On Sunday, 8 September 2019 at 18:49:51 UTC, Suleyman wrote:[...]Then having `cast(rvalue)` instead of the `__move` intrinsic would make perfect sense in the language.
Sep 08 2019
On Sun, Sep 8, 2019 at 12:05 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Sunday, 8 September 2019 at 18:49:51 UTC, Suleyman wrote:Right, you wanna make it a type constructor. This should be the case also for ref and scope too... but you will *never* get that past Walter. This thing you describe is the single biggest fundamental mistake in D, storage class is improperly used for a bunch of stuff. It's been killing me slowly for a very long time... and I'm confident after so many years that you will not succeed. Supporting cast( rvalue) doesn't depend on this anyway... you can make that expression work either way.[...]Then having `cast(rvalue)` instead of the `__move` intrinsic would make perfect sense in the language.
Sep 08 2019
On Monday, 9 September 2019 at 00:28:17 UTC, Manu wrote:Right, you wanna make it a type constructor. This should be the case also for ref and scope too... but you will *never* get that past Walter. This thing you describe is the single biggest fundamental mistake in D, storage class is improperly used for a bunch of stuff. It's been killing me slowly for a very long time... and I'm confident after so many years that you will not succeed. Supporting cast( rvalue) doesn't depend on this anyway... you can make that expression work either way.I'm not bothered too much with ref as long as we have solid pointer types. scope is a compiler hook not that much useful to the programmer.
Sep 08 2019
On Sun, Sep 8, 2019 at 5:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Monday, 9 September 2019 at 00:28:17 UTC, Manu wrote:It is though, scope has all the problems ref does, except they're worse because there's no language to deal with them at all. For instance, perfect forwarding can't deal with scope. We have talked about `auto ref` being able to address the perfect forwarding issue with ` rvalue ref`, but `scope` and `return` has never had any kind of solution.Right, you wanna make it a type constructor. This should be the case also for ref and scope too... but you will *never* get that past Walter. This thing you describe is the single biggest fundamental mistake in D, storage class is improperly used for a bunch of stuff. It's been killing me slowly for a very long time... and I'm confident after so many years that you will not succeed. Supporting cast( rvalue) doesn't depend on this anyway... you can make that expression work either way.I'm not bothered too much with ref as long as we have solid pointer types. scope is a compiler hook not that much useful to the programmer.
Sep 08 2019
On Monday, 9 September 2019 at 01:09:52 UTC, Manu wrote:[...] For instance, perfect forwarding can't deal with scope. We have talked about `auto ref` being able to address the perfect forwarding issue with ` rvalue ref`, but `scope` and `return` has never had any kind of solution.Why should there be a solution? Lvaluness is overloadable in D, and with rvalue ref rvaluness also becomes overloadable. scope is not overloadable. Ex: ``` void foo(return scope ref int i) {} void foo(ref int i) {} void main() { int i; foo(&i); // error: ambiguous call } ```
Sep 08 2019
On Monday, 9 September 2019 at 01:25:18 UTC, Suleyman wrote:[...] foo(&i); // error: ambiguous callA little mistake there, it should be `foo(i)`.
Sep 08 2019
On Sun, Sep 8, 2019 at 6:30 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Monday, 9 September 2019 at 01:25:18 UTC, Suleyman wrote:I mean there's no way for a template parameter to take the scope-ness of its argument... although now I think on it, inference should probably help out there. It's okay because it doesn't change the calling convention like ref does, that's the real problem. Well, is it particularly hard to try your plan? I guess you might as well do it, and then see what happens if you don't think it's too much work...[...] foo(&i); // error: ambiguous callA little mistake there, it should be `foo(i)`.
Sep 08 2019
On Sunday, 8 September 2019 at 00:46:22 UTC, Suleyman wrote:At this point we should consider having an rvalue pointer type. We have concluded that preserving the rvalueness of a ref (which is simply a pointer) is valuable, we could take it further and save the rvalueness in pointer types as well. I find it strange that you cant declare a pointer to an rvalue ref in C++ for example `int&&*`.This can be achieved with either an rvalue type modifier like const and shared or with a type suffix like &. Then parameters could be declared as a ref or a pointer to an rvalue'ed type.
Sep 07 2019
On Thu, Sep 5, 2019 at 4:00 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 5 September 2019 at 22:28:44 UTC, Manu wrote:It's relevant because the calling convention needs to be defined, and it's volatile with respect to architecture. I don't think defining a custom ABI this way is a good path. It's just different problems, and ABI problems are always bad problems.On Thu, Sep 5, 2019 at 2:55 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:Didn't seem relevant, as these are all ABI details, and I am aware of this. My generalization wrt. > 8 bytes on Win64 wasn't totally accurate; vectors > 8 bytes are indeed passed in a vector register. [I have implemented this for LDC - still not fully 100% __vectorcall compatible.]On Thursday, 5 September 2019 at 21:31:59 UTC, Manu wrote:You dismissed the second half of my sentence.We lose by-val calling semantics, which are more efficient for small struct's (most things), and certain classes of wide-registers in various architectures (impossible to codify the proper rules in the language).No, I've explicitly stated that this obviously only affects non-PODs and large PODs. On Win64, `large` is already anything8 bytes. Of course we don't want to pass an int by ref.
Sep 06 2019
On Friday, 6 September 2019 at 17:46:51 UTC, Manu wrote:It's relevant because the calling convention needs to be defined, and it's volatile with respect to architecture. I don't think defining a custom ABI this way is a good path. It's just different problems, and ABI problems are always bad problems.It'd just make the D ABI conform to the C++ ABI wrt. passing non-PODs and large PODs by value, making the compilers adhere to the D spec *and* fixing C++ interop when passing such structs by value across language barriers, soomething you're surely looking forward too. So the proposed ABI change (pass by ref instead of on stack & let callER destruct it) is the exact opposite of what you seem to see it as - it's streamlining the D ABI with C++. And it's orthogonal to the proposed move/forward intrinsics.
Sep 06 2019
On Friday, 6 September 2019 at 18:13:03 UTC, kinke wrote:It'd just make the D ABI conform to the C++ ABI wrt. passing non-PODs and large PODs by value, making the compilers adhere to the D spec *and* fixing C++ interop when passing such structs by value across language barriers, soomething you're surely looking forward too. So the proposed ABI change (pass by ref instead of on stack & let callER destruct it) is the exact opposite of what you seem to see it as - it's streamlining the D ABI with C++. And it's orthogonal to the proposed move/forward intrinsics.Dammit, I stand corrected again, just checked the clang ABI on Posix x86_64 - it does pass large PODs on the stack. So please discard my mentionings of large PODs (those are passed by ref on Win64...) in this thread, my intention was always to firstly adopt the respective C++ ABI for each target, and secondly to exploit the low-level-by-ref-passing of non-PODs (AFAIK, what C++ does for all targets) when moving lvalue arguments to by-value params (by eliding the temporary and passing the lvalue ref directly), thirdly hoping to cover enough use cases in order NOT to have to introduce rvalue refs in the language.
Sep 06 2019
On Fri, Sep 6, 2019 at 11:45 AM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Friday, 6 September 2019 at 18:13:03 UTC, kinke wrote:'enough' is a subjective quantity; and I respect an effort to explore avoiding rval ref's, but also don't be xenophobic about it. It's a really good solution, and if we don't arrive at a distinctly better one, then I think we should accept the state of the art, and not do something different for the sake of doing something different. I've thought about this a lot, I personally think C++'s rval ref's were really well thought through, and I can't imagine a simple/better design. I also see that the only major oddity in C++ (T&&, so called, universal references) are addressed by `auto ref`, which feels much less surprising and more obvious for users to me. My email way up the feed listing all the cases feels robust to me. There's synergy with Andrei's `-preview`, and there are no changes to existing semantics, it's purely additive.It'd just make the D ABI conform to the C++ ABI wrt. passing non-PODs and large PODs by value, making the compilers adhere to the D spec *and* fixing C++ interop when passing such structs by value across language barriers, soomething you're surely looking forward too. So the proposed ABI change (pass by ref instead of on stack & let callER destruct it) is the exact opposite of what you seem to see it as - it's streamlining the D ABI with C++. And it's orthogonal to the proposed move/forward intrinsics.Dammit, I stand corrected again, just checked the clang ABI on Posix x86_64 - it does pass large PODs on the stack. So please discard my mentionings of large PODs (those are passed by ref on Win64...) in this thread, my intention was always to firstly adopt the respective C++ ABI for each target, and secondly to exploit the low-level-by-ref-passing of non-PODs (AFAIK, what C++ does for all targets) when moving lvalue arguments to by-value params (by eliding the temporary and passing the lvalue ref directly), thirdly hoping to cover enough use cases in order NOT to have to introduce rvalue refs in the language.
Sep 06 2019
On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:The whole program only calls the destructor for the lvalue, and only once. You need a competitive alternative.Sry, I haven't focused on that - yes, rvalue refs would be even faster because of elided destruction + T.init reset. For hard-core guys wanting to optimize that away, I'd suggest to just use a regular ref in the function signature and enable `-preview=rvaluerefparam`. A regular ref (combined with either pragma(mangle) tricks or some UDA for C++ mangling) could also be used to represent C++ functions with rvalue refs.
Sep 05 2019
On Thu, Sep 5, 2019 at 2:40 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:Please no. This is a terrible series of sentences >_< `-preview=rvaluerefparam` naturally interacts benefitially with the proposed ` rvalue` attribute. Don't mess with that.The whole program only calls the destructor for the lvalue, and only once. You need a competitive alternative.Sry, I haven't focused on that - yes, rvalue refs would be even faster because of elided destruction + T.init reset. For hard-core guys wanting to optimize that away, I'd suggest to just use a regular ref in the function signature and enable `-preview=rvaluerefparam`. A regular ref (combined with either pragma(mangle) tricks or some UDA for C++ mangling) could also be used to represent C++ functions with rvalue refs.
Sep 05 2019
On Thursday, 5 September 2019 at 21:45:10 UTC, Manu wrote:On Thu, Sep 5, 2019 at 2:40 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:Care to elaborate? Suleyman's example can be slightly adapted to this: void foo(ref S value, int n = 0) { if (n > 32) return; foo(value, n + 1); } With `-preview=rvaluerefparam`, we can feed it with an rvalue too, and get exactly the same behavior as Suleyman's original version, without ` rvalue ref` and the need to move the `value` parameter for the recursive call.On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:Please no. This is a terrible series of sentences >_< `-preview=rvaluerefparam` naturally interacts benefitially with the proposed ` rvalue` attribute. Don't mess with that.The whole program only calls the destructor for the lvalue, and only once. You need a competitive alternative.Sry, I haven't focused on that - yes, rvalue refs would be even faster because of elided destruction + T.init reset. For hard-core guys wanting to optimize that away, I'd suggest to just use a regular ref in the function signature and enable `-preview=rvaluerefparam`. A regular ref (combined with either pragma(mangle) tricks or some UDA for C++ mangling) could also be used to represent C++ functions with rvalue refs.
Sep 05 2019
On Thu, Sep 5, 2019 at 3:25 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 5 September 2019 at 21:45:10 UTC, Manu wrote:Without that preview, you can't pass an rvalue temporary to a ref at all, so it's a necessary piece of passing rvalues by references.On Thu, Sep 5, 2019 at 2:40 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:Care to elaborate?On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:Please no. This is a terrible series of sentences >_< `-preview=rvaluerefparam` naturally interacts benefitially with the proposed ` rvalue` attribute. Don't mess with that.The whole program only calls the destructor for the lvalue, and only once. You need a competitive alternative.Sry, I haven't focused on that - yes, rvalue refs would be even faster because of elided destruction + T.init reset. For hard-core guys wanting to optimize that away, I'd suggest to just use a regular ref in the function signature and enable `-preview=rvaluerefparam`. A regular ref (combined with either pragma(mangle) tricks or some UDA for C++ mangling) could also be used to represent C++ functions with rvalue refs.Suleyman's example can be slightly adapted to this: void foo(ref S value, int n = 0) { if (n > 32) return; foo(value, n + 1); } With `-preview=rvaluerefparam`, we can feed it with an rvalue too, and get exactly the same behavior as Suleyman's original version, without ` rvalue ref` and the need to move the `value` parameter for the recursive call.If you lose the ` rvalue` attribute, you have no idea that you can steal the contents of `value`... it's not a move argument, it's just an lvalue argument by ref. Inside that function, you must copy, because you can't know what you received.
Sep 05 2019
On Thursday, 5 September 2019 at 22:31:41 UTC, Manu wrote:If you lose the ` rvalue` attribute, you have no idea that you can steal the contents of `value`... it's not a move argument, it's just an lvalue argument by ref. Inside that function, you must copy, because you can't know what you received.+1 I didn't realize this until I pondered on Exil's argument. Then I realized why the "-preview=..." feature doesn't cover this case. Essentially `ref` doesn't preserve move information i.e. it doesn't tell you whether the source was an lvalue or an rvalue converted to a temporary. Hence you can't just move or `forward` at the bottom of the chain.
Sep 05 2019
On Thu, Sep 5, 2019 at 12:00 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 5 September 2019 at 18:46:59 UTC, Exil wrote:I agree the default might do that, but it's equally valid to do nothing (if destruction has no side-effects), or do swap() if you want to allow the naturally occurring destructor to tidy up, or various other strategies. If we want to allow these potentially efficient implementations, then perhaps we should mark moved lvalues with a bit saying they have been invalidated, and the compiler can complain about future references?There's still similar to the problem we have now. You're still doing a memcpy() each time with S.init.Resetting the moved-from instance to T.init or something similar is what you'd do in the move ctor anyway (besides blitting the previous contents into the new instance and maybe doing some more adjustments), and definitely what the default implementation of the move ctor would do.
Sep 05 2019
On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:How would it work with multi-function passing though? With a rvalue reference, you are effectively just passing around a reference, until the contents of the value are moved. So you can pass it through N functions and it won't ever to do a move/copy. Would you effectively be relying on the compiler to optimize out the un-necessary moves, or would they be unavoidable, as they effectively are now?I think you scored a valid point here. This is where having rvalue ref comes in handy. In C++ assigning an rvalue ref to an lvalue does move not copy. C++ example: ``` void fun(T&& arg) { T var = arg; // move not copy } ``` D: ``` void fun(ref T arg) { T var = arg; // copy not move } ``` Ref doesn't preserve move information about the source. If we go the ` rvalue ref` way then we can achieve something similar.
Sep 04 2019
On Wednesday, 4 September 2019 at 14:45:34 UTC, Suleyman wrote:I think you scored a valid point here. This is where having rvalue ref comes in handy. In C++ assigning an rvalue ref to an lvalue does move not copy. C++ example: ``` void fun(T&& arg) { T var = arg; // move not copy } ```Nope, this calls the copy ctor (https://godbolt.org/z/Ds1vmQ) without an explicit `T var = std::move(arg)`. This is part of what makes C++ rvalue refs difficult to grasp.
Sep 04 2019
On Wednesday, 4 September 2019 at 14:56:03 UTC, kinke wrote:On Wednesday, 4 September 2019 at 14:45:34 UTC, Suleyman wrote:Sorry I was mistaken. You're right. But still move information is preserved with rvalue ref and it's utilizable for achieving what Exil referred to. Ex: https://godbolt.org/z/dlesXb.I think you scored a valid point here. This is where having rvalue ref comes in handy. In C++ assigning an rvalue ref to an lvalue does move not copy. C++ example: ``` void fun(T&& arg) { T var = arg; // move not copy } ```Nope, this calls the copy ctor (https://godbolt.org/z/Ds1vmQ) without an explicit `T var = std::move(arg)`. This is part of what makes C++ rvalue refs difficult to grasp.
Sep 04 2019
On Wednesday, 4 September 2019 at 16:37:30 UTC, Suleyman wrote:On Wednesday, 4 September 2019 at 14:56:03 UTC, kinke wrote:Apparently that's why C++' `forward` only moves at the end of the chain. Unlike `auto ref` in D which has to move at each level all the way down to the target. C++ Ex: https://godbolt.org/z/S1wacd.On Wednesday, 4 September 2019 at 14:45:34 UTC, Suleyman wrote:Sorry I was mistaken. You're right. But still move information is preserved with rvalue ref and it's utilizable for achieving what Exil referred to. Ex: https://godbolt.org/z/dlesXb.I think you scored a valid point here. This is where having rvalue ref comes in handy. In C++ assigning an rvalue ref to an lvalue does move not copy. C++ example: ``` void fun(T&& arg) { T var = arg; // move not copy } ```Nope, this calls the copy ctor (https://godbolt.org/z/Ds1vmQ) without an explicit `T var = std::move(arg)`. This is part of what makes C++ rvalue refs difficult to grasp.
Sep 04 2019
On Wednesday, 4 September 2019 at 17:41:02 UTC, Suleyman wrote:[...]I expanded rvalue ref in the POC. It is now possible to have a implementation of `forward` based on rvalue ref. Example: ``` template forward(alias arg) { // rvalue ref enum isRvalue = __traits(isRvalueRef, arg) || !(__traits(isRef, arg) || __traits(isOut, arg) || __traits(isLazy, arg)); static if (isRvalue) property rvalue ref forward() { return __move(arg); } else alias forward = arg; } unittest { class C { import core.stdc.stdio : puts; static void foo(int n) { puts("foo value"); } static void foo(ref int n) { puts("foo ref"); } static void bar( rvalue ref int n) { puts("bar rvalue ref"); } static void bar(ref int n) { puts("bar ref"); } } void foo()(auto ref int x) { C.foo(forward!x); } void bar()(auto ref int x) { C.bar(forward!x); } int i; foo(1); foo(i); bar(1); bar(i); } ``` I don't see how we can achieve the same efficiency without rvalue ref.
Sep 04 2019
On Thursday, 5 September 2019 at 01:30:02 UTC, Suleyman wrote:[...]`auto ref` now expands to either `ref` or ` rvalue ref` (instead of value).
Sep 04 2019
On Tue, Sep 3, 2019 at 5:20 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:Move semantics aren't a feature of the move constructor, they are USED BY the move constructor. You can move an argument to any function. Consider a unique_ptr, which can only move. It would be impossible to pass a unique_ptr to any other function than the move constructor itself.On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Because just detecting move-construction/-assignment/'argument moval' can get away with special identifiers for constructor/assignment operator or some special move UDA, as Suleyman has proposed so far, instead of fully extending the language by rvalue refs, with mangling additions and new overload rules etc. It's clear that this would restrict C++ interop, but that's the point - do we want to adopt the C++ approach fully, or keep things simple & tidy at the expense of not being able to represent C++ functions with rvalue refs (except for move constructor and assignment op)?I'm still demanding a use case for rvalue ref other than for move semantics.That's it; move semantics. That's not a minor thing... Why?
Sep 03 2019
On Wednesday, 4 September 2019 at 00:58:44 UTC, Manu wrote:Move semantics aren't a feature of the move constructor, they are USED BY the move constructor. You can move an argument to any function. Consider a unique_ptr, which can only move. It would be impossible to pass a unique_ptr to any other function than the move constructor itself.If you can get the move constructor and move assignment in addition to an intrinsic function for moving lvalues to rvalues then you can do move semantics without rvalue ref. unique_ptr in C++ doesn't move itself magically. You have to eplicitly move it by calling `move()`. Example: https://cpp.godbolt.org/z/8jVONg. You can do that with the machinery provided in the POC without rvalue ref.
Sep 04 2019
On Wed, Sep 4, 2019 at 7:50 AM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Wednesday, 4 September 2019 at 00:58:44 UTC, Manu wrote:You misunderstood what I'm saying. I'm saying that it can't only be a ` move` constructor that can accept rval references, any argument should be able to be one. Functions may have multiple arguments, and some of them may be unique_ptr-like objects. If you accept by-value, and you use a move-constructor to construct the argument, then we're back at making a copy at every level of the callstack, and that's specifically what we need to avoid.Move semantics aren't a feature of the move constructor, they are USED BY the move constructor. You can move an argument to any function. Consider a unique_ptr, which can only move. It would be impossible to pass a unique_ptr to any other function than the move constructor itself.If you can get the move constructor and move assignment in addition to an intrinsic function for moving lvalues to rvalues then you can do move semantics without rvalue ref. unique_ptr in C++ doesn't move itself magically. You have to eplicitly move it by calling `move()`. Example: https://cpp.godbolt.org/z/8jVONg. You can do that with the machinery provided in the POC without rvalue ref.
Sep 04 2019
On Wednesday, 4 September 2019 at 18:43:08 UTC, Manu wrote:On Wed, Sep 4, 2019 at 7:50 AM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Nope, Suleyman is totally right here. Move constructors are in fact never ever used to construct a parameter from an argument. The only time a move ctor is called is for `auto var = std::move(otherVar)`. That's how C++ handles it, and that's how an improved D could handle it as well, without rvalue refs in the language. // high-level: by value // low-level: just gets 2 pointers to temporaries and directly manipulates those, // no copying or moving at all void func(T)(UniquePtr!T a, UniquePtr!T b); void foo(T)(UniquePtr!T a) // again, `a` is a ref to a temporary { // forward the `a` ref; construct a new UniquePtr and forward its address func(move(a), makeUnique!T()); } void bar(T)() { // Construct a new UniquePtr and forward its address to `foo`. // In the end, manipulating `a` in `func` will manipulate this temporary here, across 2 calls. foo(makeUnique!T()); } For this to work, UniquePtr wouldn't need a move ctor. This is just an ABI detail (and requires a `move` intrinsic, as stated multiple times already).On Wednesday, 4 September 2019 at 00:58:44 UTC, Manu wrote:You misunderstood what I'm saying. I'm saying that it can't only be a ` move` constructor that can accept rval references, any argument should be able to be one. Functions may have multiple arguments, and some of them may be unique_ptr-like objects. If you accept by-value, and you use a move-constructor to construct the argument, then we're back at making a copy at every level of the callstack, and that's specifically what we need to avoid.Move semantics aren't a feature of the move constructor, they are USED BY the move constructor. You can move an argument to any function. Consider a unique_ptr, which can only move. It would be impossible to pass a unique_ptr to any other function than the move constructor itself.If you can get the move constructor and move assignment in addition to an intrinsic function for moving lvalues to rvalues then you can do move semantics without rvalue ref. unique_ptr in C++ doesn't move itself magically. You have to eplicitly move it by calling `move()`. Example: https://cpp.godbolt.org/z/8jVONg. You can do that with the machinery provided in the POC without rvalue ref.
Sep 04 2019
On Wednesday, 4 September 2019 at 19:01:54 UTC, kinke wrote:Move constructors are in fact never ever used to construct a parameter from an argument. The only time a move ctor is called is for `auto var = std::move(otherVar)`. That's how C++ handles itI stand corrected, apologies, too much confidence in my human memory apparently. According to https://godbolt.org/z/yo2v_V, C++ does indeed move-construct a temporary if the parameter is a value, not an rvalue ref. So perfect forwarding in the sense of simply forwarding the ref really only works if all callees down the chain take rvalue refs, so that's the C++ way of preventing lots of moving (which is still a lot of copying). So take the code snippet above as example of how this really perfect forwarding could be done in D, without resorting to rvalue refs. The state of a moved/forwarded instance would be generally impredictable (don't assume it to be T.init, as it could have been manipulated by some callee), but that's probably not a big deal. It's rather that the destruction might be unexpectedly deferred and cause problems: void caller(S lval) { callee(move(lval)); // pass `lval` by reference under the hood assert(lval.x == y); // should not be accessed after moving // do some more } // `lval` alias `param` will be destructed now before returning void callee(S param) // gets ref to temporary or moved lvalue { param.x = y; } // nope, `param` is not destroyed right before or after the call!
Sep 04 2019
On Wed, Sep 4, 2019 at 12:05 PM kinke via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Wednesday, 4 September 2019 at 18:43:08 UTC, Manu wrote:No you've misunderstood me again. Your examples above show functions receiving arguments by value... That's not even on discussion here (is it? if it is, then we're having different conversations). Those arguments will be copied (via move or otherwise) to the argument values before the calls... but that's not moving something into the call, that's just move-copying an argument. If those functions called another function, they would just continue the copy-chain down the stack.On Wed, Sep 4, 2019 at 7:50 AM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:Nope, Suleyman is totally right here. Move constructors are in fact never ever used to construct a parameter from an argument. The only time a move ctor is called is for `auto var = std::move(otherVar)`. That's how C++ handles it, and that's how an improved D could handle it as well, without rvalue refs in the language. // high-level: by value // low-level: just gets 2 pointers to temporaries and directly manipulates those, // no copying or moving at all void func(T)(UniquePtr!T a, UniquePtr!T b); void foo(T)(UniquePtr!T a) // again, `a` is a ref to a temporary { // forward the `a` ref; construct a new UniquePtr and forward its address func(move(a), makeUnique!T()); } void bar(T)() { // Construct a new UniquePtr and forward its address to `foo`. // In the end, manipulating `a` in `func` will manipulate this temporary here, across 2 calls. foo(makeUnique!T()); } For this to work, UniquePtr wouldn't need a move ctor. This is just an ABI detail (and requires a `move` intrinsic, as stated multiple times already).On Wednesday, 4 September 2019 at 00:58:44 UTC, Manu wrote:You misunderstood what I'm saying. I'm saying that it can't only be a ` move` constructor that can accept rval references, any argument should be able to be one. Functions may have multiple arguments, and some of them may be unique_ptr-like objects. If you accept by-value, and you use a move-constructor to construct the argument, then we're back at making a copy at every level of the callstack, and that's specifically what we need to avoid.Move semantics aren't a feature of the move constructor, they are USED BY the move constructor. You can move an argument to any function. Consider a unique_ptr, which can only move. It would be impossible to pass a unique_ptr to any other function than the move constructor itself.If you can get the move constructor and move assignment in addition to an intrinsic function for moving lvalues to rvalues then you can do move semantics without rvalue ref. unique_ptr in C++ doesn't move itself magically. You have to eplicitly move it by calling `move()`. Example: https://cpp.godbolt.org/z/8jVONg. You can do that with the machinery provided in the POC without rvalue ref.
Sep 04 2019
On Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:We don't need to expose rvalue ref as a language feature to do move semantics. The example with the attribute ` move` works just fine. Onl the first parameter is an rvalue ref.On Tuesday, 3 September 2019 at 01:50:20 UTC, Manu wrote:That's it; move semantics. That's not a minor thing... Why?it can't be used on functions that take more than one argument.I'm still demanding a use case for rvalue ref other than for move semantics.
Sep 04 2019
On Wednesday, 4 September 2019 at 08:21:05 UTC, Suleyman wrote:On Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:I want to tell something to u, Manu, WB... "u do something wrong with lang" let's see C++ copy constructorOn Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-dPoint( const Point& rhs ) { ... } // const ref to A. that alllet's D copy constructorthis( ref return scope const Point rhs ) { ... } // const.. ref to.. WTF?I understand that u can explain every word in such construction but for others it looks like "3.14dec" - u see here PI and something.. decimal? why? what is this mean? ok. ask any Russian friend/familiar, he will tell u that this means "big fucked up" in rude form. wait! what?! how?! and same way I read/see copy constructors in D. ok. show such construction to somebody who knows about copy constructors but not D guru and ask him what does he think about it? another side is move constructor. C++ again:Data( Data&& rhs ) { ... } // even less chars than for copy.// && - either logical AND or move semantics. u can't mistake. D?this( rvalue Data rhs ) move { ... }3.14dec! it even doesn't like to copy constructor - related entities. most people I think come to D from C++ or they tried C++ and faced with something incomprehensible, ambiguous, complex. (it seems to me that the templates are too sophisticated, and in D templates are understandable with constraints it even easier.) and they come to D cuz many people said on any forums "try to use D that has advantages over <lang>". and people try to write easy entity like "Point" and see.. this - copy/move.. it looks like fraud. its not easy to understand, u just can not step into D by soft step, u should read half specification to understand clear, simple things that is not clear and looks not simple. please hold things easy. I don't know how, but keep a easy way.
Sep 04 2019
On Wednesday, 4 September 2019 at 09:22:09 UTC, a11e99z wrote:On Wednesday, 4 September 2019 at 08:21:05 UTC, Suleyman wrote:copy can beOn Tuesday, 3 September 2019 at 23:51:43 UTC, Manu wrote:On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-dthis( Point rhs ) copy { ... }move can bethis( Point rhs ) move { ... }or even without args cuz they are known (need to think about it cuz C++ supports slicing values and D want to interoperate with C++ code) human see construction:this() copy { ... } // do we really need this() here?and let compiler see here comfortable for it:this( ref return scope const Point rhs ) { ... }OT: as I told before D needs attrs that relates to the compiler. copy move nogc safe nodiscard ... and inline/ noinline as alias for "pragma( inline, val )" cuz meaning of this pragma is same as compiler attr but looks like something foreign/alien - again one meaning in 2+ unclear forms. in the future more than a dozen attributes will appear: inline is useful nodiscard is useful likely is useful and need to do something with attrs to cuz code likeinline nodiscard Complicated func( Args... ) nogc nothrow { .. }looks like a mess/crap ignore by eyes when see code for meaning. such(above) code u should read word by word, ur eye just can ignore any parts
Sep 04 2019
On Wednesday, 4 September 2019 at 09:22:09 UTC, a11e99z wrote:On Wednesday, 4 September 2019 at 08:21:05 UTC, Suleyman wrote:That's not mandatory. You can simply define it as: this(ref Point rhs) {} It's just that it is good practice to use return scope.I want to tell something to u, Manu, WB... "u do something wrong with lang" [...][...]I understand that u can explain every word in such construction but for others it looks like "3.14dec" - u see here PI and something.. decimal? why? what is this mean? ok. ask any Russian friend/familiar, he will tell u that this means "big fucked up" in rude form. wait! what?! how?! and same way I read/see copy constructors in D. ok. show such construction to somebody who knows about copy constructors but not D guru and ask him what does he think about it? [...]
Sep 04 2019
On Wednesday, 4 September 2019 at 09:22:09 UTC, a11e99z wrote:[...] let's see C++ copy constructorI feel ya.Point( const Point& rhs ) { ... } // const ref to A. that alllet's D copy constructorthis( ref return scope const Point rhs ) { ... } // const.. ref to.. WTF?another side is move constructor. C++ again:relax, it's going to be either way with ` rvalue` or with ` move` not with both.Data( Data&& rhs ) { ... } // even less chars than for copy.// && - either logical AND or move semantics. u can't mistake. D?this( rvalue Data rhs ) move { ... }3.14dec! it even doesn't like to copy constructor - related entities.most people I think come to D from C++ or they tried C++ and faced with something incomprehensible, ambiguous, complex. (it seems to me that the templates are too sophisticated, and in D templates are understandable with constraints it even easier.) and they come to D cuz many people said on any forums "try to use D that has advantages over <lang>". and people try to write easy entity like "Point" and see.. this - copy/move.. it looks like fraud. its not easy to understand, u just can not step into D by soft step, u should read half specification to understand clear, simple things that is not clear and looks not simple. please hold things easy. I don't know how, but keep a easy way.Well C++ rvalue ref is not easy to understand either. If we go the rvalue ref way it's going to be the same. If we go the move way then if you can understand the copy constructor, the move attributes simply denotes the move version of the copy constructor. If you don't understand the difference between move and copy then you don't need to use it and it won't come in your way until you do.
Sep 04 2019
On Tuesday, 3 September 2019 at 01:07:18 UTC, Suleyman wrote:I updated the POC. You can now declare the move constructor & move opAssing like the following: ``` struct S { this(ref S) move {} // move constructor opAssign(ref S) move {} // move opAssign } ``` Or with rvalue ref: ``` struct S { this( rvalue ref S) {} // move constructor opAssign( rvalue ref S) {} // move opAssign } ``` All implementations use rvalue ref internally. It's just a matter of exposing in the language it or not.Why not define the move constructor like this: this(S rhs) {} I know it takes by value, but up until now it has been referred to as rvalue constructor. Behind the scenes the compiler will treat it as a move constructor and therefore will take the first parameter by ref. Now we have a clear distinction between copy constructor and move constructor. It should probably be in a different overload set than the other constructors.
Sep 05 2019
On Thursday, 5 September 2019 at 08:29:23 UTC, RazvanN wrote:Why not define the move constructor like this: this(S rhs) {} I know it takes by value, but up until now it has been referred to as rvalue constructor. Behind the scenes the compiler will treat it as a move constructor and therefore will take the first parameter by ref. Now we have a clear distinction between copy constructor and move constructor. It should probably be in a different overload set than the other constructors.You and kinke are orbiting around the same idea. Which is essentially making value parameters implicitly rvalue ref. You need to elaborate more on that. How it should behave, and how it would affect existing code.
Sep 05 2019
On Thursday, 29 August 2019 at 19:04:45 UTC, Manu wrote:On Thu, Aug 29, 2019 at 2:45 AM Atila Neves via Digitalmars-d <digitalmars-d puremagic.com> wrote:I've opened it before. I don't know how much of it is accidental complexity. The last time, I assumed none of it was.On Thursday, 29 August 2019 at 04:38:03 UTC, Manu wrote:Open core.lifetime and look at the text. If you're not terrified, then you're a braver man than me.On Wed, Aug 28, 2019 at 8:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:What's wrong with the current `move` and `forward` implementations?[...]It's a _part_ of perfect forwarding; the part that allows a function to receive forwarded arguments. The other part is a `forward` implementation passes them forward (and actually works), and that depends on a `move` implementation that works.Then also consider that the file has edge cases and bugs which I've run into on numerous occasions. We don't have move semantics, we have copy-elision and a bunch of hacks that unnecessarily call memcpy a lot of times (and don't actually work). Our move semantics are slow (lots of copying, not actually moving), and occasionally broken.So you've mentioned before. It'd be really helpful to have a list of those bugs and edge cases.For reference, here's the C++ implementation of that entire file: template <class T> T&& move(T& val) { return (T&&)val; } Our implementation would/should be similar.The C++ implementation is simple because the language is not by having rvalue references.
Sep 03 2019
On Tue, Sep 3, 2019 at 7:40 AM Atila Neves via Digitalmars-d <digitalmars-d puremagic.com> wrote:On Thursday, 29 August 2019 at 19:04:45 UTC, Manu wrote:You have to look no further than a call to `memcpy` in any of those functions to know it's all terrible.On Thu, Aug 29, 2019 at 2:45 AM Atila Neves via Digitalmars-d <digitalmars-d puremagic.com> wrote:I've opened it before. I don't know how much of it is accidental complexity. The last time, I assumed none of it was.On Thursday, 29 August 2019 at 04:38:03 UTC, Manu wrote:Open core.lifetime and look at the text. If you're not terrified, then you're a braver man than me.On Wed, Aug 28, 2019 at 8:45 PM Suleyman via Digitalmars-d <digitalmars-d puremagic.com> wrote:What's wrong with the current `move` and `forward` implementations?[...]It's a _part_ of perfect forwarding; the part that allows a function to receive forwarded arguments. The other part is a `forward` implementation passes them forward (and actually works), and that depends on a `move` implementation that works.I fix them as soon as finding them, but that still doesn't help that it's a mad and terrible hack, it's just one that gets closer to working.Then also consider that the file has edge cases and bugs which I've run into on numerous occasions. We don't have move semantics, we have copy-elision and a bunch of hacks that unnecessarily call memcpy a lot of times (and don't actually work). Our move semantics are slow (lots of copying, not actually moving), and occasionally broken.So you've mentioned before. It'd be really helpful to have a list of those bugs and edge cases.I don't think that's the case. Evidently, C++'s solution is a lot simpler than our solution, language included. I mean, we can't move at all. 'Simple' is only better if it actually works. If it's simple and doesn't work, then it's not better (see, copy-ctor replacing postblit for example).For reference, here's the C++ implementation of that entire file: template <class T> T&& move(T& val) { return (T&&)val; } Our implementation would/should be similar.The C++ implementation is simple because the language is not by having rvalue references.
Sep 03 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:[...] Recently D adpoted the copy constructor, the postblit is deprecated, and the postmove (DIP 1014) is also deprecated since it has the same problems as the postblit.Please don't forget to explicitly deprecate DIP 1014 in your DIP if you end up writing one, because it is not formally deprecated yet.[...] Give me your thoughts on which way you prefer. And most importantly I want you to convince me why adding rvalue refs is good for D because there aren't many use cases that I know of.Some major reasons: C++ interop, perfect forwarding, movable but non-copyable types (e.g. C++'s `std::unique_ptr`), cheaper reference counting. This is a good summary on rvalue references in C++: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
Aug 29 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:Give me your thoughts on which way you prefer. And most importantly I want you to convince me why adding rvalue refs is good for D because there aren't many use cases that I know of.An alternative solution to rvalue refs would be uniqueness semantics, which is how Rust solves move assignment (you can't move a variable's contents if it's being borrowed). Problem: D currently doesn't have baked-in unique references.
Aug 29 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:Hello everybody. Recently D adpoted the copy constructor, the postblit is deprecated, and the postmove (DIP 1014) is also deprecated since it has the same problems as the postblit. [...]I would like to point out that I have been working on a move constructor DIP [1]. It's not ready for review yet, but the backbone it there. We can unite efforts to get this to the finish line. As you can see, my DIP discusses perfect forwarding. [1] https://github.com/RazvanN7/DIPs/blob/Move_Constructor/DIPs/DIP1xxx-rn.md
Aug 29 2019
On Thursday, 29 August 2019 at 08:58:55 UTC, RazvanN wrote:https://github.com/RazvanN7/DIPs/blob/Move_Constructor/DIPs/DIP1xxx-rn.mdCurrently, the DMD compiler does not perform any move operationsIt does, the classical example being passing an rvalue argument by value to the callee: struct S { long[32] data; this(this) {} ~this() {} } void callee(S s) {} void caller() { callee(S()); } It allocates a first S instance on the caller stack and then moves that into a 2nd instance on the callee params stack (current D ABI on Posix x86_64 - non-PODs passed by value are passed on the stack), blitting the 32*8 bytes but eliding postblit (and destruction of the 1st instance) and letting callee destruct the moved-into 2nd instance. C++ passes a ref under the hood in such a case and can thus simply pass a pointer to the first instance, no blitting happening at all.
Aug 29 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:Give me your thoughts on which way you prefer. And most importantly I want you to convince me why adding rvalue refs is good for D because there aren't many use cases that I know of.Strongly related is my wish to make code like struct Range(S) { this(S s) { this.s = s; // currently passed by copy, but should be passed by move } S s; } perform pass by move instead of pass by copy thereby enable S to be a non-copyable value-type C++-style container via disable this(this); This will enable range-based algorithms in Phobos to take r-value references to non-copyable containers and generators as arguments. This will increase performance because the containers currently needs to be wrapped in a reference-counting storage such as std.typecons.RefCounted.
Aug 29 2019