September 04, 2019
On Wednesday, 4 September 2019 at 14:45:34 UTC, Suleyman wrote:
> I think you scored a valid point here. This is where having rvalue ref comes in handy. In C++ assigning an rvalue ref to an lvalue does move not copy.
>
> C++ example:
> ```
> void fun(T&& arg)
> {
>     T var = arg; // move not copy
> }
> ```

Nope, this calls the copy ctor (https://godbolt.org/z/Ds1vmQ) without an explicit `T var = std::move(arg)`. This is part of what makes C++ rvalue refs difficult to grasp.
September 04, 2019
On Wednesday, 4 September 2019 at 14:56:03 UTC, kinke wrote:
> On Wednesday, 4 September 2019 at 14:45:34 UTC, Suleyman wrote:
>> 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.
September 04, 2019
On Wednesday, 4 September 2019 at 16:37:30 UTC, Suleyman wrote:
> On Wednesday, 4 September 2019 at 14:56:03 UTC, kinke wrote:
>> 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.

September 04, 2019
On Wed, Sep 4, 2019 at 1:25 AM Suleyman via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:
> > 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`...
September 04, 2019
Wed, Sep 4, 2019 at 2:25 AM kinke via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:
> > 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?
September 04, 2019
On Wed, Sep 4, 2019 at 7:50 AM Suleyman via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Wednesday, 4 September 2019 at 00:58:44 UTC, Manu wrote:
> > 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.
September 04, 2019
On Wednesday, 4 September 2019 at 18:43:08 UTC, Manu wrote:
> On Wed, Sep 4, 2019 at 7:50 AM Suleyman via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>>
>> 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).
September 04, 2019
On Wednesday, 4 September 2019 at 19:01:54 UTC, kinke wrote:
> Move constructors are in fact never ever used to construct a parameter from an argument. The only time a move ctor is called is for `auto var = std::move(otherVar)`. That's how C++ handles 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!
September 04, 2019
On Wed, Sep 4, 2019 at 12:05 PM kinke via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Wednesday, 4 September 2019 at 18:43:08 UTC, Manu wrote:
> > 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.
September 04, 2019
On Wednesday, 4 September 2019 at 09:20:05 UTC, kinke wrote:
> On Wednesday, 4 September 2019 at 00:33:06 UTC, Exil wrote:
>> 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