DIP 1014
Stanislav Blinov
stanislav.blinov at gmail.com
Wed Oct 3 17:43:08 UTC 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.
More information about the Digitalmars-d
mailing list