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