August 29, 2019
On Thursday, 29 August 2019 at 08:58:55 UTC, RazvanN wrote:
> https://github.com/RazvanN7/DIPs/blob/Move_Constructor/DIPs/DIP1xxx-rn.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.
August 29, 2019
On Thu, Aug 29, 2019 at 2:45 AM Atila Neves via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Thursday, 29 August 2019 at 04:38:03 UTC, Manu wrote:
> > 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.
August 29, 2019
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`.
August 29, 2019
On Wednesday, 28 August 2019 at 23:32:01 UTC, Suleyman wrote:
> Give me your thoughts on which way you prefer. And most importantly I want you to convince me why adding rvalue refs is good for D because there aren't many use cases that I know of.

Strongly related is my wish to make code like

struct Range(S)
{
    this(S s) {
        this.s = s; // currently passed by copy, but should be passed by move
    }
    S s;
}

perform pass by move instead of pass by copy thereby enable S to be a non-copyable value-type C++-style container via

    @disable this(this);

This will enable range-based algorithms in Phobos to take r-value references to non-copyable containers and generators as arguments. This will increase performance because the containers currently needs to be wrapped in a reference-counting storage such as std.typecons.RefCounted.
August 29, 2019
On Thursday, 29 August 2019 at 20:26:10 UTC, kinke wrote:
>     else // void wrapper(@rvalue ref NoPOD s)
>     {
>         // call rvalue overload, perfectly forwarding the `s` rvalue reference
>         callee(s);
>     }

This should have been: `callee(move(s))`.
August 31, 2019
I made a POC for the implementation without rvalue ref.
Here it is: https://github.com/dlang/dmd/pull/10383.

It compiles. And works just like the rvalue ref implementation would.

On Thursday, 29 August 2019 at 19:04:45 UTC, Manu wrote:
> For reference, here's the C++ implementation of that entire file:
>
> template <class T>
> T&& move(T& val) { return (T&&)val; }
>
> Our implementation would/should be similar.

I also added a `cast(rvalue)` which does the same as the `(T&&)` in C++ but returns an rvalue. And an intrinsic `__move()` and a `__traits(getRvalue)` which are simply syntactic flavors of `cast(rvalue)`.

September 01, 2019
On Saturday, 31 August 2019 at 20:15:22 UTC, Suleyman wrote:
> I made a POC for the implementation without rvalue ref.
> Here it is: https://github.com/dlang/dmd/pull/10383.
>
> It compiles. And works just like the rvalue ref implementation would.

I realise you need to differentiate between copy constructors and move
constructors somehow, but I'm not sure calling move constructors
`__move_ctor` is the right solution. The average user of D should
neverhave to use a `__` symbol, but it's not unlikely they might want to
write a struct with move semantics.  Maybe a solution with a UDA is
possible?

> I also added a `cast(rvalue)` which does the same as the `(T&&)` in C++ but returns an rvalue. And an intrinsic `__move()` and a `__traits(getRvalue)` which are simply syntactic flavors of `cast(rvalue)`.

I'm not sure why there are three different ways to 'cast to an rvalue'?
Is there a technical reason?

I also noticed you're casting lvalues to 'rvalues' and then binding them
to a `ref` param. Is that not an rvalue reference? Why can't we have
generalised rvalue references instead?
September 01, 2019
On Sunday, 1 September 2019 at 08:59:08 UTC, Les De Ridder wrote:
> [...]
> Maybe a solution with a UDA is possible?

Possible.

> I'm not sure why there are three different ways to 'cast to an rvalue'?
> Is there a technical reason?

I'm just demonstrating the possibilities. Likely only one of them will be picked.

> I also noticed you're casting lvalues to 'rvalues' and then binding them a `ref` param. Is that not an rvalue reference?

Yes but I would argue this is just a perk of rvalue ref and not what the essence of it is.
You can do the same thing by assigning to a temporary then passing it by ref. This all the compiler does here.

> Why can't we have generalised rvalue references instead?

What do you need if for? Rvalue ref comes with it's own overloading and implicit conversion rules. Generalized rvalue ref is not absolutely needed for move semantics. What else comes to mind other than move semantics as a use case?

September 01, 2019
On Sun, Sep 1, 2019 at 7:10 AM Suleyman via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Sunday, 1 September 2019 at 08:59:08 UTC, Les De Ridder wrote:
> > [...]
> > 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.
September 03, 2019
I updated the POC.

You can now declare the move constructor & move opAssing like the following:

```
struct S
{
    this(ref S) @move {}     // move constructor
    opAssign(ref S) @move {} // move opAssign
}
```

Or with rvalue ref:

```
struct S
{
    this(@rvalue ref S) {}     // move constructor
    opAssign(@rvalue ref S) {} // move opAssign
}
```

All implementations use rvalue ref internally. It's just a matter of exposing in the language it or not.
1 2 3 4 5 6 7 8 9 10 11 12