www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Discussion: Rvalue refs and a Move construtor for D

reply Suleyman <sahmi.soulaimane gmail.com> writes:
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.

## With rvalue ref

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

## Without rvalue refs

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
next sibling parent reply Mike Franklin <slavo5150 yahoo.com> writes:
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.

 ## With rvalue ref

 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
parent Manu <turkeyman gmail.com> writes:
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:

 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.

 ## With rvalue ref

 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?
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.
Aug 28
prev sibling next sibling parent reply Mike Franklin <slavo5150 yahoo.com> writes:
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
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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?

 Mike
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 28
parent reply Manu <turkeyman gmail.com> writes:
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:
 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
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.
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.
Aug 28
parent reply Atila Neves <atila.neves gmail.com> writes:
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:
 On Thursday, 29 August 2019 at 01:01:36 UTC, Mike Franklin 
 wrote:
 [...]
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.
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.
What's wrong with the current `move` and `forward` implementations?
Aug 29
parent reply Manu <turkeyman gmail.com> writes:
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:
 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:
 [...]
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.
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.
What's wrong with the current `move` and `forward` implementations?
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.
Aug 29
next sibling parent reply kinke <noone nowhere.com> writes:
On Thursday, 29 August 2019 at 19:04:45 UTC, Manu wrote:
 Our move semantics are slow (lots of copying
I 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
parent kinke <noone nowhere.com> writes:
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
prev sibling next sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Les De Ridder <les lesderid.net> writes:
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
next sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent Manu <turkeyman gmail.com> writes:
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:
 [...]
 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?
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.
Sep 01
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
next sibling parent reply Manu <turkeyman gmail.com> writes:
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
next sibling parent reply Eduard Staniloiu <edi33416 gmail.com> writes:
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:

 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.
Nice job! I agree with Manu.
Sep 03
parent Suleyman <sahmi.soulaimane gmail.com> writes:
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:
 On Mon., 2 Sep. 2019, 6:10 pm Suleyman via Digitalmars-d, < 
 digitalmars-d puremagic.com> wrote:

 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.
Nice job! I agree with Manu.
I'm still demanding a use case for rvalue ref other than move semantics.
Sep 03
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
That's it; move semantics. That's not a minor thing... Why?
Sep 03
next sibling parent reply kinke <noone nowhere.com> writes:
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:
 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?
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)?
Sep 03
next sibling parent reply Exil <Exil gmall.com> writes:
On Wednesday, 4 September 2019 at 00:16:06 UTC, kinke wrote:
 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:
 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?
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)?
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?
Sep 03
next sibling parent Manu <turkeyman gmail.com> writes:
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:
 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:
 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?
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)?
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, this thing. The excessive memcpy's at every level of the callstack are a major problem.
Sep 03
prev sibling next sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent Manu <turkeyman gmail.com> writes:
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:
 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
And it comes for free with Andrei's DIP `-preview`...
Sep 04
prev sibling next sibling parent reply kinke <kinke gmx.net> writes:
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
next sibling parent Manu <turkeyman gmail.com> writes:
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:
 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.
rvalue ref could be implicit, but it still need to be attributed on the argument... isn't that what's being resisted here?
Sep 04
prev sibling parent reply Exil <Exil gmall.com> writes:
On Wednesday, 4 September 2019 at 09:20:05 UTC, kinke wrote:
 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.
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); // ok
Sep 04
parent reply kinke <noone nowhere.com> writes:
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
parent reply Eduard Staniloiu <edi33416 gmail.com> writes:
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 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.
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. Edi
Sep 05
next sibling parent reply Les De Ridder <les lesderid.net> writes:
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:
 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`.
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.
Sep 05
parent kinke <kinke gmx.net> writes:
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:
 I would expect the contents of the moved lvalue to be reset to 
 `Foo.init`.
I believe that's what kinke is proposing too?
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'.
Sep 05
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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:
 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.
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. Edi
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 } ```
Sep 05
parent reply kinke <kinke gmx.net> writes:
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
next sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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.init
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.
     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
Calling the destructor twice is not more efficient than one call to the move constructor and one call to the destructor.
Sep 05
parent kinke <noone nowhere.com> writes:
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
prev sibling parent reply Exil <Exil gmall.com> writes:
On Thursday, 5 September 2019 at 15:49:37 UTC, kinke wrote:
 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
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 times
Sep 05
parent reply kinke <noone nowhere.com> writes:
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
next sibling parent kinke <noone nowhere.com> writes:
On Thursday, 5 September 2019 at 18:59:50 UTC, kinke wrote:
 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.
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.
Sep 05
prev sibling next sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
next sibling parent Suleyman <sahmi.soulaimane gmail.com> writes:
On Thursday, 5 September 2019 at 19:38:57 UTC, Suleyman wrote:
 His initial point about the advantage of rvalue [...]
rvalue ref*
Sep 05
prev sibling next sibling parent reply kinke <noone nowhere.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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
parent reply kinke <noone nowhere.com> writes:
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 it'll increase the interest in D from Java/C#/JavaScript people. [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
parent reply Manu <turkeyman gmail.com> writes:
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:
 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 dismissed the second half of my sentence.
 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 it'll increase the interest in D from Java/C#/JavaScript people.
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.
 [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
parent reply kinke <noone nowhere.com> writes:
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:
 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 dismissed the second half of my sentence.
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.]
 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
next sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply kinke <noone nowhere.com> writes:
On Friday, 6 September 2019 at 15:18:24 UTC, Suleyman wrote:
 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.
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 }
 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
next sibling parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
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 }
 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.
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.
Sep 06
parent reply kinke <noone nowhere.com> writes:
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
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:
 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; } }
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.
Sep 06
next sibling parent reply Manu <turkeyman gmail.com> writes:
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:
 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; } }
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.
S is an rvalue; there are no other references to S. That's the point.
Sep 06
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
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.
Sep 06
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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:
 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.
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.
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.
Sep 07
parent reply Manu <turkeyman gmail.com> writes:
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:
 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:
 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.
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.
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.
I don't know what you mean. It absolutely makes sense, and it's very useful.
Sep 07
parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling parent Manu <turkeyman gmail.com> writes:
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:
 On Friday, 6 September 2019 at 19:50:30 UTC, kinke wrote:
 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; } }
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.
S is an rvalue; there are no other references to S. That's the point.
This doesn't come up very often, but when you need it, it has been a massive performance opportunity for us.
Sep 06
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply kinke <noone nowhere.com> writes:
On Friday, 6 September 2019 at 20:24:24 UTC, Suleyman wrote:
 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 `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.
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 example
  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
next sibling parent kinke <noone nowhere.com> writes:
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
prev sibling next sibling parent Suleyman <sahmi.soulaimane gmail.com> writes:
On Saturday, 7 September 2019 at 12:04:49 UTC, kinke wrote:
 I never meant to make such a huge breaking change to existing 
 semantics
I 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
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply kinke <noone nowhere.com> writes:
On Saturday, 7 September 2019 at 21:09:45 UTC, Suleyman wrote:
 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.
Yes.
 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
next sibling parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
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:
 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.
Yes.
 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...
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.
Sep 07
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
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
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
next sibling parent reply Manu <turkeyman gmail.com> writes:
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:
 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&&*`.
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.
Sep 07
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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/ZfdOeu
 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.
Ref in C++ is unjustifiably limited. And saving rvalueness has nothing to do in the ref vs pointer battle.
Sep 07
parent reply Manu <turkeyman gmail.com> writes:
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 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/ZfdOeu
 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.
Ref in C++ is unjustifiably limited. And saving rvalueness has nothing to do in the ref vs pointer battle.
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 :/
Sep 07
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
next sibling parent reply Les De Ridder <les lesderid.net> writes:
On Sunday, 8 September 2019 at 02:06:54 UTC, Suleyman wrote:
 On Sunday, 8 September 2019 at 01:46:11 UTC, Manu wrote:
 [...]
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.
Copy constructors got in...
Sep 07
parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
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`.
Sep 07
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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:
 [...]
Then having `cast(rvalue)` instead of the `__move` intrinsic would make perfect sense in the language.
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.
Sep 08
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
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.
Sep 08
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
On Monday, 9 September 2019 at 01:25:18 UTC, Suleyman wrote:
 [...]
     foo(&i); // error: ambiguous call
A little mistake there, it should be `foo(i)`.
Sep 08
parent Manu <turkeyman gmail.com> writes:
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:
 [...]
     foo(&i); // error: ambiguous call
A little mistake there, it should be `foo(i)`.
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...
Sep 08
prev sibling parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling parent reply Manu <turkeyman gmail.com> writes:
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:
 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:
 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 dismissed the second half of my sentence.
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.]
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.
Sep 06
parent reply kinke <noone nowhere.com> writes:
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
parent reply kinke <noone nowhere.com> writes:
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
parent Manu <turkeyman gmail.com> writes:
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:
 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.
'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.
Sep 06
prev sibling parent reply kinke <noone nowhere.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
Please no. This is a terrible series of sentences >_< `-preview=rvaluerefparam` naturally interacts benefitially with the proposed ` rvalue` attribute. Don't mess with that.
Sep 05
parent reply kinke <noone nowhere.com> writes:
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:
 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.
Please no. This is a terrible series of sentences >_< `-preview=rvaluerefparam` naturally interacts benefitially with the proposed ` rvalue` attribute. Don't mess with that.
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.
Sep 05
parent reply Manu <turkeyman gmail.com> writes:
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:
 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:
 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.
Please no. This is a terrible series of sentences >_< `-preview=rvaluerefparam` naturally interacts benefitially with the proposed ` rvalue` attribute. Don't mess with that.
Care to elaborate?
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.
 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
parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling parent Manu <turkeyman gmail.com> writes:
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:
 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.
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?
Sep 05
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply kinke <kinke gmx.net> writes:
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
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
On Wednesday, 4 September 2019 at 14:56:03 UTC, kinke wrote:
 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.
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.
Sep 04
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
On Wednesday, 4 September 2019 at 16:37:30 UTC, Suleyman wrote:
 On Wednesday, 4 September 2019 at 14:56:03 UTC, kinke wrote:
 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.
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.
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.
Sep 04
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling parent reply Manu <turkeyman gmail.com> writes:
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:
 On Tue, Sep 3, 2019 at 2:45 PM Suleyman via Digitalmars-d
 <digitalmars-d puremagic.com> wrote:
 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?
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)?
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.
Sep 03
parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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
parent reply Manu <turkeyman gmail.com> writes:
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:
 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.
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.
Sep 04
parent reply kinke <noone nowhere.com> writes:
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:
 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.
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.
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).
Sep 04
next sibling parent kinke <noone nowhere.com> writes:
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 
 it
I 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
prev sibling parent Manu <turkeyman gmail.com> writes:
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:
 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:
 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.
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.
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).
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.
Sep 04
prev sibling parent reply Suleyman <sahmi.soulaimane gmail.com> writes:
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:
 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.
That's it; move semantics. That's not a minor thing... Why?
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.
Sep 04
parent reply a11e99z <black80 bk.ru> writes:
On Wednesday, 4 September 2019 at 08:21:05 UTC, Suleyman wrote:
 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
I want to tell something to u, Manu, WB... "u do something wrong with lang" let's see C++ copy constructor
 Point( const Point& rhs ) { ... } // const ref to A. that all
let's D copy constructor
 this( 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
next sibling parent a11e99z <black80 bk.ru> writes:
On Wednesday, 4 September 2019 at 09:22:09 UTC, a11e99z wrote:
 On Wednesday, 4 September 2019 at 08:21:05 UTC, Suleyman wrote:
 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
copy can be
 this( Point rhs )  copy { ... }
move can be
 this( 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 like
  inline  nodiscard Complicated func( Args... )  nogc nothrow { 
 .. }
looks like a mess/crap attrs in C#/Rust looks as separate clear constructions that u can 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
prev sibling next sibling parent RazvanN <razvan.nitu1305 gmail.com> writes:
On Wednesday, 4 September 2019 at 09:22:09 UTC, a11e99z wrote:
 On Wednesday, 4 September 2019 at 08:21:05 UTC, Suleyman wrote:
 [...]
I want to tell something to u, Manu, WB... "u do something wrong with lang" [...]
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 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
prev sibling parent Suleyman <sahmi.soulaimane gmail.com> writes:
On Wednesday, 4 September 2019 at 09:22:09 UTC, a11e99z wrote:
 [...]
 let's see C++ copy constructor
 Point( const Point& rhs ) { ... } // const ref to A. that all
let's D copy constructor
 this( ref return scope const Point rhs ) { ... } // const.. 
 ref to.. WTF?
I feel ya.
 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.
relax, it's going to be either way with ` rvalue` or with ` move` not with both.
 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
prev sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
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
parent Suleyman <sahmi.soulaimane gmail.com> writes:
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
prev sibling parent reply Atila Neves <atila.neves gmail.com> writes:
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:
 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:
 [...]
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.
What's wrong with the current `move` and `forward` implementations?
Open core.lifetime and look at the text. If you're not terrified, then you're a braver man than me.
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.
 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
parent Manu <turkeyman gmail.com> writes:
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:
 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:
 On Wed, Aug 28, 2019 at 8:45 PM Suleyman via Digitalmars-d
 <digitalmars-d puremagic.com> 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.
What's wrong with the current `move` and `forward` implementations?
Open core.lifetime and look at the text. If you're not terrified, then you're a braver man than me.
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.
You have to look no further than a call to `memcpy` in any of those functions to know it's all terrible.
 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 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.
 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.
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).
Sep 03
prev sibling next sibling parent Les De Ridder <les lesderid.net> writes:
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
prev sibling next sibling parent Olivier FAURE <couteaubleu gmail.com> writes:
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
prev sibling next sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
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
parent kinke <kinke gmx.net> writes:
On Thursday, 29 August 2019 at 08:58:55 UTC, RazvanN wrote:
 https://github.com/RazvanN7/DIPs/blob/Move_Constructor/DIPs/DIP1xxx-rn.md
 Currently, the DMD compiler does not perform any move operations
It 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
prev sibling parent Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
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