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