On Mon, 30 May 2022 at 10:23, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Mon, May 30, 2022 at 11:37 AM Edward Catmur via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Mon, 30 May 2022 at 09:26, Maciej Cencora via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Can't we base relocation on move+destroy only? (or just memcpy + end
of scope in case of trivially relocatable types).

Obj a;
auto b = reloc a;

At 'reloc a' line, we either:
 1) construct 'b' by invoking move constructor from 'a', invoke
destructor for 'a', remove 'a' from scope
 2) construct 'b' by memcpy-ing 'a', do not invoke 'a' destructor,
remove ab' from scope

We expect there to exist types that are immovable and [non-trivially] relocatable.

FWIW, I (in P1144) consider that an oxymoron. A type that is immobile/immovable cannot be relocatable, because it's not movable. Immobility is for types that cannot be moved from place to place — like `std::mutex`, for example. Typically they cannot be moved because if you moved them then bad things would happen.
Similarly, if you "relocated" a `std::mutex`, bad things would happen.  You can't have a mutex suddenly change memory addresses and expect the rest of the program to just be cool with that.

That's the strongest version of immovability; types that must occupy the same storage throughout their lifetime. But there are weaker versions where a class type has no copy or move constructor but is nevertheless relocatable:

- types that have invariants that preclude having an empty state (gsl::non_null)
- types where adding an empty state would require changing layout (adding a flag)
- types where adding an empty state would require adding branches to the destructor

In each of these cases, you *could* add a move constructor, but it would worsen the programmer experience and add the possibility of bugs, since you need to handle an empty state solely for the purpose of relocation by move+destroy; these classes are not semiregular by design.

Perhaps it would help to introduce terminology distinguishing the strong case from the weaker case? Say, "immobile" for the strong case and "immovable" for the weaker case? Or, just "non-semiregular" for the weaker case, if you'd prefer.

The right answer to Maciej's question is: Because you expect there to exist types that are efficiently relocatable but not trivially relocatable.
One real-world example is
    struct S {
        std::any a; // non-trivially relocatable
        std::list<int> b;  // MSVC: trivially relocatable, inefficiently movable
To move-construct an `S`, you need to move the any (using its move-ctor) and then move the list (using its move-ctor, which allocates memory because MSVC uses a sentinel-node implementation).
But to relocate an `S`, you merely need to move-and-destroy the any (still using its move-ctor, presumably) and then relocate the list (using its relocation operation, which is tantamount to memcpy, and does not allocate).
Move-constructing an `S` allocates, and is non-noexcept.
Relocating an `S` doesn't need to allocate, and can be noexcept.
However, `S` is not trivially relocatable.

So, it's tempting to look for some syntax to express a relocation operation that is different from move+destroy and yet also different from memcpy.
That's what Sébastien is doing here (and also Pablo in N4158, and Denis in P0023).

The advantage of a syntax is that you can express trivial relocation simply by defaulting it, and it Does The Right Thing if any member becomes non-trivial.