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.

Reply via email to