C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Relocation in C++

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Mon, 30 May 2022 13:25:12 -0400
On Mon, May 30, 2022 at 12:53 PM Edward Catmur <ecatmur_at_[hidden]>
wrote:

> On Mon, 30 May 2022 at 10:23, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
>
>> On Mon, May 30, 2022 at 11:37 AM Edward Catmur via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>>
>>> 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.
>>
>
> 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: [e.g.]
> - types that have invariants that preclude having an empty state
> (gsl::non_null)
>
> 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.
>

I strongly discourage making any Memmi/Derrida–style subtle distinctions
between the words "immobile" and "immovable."

I suggest phrases like "types that have an `operator reloc` but no move
constructor" (describing the physical situation) or "types that are
conceptually movable but lack any moved-from state" (describing the
conceptual situation).

If you want to permit "types that are conceptually movable but lack any
moved-from state," then you need to flesh out the semantics of operations
like assignment and swapping. I've seen a lot in this thread about
relocating-into-a-constructor, but not much IIRC about
relocating-into-an-assignment or swapping-by-relocating.

    gsl::non_null<int*> p1, p2, p3 = ...;
    gsl::non_null<int*> q1 = reloc p1; // OK in your world
    q1 = reloc p2; // OK??
    std::swap(q1, p3); // OK??

*Conceptually*, all of these operations should be legal, right? None of
them leave a gsl::non_null<int*> object in a moved-from state. Can your
`operator reloc` actually handle all of them, though? (This would go in
the paper.)


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.
>>
>
Maciej replies:
> Ok, but how can a type that needs custom relocation still be efficiently
relocatable?
> E.g. if you need a fixup for self-reference, then relocating won't ever
be as efficient as memcpy.

By "efficient relocation," I mean that relocation can be done more
efficiently than move-and-destroy. My type `S` above is a concrete example.
Relocating won't ever be as efficient as memset-to-zero, either, but that
doesn't matter — the first rule of programming is "Be correct." It doesn't
matter how *fast* you do an operation if you get its semantics wrong.
Trying to relocate an `S` with memcpy will produce *wrong answers*. Trying
to relocate it with move+destroy will produce correct answers, but will be
slower than the "efficient" relocation operation that I describe above.


Edward writes:

> 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.
>

Pedantic terminology nit: Just like with any other special member,
`=default` on your `operator reloc` wouldn't mean it was *trivial*; it
would just mean it was *defaulted*, i.e., *memberwise*.
struct A { std::string s; operator reloc(A&&) = default; }; // memberwise
and trivial
struct B { std::any a; operator reloc(B&&) = default; }; // memberwise and
non-trivial

–Arthur

Received on 2022-05-30 17:25:24