Date: Mon, 30 May 2022 10:53:05 -0600
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:
>
>> On Mon, 30 May 2022 at 09:26, Maciej Cencora via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> Can't we base relocation on move+destroy only? (or just memcpy + end
>>> of scope in case of trivially relocatable types).
>>>
>>> I.e.:
>>> 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.
Relevant:
>
> https://quuxplusone.github.io/blog/2019/02/20/p1144-what-types-are-relocatable/
>
>
wrote:
> On Mon, May 30, 2022 at 11:37 AM Edward Catmur via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Mon, 30 May 2022 at 09:26, Maciej Cencora via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> Can't we base relocation on move+destroy only? (or just memcpy + end
>>> of scope in case of trivially relocatable types).
>>>
>>> I.e.:
>>> 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.
Relevant:
>
> https://quuxplusone.github.io/blog/2019/02/20/p1144-what-types-are-relocatable/
>
>
Received on 2022-05-30 16:53:22