Moving via relocate-and-construct

A slow-moving StackOverflow discussion thread alerted me to this interesting application of P1144 std::relocate (Godbolt):

On libc++ and MSVC, std::array<std::string, 1000> is a trivially relocatable type; but it’s not trivially move-constructible, because string’s move-constructor has to null out the source object. Moving a single string is tantamount to a memcpy plus a memset (to null out the source). So, move-constructing an array<string, 1000> is tantamount to a 1000-times-larger memcpy plus a 1000-times-larger memset. But no mainstream compiler is smart enough to fission the loop body in that way.

P1144 std::relocate gives us a name for array<string>’s operation that is tantamount to memcpy; and we already have a name (“default constructor”) for array<string>’s operation that is tantamount to memset. So — here’s the cool part — we can help our insufficiently-smart compiler see the right way to factor this code by writing the move-construction as a relocate plus a placement-new.

using A = std::array<std::string, 1000>;

A *move_to_heap(A& a) {
  // Compiler fails to fission the loop
  return new A(std::move(a));
}

A *move_to_heap(A& a) {
  // Compiler generates memcpy + memset
  auto *p = new A(std::relocate(&a));
  ::new (&a) A;
  return p;
}

Of course this trick isn’t general-purpose. It does work for non-trivially relocatable types (for which std::relocate rightly won’t generate a memcpy); but it doesn’t compile for types that aren’t default-constructible, nor does it work for types whose default constructor has unwanted side effects, or whose moved-from state is distinguishable from their default-constructed state. (As usual, std::pmr::string is an example.) Therefore I wouldn’t expect to see this trick in generic code.

But this is a neat application of the P1144 high-level primitives (is that an oxymoron?) to help the compiler see how to generate optimal code, without throwing up our hands and descending into undefined-behavior-land. I can’t think of a “standard-compliant” way to get the memcpy+memset codegen in today’s C++ — but with P1144’s vocabulary, it’s straightforward.

Posted 2023-12-06