rvalues -> ref (yup... again!)

Rubn where at is.this
Tue Mar 27 20:25:36 UTC 2018


On Tuesday, 27 March 2018 at 07:33:12 UTC, Atila Neves wrote:
> On Tuesday, 27 March 2018 at 00:30:24 UTC, Rubn wrote:
>> On Monday, 26 March 2018 at 14:40:03 UTC, Atila Neves wrote:
>>> C++ T&& (Rvalue reference) -> D T
>>
>> Not really, in C++ it is an actual reference and you get to 
>> choose which function actually does the move. In D it just 
>> does the copy when passed to the function.
>
> It doesn't copy.

It copies the memory, so it does 2 memcpy's in the sense where as 
C++ only calls its move constructor once.

>> So you can't do this in D.
>>
>> void bar(T&& t)
>> {
>>     // actually move contents of T
>> }
>>
>> void foo(T&& t)
>> {
>>     bar(std::forward<T>(t)); // Can't do this in D without 
>> making another actual copy cause it isn't a reference
>> }
>
>
> You can most definitely do this in D:
>
> void bar(T)(auto ref T t) {
>     // T is a ref for lvalues, by value for rvalues
> }
>
> void foo(T)(auto ref T t) {
>     import std.functional: forward;
>     bar(forward!t);
> }
>
>
> More to the point:
>
> import std.stdio;
>
> struct Foo {
>     ubyte[] data;
>
>     this(int n) {
>         writeln("ctor n = ", n);
>         data.length = n;
>     }
>
>     this(this) {
>         writeln("postBlit n = ", data.length);
>         data = data.dup;
>     }
> }
>
> void foo(T)(auto ref T t) {
>     import std.functional: forward;
>     bar(forward!t);
> }
>
> void bar(T)(auto ref T t) {
>     writeln("bar: ", t.data[0]);
> }
>
> void main() {
>     bar(Foo(10));
>     auto f = Foo(5);
>     f.data[0] = 42;
>     bar(f);
> }
>
>
> The output is:
>
> ctor n = 10
> bar: 0
> ctor n = 5
> bar: 42
>
> Notice the lack of "postBlit" in the output. No copies were 
> made. In D, by value *does not* mean copy. And given that, 
> contrary to C++, the compiler doesn't write the postBlit 
> constructor for you, you'd only ever get copies if you 
> implemented it yourself!

Well for starters your code is wrong. You are calling bar() 
instead of foo().

D has hidden implementation details, from your perspective it 
looks like it isn't doing any copying from the high-level 
viewpoint, but from the low-level viewpoint your object has been 
copied (moved memory) multiple times.

struct Foo {
     ubyte[1024] data;
}

void foo(T)(auto ref T t) {
     import std.functional: forward;
     bar(forward!t);
}

Foo gfoo;

void bar(T)(auto ref T t) {
     import std.algorithm.mutation : move;
     move(t, gfoo);
}

void main() {
     foo(Foo(10));
}

_D7example__T3fooTSQr3FooZQnFNbNiNfQrZv:
   push rbp
   mov rbp, rsp
   sub rsp, 3104
   lea rax, [rbp + 16]
   lea rdi, [rbp - 2048]
   lea rcx, [rbp - 1024]
   mov edx, 1024
   mov rsi, rcx
   mov qword ptr [rbp - 2056], rdi
   mov rdi, rsi
   mov rsi, rax
   mov qword ptr [rbp - 2064], rcx
   call memcpy at PLT    <--------------------- hidden copy


Then the other copy is in move() to gfoo. That hidden copy will 
happen for every additional function call you try to pass Foo 
through.


>> What's a concrete example that you would be required to know 
>> whether a const& is a temporary or not.
>
> To know whether or not you can move instead of copy. If it's a 
> temporary, you can move. If it's not, you have to copy. Since 
> temporaries bind to const T& in C++, you might have a 
> temporary, or you might have an lvalue. Since you don't know, 
> you have to copy. To support move semantics, C++ got T&&, which 
> lvalues can't bind to. So if you have a T&&, you know it's 
> about to go away and a move is possible.
>
> In D, if it's ref then it can't be a temporary. If it's a value 
> then it can, and it gets moved.

D already has move semantics, an easy solution to this is to just 
use another keyword. It doesn't have to bind to const ref to get 
what is desired:

// what was suggested in the original DIP, since scope is being 
used for something else now
void foo(@temp ref value)
{
}

Now you don't have this problem. You only get this behavior when 
you basically say you don't care whether it is a temporary or not.

So what's your problem with it now ?

>> I've come across a few pains of such. It make be easier to use 
>> but it comes at a performance hit. In part binaries become 
>> huge because of how "init" is implemented.
>>
>> struct StaticArray(T, size_t capacity)
>> {
>>     size_t length;
>>     T[capacity] values;
>> }
>>
>> Copying the above structure copies unnecessary data for any 
>> move/copy operation. Eg when length = 0, it'll still copy 
>> everything. This includes initialization.
>
> This is that rare type for which moving is the same as copying. 
> In that case (assuming it gets copied, see my reply to Manu), 
> pass by ref. You won't be able to pass in temporaries, but I 
> think that's a small price to pay for not having rvalue 
> references.
>
> In this case specifically, I don't know why you wouldn't just 
> slice it when passing to functions.
>
> Atila

This wasn't an example for rvalue references. This was an example 
illustrating the negative results of the current "pain-free" 
system. I have a 100 mb binary, 90 mb of that come from a single 
structure. I mean sure you don't really have to worry about 
implementing move constructors and such but it is far from being 
pain free, that's what that was suppose to illustrate.



More information about the Digitalmars-d mailing list