On Wednesday, 3 October 2018 at 18:58:37 UTC, Shachar Shemesh wrote:

On 03/10/18 20:43, Stanislav Blinov wrote:
On Wednesday, 3 October 2018 at 15:33:00 UTC, Shachar Shemesh wrote:
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.

Well, I view this issue as a deal breaker. If you need to move the object *in order* to pass it to your move hook, then anything that requires knowing the address of the old instance will, by definition, not work.

I feel like we're still not on the same page here. this(typeof(this)) doesn't work like a move hook in D right now. I'm *suggesting* making that be a move ctor, instead of opPostMove from your DIP (see below).

Look at the output. The operator is being run, it can't *not* run,

Sure it can. Just look at the example I posted on the other thread (https://forum.dlang.org/post/pp2v16$1014$1...@digitalmars.com). The hook you mention is downright @disabled there.

In fact, had that not been the case, this DIP would never have happened.

Yup, we're definitely not on the same page :) That's not what I'm talking about at all.

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.

No, that's not true. Try printing the instance's address in the constructor and again in your operator.

It *is* true when the type doesn't have a destructor. Extending that to a move hook, it will also be true because destruction will be elided. I know what you're talking about, that happens for types that have destructors.

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.

I'm sorry, I'm not following. What is the difference between what you're proposing and opPostMove as defined?

1. with this(typeof(this)) the type of argument would never change. With opPostMove, it may. Remember that 'is(Tracker == const(Tracker))' is false.
2. you won't have to always move all the bits unconditionally.
3. symmetry with this(this)

This is the run I got:
$ ./movetest2
Address of temporary is 'b382e390', counter points to 'b382e390'
... which is '0' bytes from the address of temporary.

...I'm not sure what I should have seen, or what I should have concluded from it. This is your original program, unmodified.

This illustrates the intended behavior of a move hook if it existed in the language. The 'rhs' that was passed to the call was constructed at that same address (&rhs.counter == &rhs.localCounter). I.e. this is how I'm suggesting a this(typeof(this)) *could* work.

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

No. My proposal requires one amendment to argument passing in the ABI, but no special cases at all. Changes to the ABI are not the same as changes to the run time library.

How so? Or, more to the point, what's argument passing OR runtime have to do with this?

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:

It does not. It copies the bits from one to the other.

Poor choice of words on my part. It *creates* two. Whereas with this(typeof(this)) no implicit copying of bits is required, a-la a C++ move constructor. The programmer is free to choose the bits they need, or do a blit-and-patch if so desired. Except that unlike a C++ move constructor, no state bookkeeping would be necessary (same is true with your DIP as is).

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).

I'm not sure I follow you on that one. What did you mean?

In your case, that would be when it calls __move_post_blt.

3. In your case, it calls the opPostMove.

In all cases, you need to call the hook, whatever it is, for those structs that have it, and do some default handling for those that don't.

Correct, which would just be whatever the compilers already do.

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.

Like I said above, I don't think that's correct.

I'm assuming you're talking about why the example fails with the destructor. That's because what the DIP and you and me are currently discussing do not exist in the language. You add a destructor, you force the compiler to construct an additional temporary. That wouldn't happen if we were looking at an actual move hook, not that haphazard attempt to illustrate how one could work.

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.

Convince me that the pointers indeed don't change when passed to the function, and then we can discuss whether this point is correct.

They don't in my unmodified example. The reason they do at all are (1) destructors and (2) under-specification. The (1) won't be an issue for a move ctor, as the compiler won't need to destruct the original, and the (2) would be obviously avoided for types that do define the hook.

I guess I've just created more confusion than explanation, so to reiterate:

You're proposing:

1. Make the compiler emit extra code every time a struct is moved.
2. Allow users to provide a custom opPostBlit that takes an address of the original, called by (1).

I'm proposing:

1. Allow users to provide a custom ctor with a signature this(typeof(this) rhs). If none is provided but any member of the struct has one, generate one implicitly. 2. Such ctor should be called whenever a value needs be moved, with &this being the target address and &rhs the source address.

Reply via email to