DIP 1014

Stanislav Blinov stanislav.blinov at gmail.com
Wed Oct 3 20:25:52 UTC 2018


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.


More information about the Digitalmars-d mailing list