Using in as a parameter qualifier

via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Thu Oct 23 08:16:15 PDT 2014


On Thursday, 23 October 2014 at 05:17:14 UTC, Shriramana Sharma 
via Digitalmars-d-learn wrote:
> Hi Jonathan and thanks again for your kind replies.
>
> On 10/23/14, Jonathan M Davis via Digitalmars-d-learn
> <digitalmars-d-learn at puremagic.com> wrote:
>> That will result in a move operation. No copying will take 
>> place.
>> And it technically, it may not actually move anything it all 
>> and
>> just use the object where it's initially constructed. I'm not
>> sure what the actual, generated machine code ends up doing in
>> that regard. But there's no copying or double-construction.
>
> Well blitting is copying isn't it? I read your SE answer where 
> you
> define a move as a blit without the postblit. Hmm. Somehow the 
> name
> "move" seems to be misleading...

I think a clarification of the terms is in order.

* Blitting. This is a low-level bitwise copy from one location to 
another. Depending on the circumstances, it may be between two 
memory locations, but it can also be from one or more registers 
into memory, or vice versa, or between registers. Which one it is 
can not be influenced nor predicted reliable from a D program, 
but the compiler's optimizer will try to choose the best method 
(usually registers).

* Copying. This is a high-level operation, defined by the 
programming language specification. For D, it means _blitting_, 
followed optionally by a call to a postblit method `this(this)` 
that can be used to make adjustments to the new copy (e.g. 
duplicating internal buffers, incrementing a reference counter, 
...). For C++, both parts are combined in the copy constructor.

* Moving. This is again a high-level concept, meaning that an 
object is transferred to a new location (variable, parameter), 
and the old location will be invalid afterwards. Semantically, it 
should always be equivalent to _copying_ the source to the 
destination, and then destroying the source. It is implemented as 
_blitting_ from the source to the destination. In D, it is thus 
equivalent to copying if there is no postblit defined.

On a language level, D itself has no concept of moving, except 
that the specification forbids structs to contain references into 
themselves, in order to allow the compiler to choose moving over 
copying. But there is no way for the programmer to force it; the 
compiler decides at its own discretion.

>
> To clarify that, is this the same as the C++11 move behaviour 
> or is it
> subtly different? IIUC in case of a C++ vector, a move would 
> mean that
> the length and possibly other direct members of the class 
> including
> the pointer to the heap-allocated managed data would be copied 
> to the
> target (same as blit I guess) and the managed data itself is not
> dupped (i.e. postblit is not called) and we call it a move, 
> yeah?

Yes.

> But
> it's actually better called a shallow copy, no?
>
> Or is a shallow copy different from a move in any other way?

Using the above terms, it is a shallow _blit_, because there's no 
copy-constructor or postblit being called. The point is that the 
compiler only does it if its outcome is semantically equivalent 
to a (deep) copy immediately followed by a destruction. This is 
commonly the case when the source cannot be accessed after the 
move.

To take std::vector as an example, doing a shallow copy if the 
source is still accessible after the copy will result in a 
semantically different behaviour, because both containers will 
now point to the same contents.

>
>> const does _not_ mean that a copy doesn't need to be made. It 
>> has
>> zero effect on that. In fact, if you're passing an lvaue to a
>> function that doesn't take its arguments by ref, it's almost a
>> guarantee that a copy will be made. The only exception would be
>> if the compiler determines that the lvalue in question is never
>> used after that call, in which case, it might just move the
>> object rather than copy it.
>
> But that doesn't seem logical. If the compiler is able to 
> determine
> that the lvalue in question is not modified in the function at 
> all
> (either heuristically or by the programmer defining it as "in" 
> or
> "const") then it should be able to optimize by not even making 
> the
> move aka shallow copy, no?

In principle, it could, but there is a complication: The function 
you call in this particular place can also be called from 
somewhere else, potentially from a different library which the 
compiler has no control over. Therefore, it needs to provide a 
fixed and predictable interface to the outside.

The optimization you have in mind requires pass-by-value to be 
turned into pass-by-reference, which would change this interface. 
However, the compiler could theoretically provide a different 
implementation of the function in addition, and keep the original 
one around for when it's needed. But I'd say this is an advanced 
optimization technique that you surely cannot rely on.

>
> Of course I understand it doesn't *have* to do an optimization, 
> and
> that D believes that ideally optimization opportunities should 
> be
> recognized and acted upon by the compiler and the user should 
> not need
> to specify places where this is possible (I listened to the
> Andrei/Walter panel talk where IIUC they were saying this is 
> the ideal
> behaviour and keywords like "pure" "nothrow" etc shouldn't 
> ideally be
> needed), but I just want to be sure whether I'm understanding 
> right
> that there is indeed the possibility in the present case.
>
>> So, you mean if draw was a member function of Bezier, and you 
>> did
>> something like
>> Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
>> you want to know whether a copy of the Bezier object would be
>> made? The this member of a struct is a ref - in this case ref
>> Bezier - so it doesn't make a copy.
>
> Actually it doesn't even make a move aka shallow copy right? If 
> that's
> right, then sorta here we are seeing how a ref to a temporary 
> can
> indeed exist, i.e. implicitly within the member functions. 
> (I'll post
> separately on that topic.)

Yes, but arguably that's a hole in the language that needs to be 
plugged ;-) It's certainly an inconsistency.

The problem is that inside `draw()`, you don't know whether 
`this` is a reference to a temporary or a longer lived object. 
It's dangerous if you accidentally return a reference to a 
temporary (or in many cases, even a stack variable).

>
>> If draw is a free function, then
>> Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
>> is literally transformed into
>> draw(Bezier(100, 100, 133, 200, 166, 200, 200, 200))
>> by the compiler, so the behavior of those two lines is 
>> identical.
>> And as I said above, that means that a move is made.
>
> OK so if I read this right, if the function is defined as a free
> function, then calling it on a temporary will make a shallow 
> copy (and
> I can't use an explicit ref on the temporary argument), but if 
> it is
> defined as a member then calling it on a temporary will not 
> even make
> a shallow copy. Because of UFCS, how I'm *calling* the function 
> will
> not have an effect on whether the shallow copy is made or not, 
> but how
> I *defined* it will. Correct?

Exactly.


More information about the Digitalmars-d-learn mailing list