Polymorphic types aren’t trivially relocatable

One of the (minor, non-key) differences between P1144 trivial relocatability and P2786R1 trivial relocatability (§11.3, page 19) is that P2786R1 accidentally considers polymorphic types to be trivially relocatable by default. I say “accidentally” because of course this cannot really be permitted. Godbolt:

struct B {
    virtual int getter() const { return 1; }
    virtual ~B() = default;
};
struct D : B {
    int d = 2;
    int getter() const override { return d; }
};

alignas(D) char dbuf[sizeof(D)];
alignas(B) char bbuf[sizeof(B)];
B *d = ::new (dbuf) D();
B *b = (B*)bbuf;
// RELOCATE
printf("Expect 1: %d\n", b->getter());
b->~B();

The “RELOCATE” comment should be replaced with the code to relocate a B object from *d into *b. In P1144, the spelling is simply

std::relocate_at(d, b);

and the library decides whether to use trivial relocation behind the scenes. In P2786R1, the spelling is

static_assert(std::is_trivially_relocatable_v<B>);
std::trivially_relocate(d, d+1, b);

In P1144, B is not considered trivially relocatable, and the code above Just Works.

Why is B not trivially relocatable? P1144 offers two answers: One, at the compiler level, B doesn’t meet [basic.types.general]’s criterion that a trivially relocatable type mustn’t have virtual member functions. Two, at the human level, we can see that B fails to model [concept.relocatable]’s semantic requirements.

In P2786R1, B is (“accidentally”) considered trivially relocatable, and the code above has undefined behavior at runtime.

*d’s vptr is memmoved into *b, and then the virtual call b->getter() invokes D::getter through that (now-erroneous) vptr, which tries to access the data member d of a D object that doesn’t actually exist.

The Godbolt above uses my own Clang/libc++ implementation of P1144R8 P1144R11, and Corentin Jabot’s Clang implementation of P2786R0 P2786R12 (with none of the library components). I’ve told Corentin he should feel free to adopt any pieces of my libc++ implementation that would help get the P2786 demonstration into a working state. Of course, the authors of P2786 could also help by refactoring P2786’s library clauses to match P1144’s. :) As you can see, the P1144 library facilities tend to be more usable than P2786’s.

UPDATE, 2025: This part has been done now, by P3516 “Uninitialized algorithms for relocation” — thanks Louis Dionne and Giuseppe D’Angelo!

This might be a good time to remind folks that my Clang/libc++ implementation also provides a [[clang::maybe_trivially_relocatable]] attribute implementing P2786’s “dull knife” semantics (but without P2786R1’s sharp-when-you-didn’t-want-it parts, like its treatment of polymorphic types). So you can experiment with a full libc++ implementation of dull-knife semantics simply by passing -D_LIBCPP_TRIVIALLY_RELOCATABLE_IF(x)=[[clang::maybe_trivially_relocatable(x)]]. (Godbolt.)

Posted 2023-06-24