September 04, 2019
On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:
> So you change the ABI to pass by a pointer to the object on the stack. In cases where we use some "move" intrinsic, a pointer to a lvalue (passed in to the move()) is passed in place of the pointer to the object on the stack?

Yes.

> Because of this, even though the function doesn't use a reference. It could unknowingly change state outside of the scope of the function. That'll entirely depend on the use if they mistakenly use `move()` where they didn't mean to.

Yes, the assumption being that people do use `move` when they mean to (it's explicit after all) and are fine with not being able to make any assumptions about its state after the move. Re-assigning would still be possible.

But as stated further up, the crux is probably the lifetime for moved lvalues, i.e., the by-value parameter not being destroyed right before/after returning, but when the moved-from lvalue goes out of scope.
Maybe we could destruct the moved-from lvalue after the call and reset it to `T.init`. It would be destroyed a 2nd time when going out of scope.
September 05, 2019
On Wednesday, 4 September 2019 at 17:41:02 UTC, Suleyman wrote:
> [...]

I expanded rvalue ref in the POC. It is now possible to have a implementation of `forward` based on rvalue ref.

Example:
```
template forward(alias arg)
{
    // rvalue ref
    enum isRvalue = __traits(isRvalueRef, arg) ||
                    !(__traits(isRef,  arg) ||
                      __traits(isOut,  arg) ||
                      __traits(isLazy, arg));
    static if (isRvalue)
        @property @rvalue ref forward() { return __move(arg); }
    else
        alias forward = arg;
}

unittest
{
    class C
    {
        import core.stdc.stdio : puts;

        static void foo(int n)     { puts("foo value"); }
        static void foo(ref int n) { puts("foo ref"); }

        static void bar(@rvalue ref int n)  { puts("bar rvalue ref"); }
        static void bar(ref int n)          { puts("bar ref"); }
    }

    void foo()(auto ref int x) { C.foo(forward!x); }
    void bar()(auto ref int x) { C.bar(forward!x); }

    int i;
    foo(1);
    foo(i);

    bar(1);
    bar(i);
}
```

I don't see how we can achieve the same efficiency without rvalue ref.
September 05, 2019
On Thursday, 5 September 2019 at 01:30:02 UTC, Suleyman wrote:
> [...]

`auto ref` now expands to either `ref` or `@rvalue ref` (instead of value).


September 05, 2019
On Tuesday, 3 September 2019 at 01:07:18 UTC, Suleyman wrote:
> I updated the POC.
>
> You can now declare the move constructor & move opAssing like the following:
>
> ```
> struct S
> {
>     this(ref S) @move {}     // move constructor
>     opAssign(ref S) @move {} // move opAssign
> }
> ```
>
> Or with rvalue ref:
>
> ```
> struct S
> {
>     this(@rvalue ref S) {}     // move constructor
>     opAssign(@rvalue ref S) {} // move opAssign
> }
> ```
>
> All implementations use rvalue ref internally. It's just a matter of exposing in the language it or not.

Why not define the move constructor like this:

this(S rhs) {}

I know it takes by value, but up until now it has been referred to as rvalue constructor. Behind the scenes the compiler will treat it as a move constructor and therefore will take the first parameter by ref. Now we have a clear distinction between copy constructor and move constructor. It should probably be in a different overload set than the other constructors.
September 05, 2019
On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:
> On Wednesday, 4 September 2019 at 21:09:27 UTC, Exil wrote:
>> So you 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

September 05, 2019
On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu wrote:
> On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:
>> 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.
September 05, 2019
On Thursday, 5 September 2019 at 13:03:04 UTC, Les De Ridder wrote:
> On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu wrote:
>> 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'.
September 05, 2019
On Thursday, 5 September 2019 at 12:09:33 UTC, Eduard Staniloiu wrote:
> On Wednesday, 4 September 2019 at 21:59:46 UTC, kinke wrote:
>> 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
}
```

September 05, 2019
On Thursday, 5 September 2019 at 15:32:31 UTC, Suleyman wrote:
> Practical example:
> [...]
> void main()
> {
>     auto s = S(_1GB);
>     bar(__move(s));  // calls move ctor
>     assert(s.length == 0); // s is still usable
> }

Yes, that's what C++ does, and is not as efficient as can be. What I'm looking for is that there's no actual moving (move ctor call etc.) at all for move/__move in an argument expression. In your code example:

void main()
{
    auto s = S(_1GB);
    // does NOT call move ctor, just passes `s` by ref directly instead of a moved-to
    // temporary
    bar(__move(s));
    // after the call, destruct `s` and reset to T.init
    assert(s.length == 0); // s is still usable
    // this now does call the move ctor:
    auto s2 = __move(s);
} // `s` goes out of scope and is destructed again
September 05, 2019
On Thursday, 5 September 2019 at 15:49:37 UTC, kinke wrote:
> [...]
>
> void main()
> {
>     auto s = S(_1GB);
>     // does NOT call move ctor, just passes `s` by ref directly instead of a moved-to
>     // temporary
>     bar(__move(s));
>     // after the call, destruct `s` and reset to T.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.

1 2 3 4 5 6 7 8 9 10 11 12