move semantics are a mess

Exil Exil at gmall.com
Sun May 26 19:19:12 UTC 2019


On Sunday, 26 May 2019 at 18:24:17 UTC, Manu wrote:
> I've been trying to do some initial work with copy ctor's, and 
> that has lead me to closely scritinise the various construction 
> flow's, and it's revealed a whole bunch of issues with move 
> semantics.
>
> The 2 most immediate issues are here:
> https://issues.dlang.org/show_bug.cgi?id=19904
> Those are surprising, the functions specifically designed to 
> perform
> move semantics stop move semantics in their tracks.
>
> ------------------------------
>
> But it doesn't end there.
> I tried to correct those issues by adding the appropriate
> `forward!args` in the right places, but that causes chaos.
>
> One serious issue I've noticed looks like this:
>   void fun(Args...)(auto ref Args args)
>   {
>     auto b = T(forward!args);
>   }
>
>   fun(myT.move); // <- call with rvalue; move semantics desired
>
> I've encountered various forms of this general pattern. So the 
> trouble
> here is, it tried to call a T constructor with `args`, and 
> there are
> cases:
>   1. args are actual constructor args -> call appropriate 
> constructor
>   2. args is a T lvalue -> call the copy constructor
>   3. args is a T rvalue -> `b` should be move initialised, but 
> you get
> a compile error because it tries to pass an rvalue to the copy
> constructor which strictly reveices a ref arg, and that call is
> invalid.
>
> It leads to this:
>
> struct S
> {
>   this(ref inout(S) copyFrom) inout {} // <- copy ctor
>   this(S moveFrom) { this = moveFrom.move; } // <- !!! some 
> kind of
> move constructor?
> }
>
> The rvalue ctor becomes necessary whenever a copy constructor 
> exists, otherwise lots of meta breaks.
>
> ------------------------------
>
> Now, to top it off... if you inspect the move/emplace/forward 
> machinery in druntime, you'll notice that the implementations 
> of those functions are unbelievably hideous. They have to 
> carefully determine heaps of edge-case-ey junk, and then 
> manually call copy ctors, or use memcpy() and memset() to 
> manually perform a binary move operations.
>
> I have determined that in move/emplace/moveEmplace/forward 
> constructions, where it does correctly perform moves (run 
> through the memcpy() path) a series of times, the compiler does 
> NOT optimise these memcpy/memset's away, and the memory just 
> gets shuffled around a whole bunch between initial construction 
> and the resting location.
>
> Move semantics effectively don't work. They're a gross hack at 
> best.
>
> I suggest, the *language* desperately needs an `emplace` 
> semantic, something that can be recognised by the compiler and 
> cascade through chains of such work, where all that 
> edge-case-ey crap can be properly internalised.
>
> It's really sad to say that the C++'s rvalue (T&&) solution is 
> uncomparably simpler than the mess we have to do to express 
> `emplace` or `move` in D.
>
> It's embarrassing that the language can't express 
> emplace/move/forward/etc natively, and I think this should be 
> extremely high priority for a DIP, perhaps supported by the 
> dlang foundation.
>
> __traits(emplace, T, ptr, args...) ?
> Similar traits might exist for `forward` and friends too, and 
> they
> could be thinly wrapped in the functions in druntime.

Think this has been brought up before, moving in D is 'built-in' 
and is done as just a copy. Which is why there is that opMove 
DIP. In C++ it is just a type essentially. If you have a function 
that needs to forward it's parameters to another function, in C++ 
where you are just passing a reference in D that means making N 
copies, where N is the number of function calls you have to go 
through. Price of performance for "simplicity"?


More information about the Digitalmars-d mailing list