move semantics are a mess

Manu turkeyman at gmail.com
Sun May 26 18:24:17 UTC 2019


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.


More information about the Digitalmars-d mailing list