October 03, 2018
On 03/10/18 16:56, Stanislav Blinov wrote:
> struct S {
>      this(S rhs);

OMG, that's so simple!!! Why didn't I think of it?

Oh wait, I did.

And this simply and utterly doesn't work.

If you read the DIP, you will notice that the *address* in which the old instance resides is quite important for performing the actual move. This is not available with the interface you're suggesting, mainly because by the time you have rhs, it has already moved.

In other words, for the interface above to work, the type must already be movable, which kinda contradict what we're trying to achieve here.

> in C++ would have an equivalent signature of
> 
> ReturnType foo(Type&& x); // NOT ReturnType foo(Type x);

No, it is not. You see, in C++, x is an "rvalue *reference*". x has not moved by this point in the run, it has simply had its address passed to foo. Please see https://stackoverflow.com/questions/28483250/rvalue-reference-is-treated-as-an-lvalue

Shachar
October 03, 2018
On Wednesday, 3 October 2018 at 14:07:58 UTC, Shachar Shemesh wrote:
> On 03/10/18 16:56, Stanislav Blinov wrote:
>> struct S {
>>      this(S rhs);
>
> OMG, that's so simple!!! Why didn't I think of it?
>
> Oh wait, I did.

Now I see why sometimes your posts are greeted with hostility.

> And this simply and utterly doesn't work.
>
> If you read the DIP, you will notice that the *address* in which the old instance resides is quite important for performing the actual move. This is not available with the interface you're suggesting, mainly because by the time you have rhs, it has already moved.
>
> In other words, for the interface above to work, the type must already be movable, which kinda contradict what we're trying to achieve here.

In the presence of such a constructor, the compiler will have to call it every time it moves the value, same as what you're proposing for __move_post_blt. This obviates the need of an address: address of the argument will always already be sufficient, even though it's not ref, as the chain of calls for this(S) will inevitably start with the address of something constructed in place.

>> in C++ would have an equivalent signature of
>> 
>> ReturnType foo(Type&& x); // NOT ReturnType foo(Type x);
>
> No, it is not. You see, in C++, x is an "rvalue *reference*". x has not moved by this point in the run, it has simply had its address passed to foo.

You've misunderstood me. Yes, in C++ there's an obvious difference between pass-by-value and pass-by-rvalue-reference, and it is always user's responsibility to write a move ctor. Not so in D. In D, you can always assume that anything passed by value *is* an rvalue reference, precisely because of D's take on move semantics. I.e. any argument passed by value can assumed to be moved or constructed in place (that's the main difference from C++, where it must be explicitly specified).
October 03, 2018
On Wednesday, 3 October 2018 at 14:07:58 UTC, Shachar Shemesh wrote:

> If you read the DIP, you will notice that the *address* in which the old instance resides is quite important...

Allow me to further illustrate with something that can be written in D today:

import std.stdio;

struct Tracker {
    static int globalCounter;
    int localCounter;
    int* counter;

    this (bool local) {
        if (local) counter = &localCounter;
        else counter = &globalCounter;
    }

    // this should be this(Tracker rhs)
    void opAssign(Tracker rhs) {
        // note: taking address of local parameter
        // note: LDC will already 'optimize' the move which in the absence
        // of any move hooks will mess up the address; try with DMD
        printf("Address of temporary is '%x', counter points to '%x'\n", &rhs, rhs.counter);
        auto d = cast(void*) rhs.counter - cast(void*) &rhs;
        printf("... which is '%ld' bytes from the address of temporary.\n", d);
        localCounter = rhs.localCounter;
        counter = rhs.counter;
        if (counter is &rhs.localCounter)
            counter = &localCounter;
    }
}

auto createCounter(bool local = true) {
    Tracker result = Tracker(local);
    return result;
}

auto createCounterNoNRV(bool local = true) {
    return Tracker(local);
}

void main() {

    Tracker stale1, stale2;

    stale1 = createCounter();
    stale2 = createCounter(false);

    Tracker stale3, stale4;

    stale3 = createCounterNoNRV();
    stale4 = createCounterNoNRV(false);
}


If you run the above with DMD, you'll see what I mean about obviating the address. If we get this(typeof(this)) (that is *always* called on move) into the language, the behavior would be set in stone regardless of compiler.
October 03, 2018
On 03/10/18 17:29, Stanislav Blinov wrote:
>> OMG, that's so simple!!! Why didn't I think of it?
>>
>> Oh wait, I did.
> 
> Now I see why sometimes your posts are greeted with hostility.

Yes. I am actually sorry about that. I was responding to your assumption that I'm wrong. Had your post been phrased as "why didn't you", instead of "you're wrong wrong wrong" I wouldn't have responded that way.

Like I said, I am sorry.


> Allow me to further illustrate with something that can be written in D > today:

I am not sure what you were trying to demonstrate, so instead I wanted to see if you succeeded. I added the following to your Tracker struct:

     ~this() {
        writefln("%s destructed", &this);
        assert(counter is null || counter is &localCounter);
    }

I.e. - I am asserting if a move was not caught. The program fails to run on either ldc or dmd. To me, this makes perfect sense as for the way D is built. In essence, opAssign isn't guaranteed to run. Feel free to build a struct where that assert passes to convince me.

Here is the flaw in your logic:

    void opAssign(Tracker rhs)

rhs is passed by value. This means that already at the point opAssign is called, rhs *already* has a different address than the one it was passed in with. I did not follow your logic on why this isn't so, but I don't see how you can make it not so without changing the ABI quite drastically.

Shachar
October 03, 2018
On 03/10/18 18:33, Shachar Shemesh wrote:
>       ~this() {
>          writefln("%s destructed", &this);
>          assert(counter is null || counter is &localCounter);
>      }

You might also want to add @disable this(this); and remove the dead code (i.e. - the case where the pointer is global) to reduce noise. I verified that neither one changes anything in the outcome.

Shachar
October 03, 2018
On Wed, Oct 3, 2018 at 2:50 AM Corel via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
> On Wednesday, 3 October 2018 at 08:21:38 UTC, Manu wrote:
> > On Tue, Oct 2, 2018 at 6:15 PM Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> >>
> >> On 10/2/2018 4:30 PM, Adam D. Ruppe wrote:
> >> > On Tuesday, 2 October 2018 at 22:30:38 UTC, Jonathan M Davis wrote:
> >> >> Yeah. IIRC, it was supposed to be _guaranteed_ that the compiler moved structs in a number of situations - e.g. when the return value was an rvalue. Something like
> >> >
> >> > Eh, I don't think that moves it, but rather just constructs it in-place for the next call.
> >>
> >> The technical term for that is "copy elision".
> >
> > Okay, so copy elision is working... but moves otherwise are not? That's still not what we've been peddling all these years. A whole lot of design surface area is dedicated to implicit move semantics... and they don't work? What does it do? postblit unnecessarily?
>
> The impression is that you are complaining about the continuous lack of "things" based on an incomplete knowledge of how D works in detail ... tragically you invoke low-level features, and you do not know the question.
>
> The fact that in D the structures to date are not moved, is known for years ... take advantage of this fact, and move on.
>
> Work on an implementation that works, AFTER profile it, and possibly complain about performance.

O_o .. this is one of the stranger replies I've ever gotten here.
October 03, 2018
On 03/10/18 12:48, Corel wrote:
> The fact that in D the structures to date are not moved, is known for years ... take advantage of this fact, and move on.

I have no idea where you got this fact:

import std.stdio;

struct MoveTest {
    static uint counter=1;
    uint id;

    @disable this(this);
    @disable this(MoveTest);

    this(uint dummy) {
        id = counter++;
        writefln("Constructed %s id %s", &this, id);
    }

    ~this() {
        writefln("Id %s destroyed at %s", id, &this);
    }
}

MoveTest func1() {
    return MoveTest(3);
}

void func2(MoveTest m) {
}

int main() {
    func2(func1());

    return 0;
}

$ rdmd movetest.d
Constructed 7FFDC7A663E0 id 1
Id 1 destroyed at 7FFDC7A66400

Our instance was constructed at one address, but destroyed at another. In other words, it was moved.

Can we, please, put that myth to rest?

Shachar
October 03, 2018
On Wednesday, 3 October 2018 at 15:33:00 UTC, Shachar Shemesh wrote:
> On 03/10/18 17:29, Stanislav Blinov wrote:
>>> OMG, that's so simple!!! Why didn't I think of it?
>>>
>>> Oh wait, I did.
>> 
>> Now I see why sometimes your posts are greeted with hostility.
>
> Yes. I am actually sorry about that. I was responding to your assumption that I'm wrong. Had your post been phrased as "why didn't you", instead of "you're wrong wrong wrong" I wouldn't have responded that way.
>
> Like I said, I am sorry.

I am sorry as well since I wasn't clear in my initial post.

> > Allow me to further illustrate with something that can be
> written in D > today:
>
> I am not sure what you were trying to demonstrate, so instead I wanted to see if you succeeded. I added the following to your Tracker struct:
>
>      ~this() {
>         writefln("%s destructed", &this);
>         assert(counter is null || counter is &localCounter);
>     }
>
> I.e. - I am asserting if a move was not caught. The program fails to run on either ldc or dmd. To me, this makes perfect sense as for the way D is built. In essence, opAssign isn't guaranteed to run. Feel free to build a struct where that assert passes to convince me.

That's a slightly different issue here.
Look at the output. The operator is being run, it can't *not* run, unlike postblit (ironically, right now it doesn't run on fixed-size arrays though). In fact, as soon as you define a destructor, the compiler will generate a by-value opAssign if you haven't defined one.
That's a separate problem. Currently, presence of a destructor makes the compilers generate different code, because it cannot elide destruction of arguments, because explicit move semantics do not exist in the language. That's why I haven't included a destructor in the example to begin with.

> Here is the flaw in your logic:
>
>     void opAssign(Tracker rhs)
>
> rhs is passed by value. This means that already at the point opAssign is called, rhs *already* has a different address than the one it was passed in with.

Currently that is only true if you define a destructor. That would not be true, however, if a move hook in any form existed in the language. That was my point.
I only used opAssign as something resembling the supposed new behavior, not as a "look, it already works". In the presence of a move hook, 'rhs' would first have to pass through that hook, which will not take destructors into account at all.
Consider your own DIP: what you're suggesting is the ability to take the address of the original when a move is taking place. My example shows that in the simplest case even today, address of the original is already the address of the argument. Except it cannot be enforced in any way right now. A move hook will have to enforce that, as it will have to be called for every move.

> I did not follow your logic on why this isn't so, but I don't see how you can make it not so without changing the ABI quite drastically.

The changes are literally the same as the ones you're proposing:

"When moving a struct's instance, the compiler MUST call __move_post_blt giving it both new and old instances' addresses."

That is the same that would have to happen with this(typeof(this) rhs), where &this is the address of new instance, and &rhs is the address of old instance, but there's no need for opPostMove then. I guess what I should've said from the start is that the semantics you're proposing fit nicely within one special function, instead of two.

this(typeof(this)), of course, would need to be special in the ABI, but again, that's one special function instead of two.

Let's take a step back for a moment and look at what should actually be happening for this hook to work (which you briefly mention in the DIP):

1. The compiler constructs the value. In your case, it constructs two: the original and the new one. In my case, it constructs the original and then passes it over to the move ctor (one blit potentially avoided).
2. It calls the hook (move ctor).
3. In your case, it calls the opPostMove.
4. In any case, it *doesn't* destruct the original. Ever. The alternative would be to force the programmer to put the original back into valid state, and suddenly we're back to C++ with all it's pleasantries.

That last part is quite different from the current model, in which the compiler always destructs function arguments. That's why my example fails when a destructor is present.

The other thing to note (again something that you mention but don't expand on), and that's nodding back to my comment about making move() and emplace() intrinsics, is that creating such a hook *will* invalidate current behavior of move(). Which is perhaps more easily fixed with your implementation, actually, *except* for the part about eliding destruction. Unions are unreliable for that unless we also change the spec that talks about them.
But IMHO, it's something that should be fixed by not making these facilities built into the language.
October 03, 2018
On Wednesday, 3 October 2018 at 17:43:08 UTC, Stanislav Blinov wrote:

> But IMHO, it's something that should be fixed by not making these facilities built into the language.

s/not//

October 03, 2018
On Wednesday, 3 October 2018 at 08:21:38 UTC, Manu wrote:

> Okay, so copy elision is working... but moves otherwise are not? That's still not what we've been peddling all these years. A whole lot of design surface area is dedicated to implicit move semantics... and they don't work? What does it do? postblit unnecessarily?

No. The problem is that the language is under-specified. It is built on the *assumption* that no one ever should create self-referencing data. But it does not enforce that. Which eventually leads to someone trying to make such data and then run into a wall, or worse, a heisenbug.

Thing is, there isn't anything wrong with self-referencing data per se. It's that the language plumbing should either disallow it wholesale (i.e. Rust) or allow a graceful way of handling it. Neither is present in D. The latter could be added though, that's what the DIP is about.