Date: Wed, 17 Aug 2022 18:40:59 +0200
My bad, I didn't know that function-try-block even existed in the language.
I admit I never saw it in production code :/ Thank you for clarifying that
point.
Having that in mind, adding extra initializers between "try" and ':' seems
like a viable solution.
Hm, but you can write:
>
> T& operator=(T rhs) { std::destroy_at(this); return *new (this) T(reloc
> rhs); }
>
> If we permit operator=(T), as a special member function, the same aliasing
> power as the relocating constructor, then it invokes precisely one
> destructor call and one relocating constructor call, which is perfect. If
> it doesn't have aliasing on its prvalue rhs parameter (admittedly that
> might be tricky from an ABI standpoint), then there's potentially two calls
> to the relocating constructor, but they can be elided together.
>
Indeed, but for that to hold we do need to have a destroy-and-relocate
implementation behind the scene right? copy-and-swap, even with the
aliasing, costs at least two relocations + one destruction:
In: `T x, y; y = reloc x;`
A possible swap-based implementation would look like (considering the
reloc-assignment is inlined):
alignas(T) std::byte buff[sizeof(T)];
std::relocate_at(&y, &buff);
std::relocate_at(&x, &y);
std::relocate_at(&buff, &x);
std::destroy_at(&x); // destroy previous `y` content
Note that the last std::relocate_at call could be alleviated, as 'x' cannot
to be reused:
alignas(T) std::byte buff[sizeof(T)];
std::relocate_at(&y, &buff);
std::relocate_at(&x, &y);
std::destroy_at(reinterpret_cast<T*>(&buff)); // destroy previous `y`
content
Having written that, I don't understand how this is better than:
std::destroy_at(&y);
std::relocate_at(&x, &y);
> What feels unsafe about destroy-and-relocate is that we destruct an object
>>> within a member function. We can tweak things around and turn the
>>> reloc-assignment operator into a static function behind the scene:
>>> T& operator=(T src) = reloc; // same as: static T& assign_reloc(T& dst,
>>> T& src) { std::destroy_at(&dst); return *std::relocate_at(&src, &dst); }
>>>
>>> Here we no longer have the scary std::destroy_at(this), and we avoid
>>> self-destruction in a member function (which may be an UB?).
>>>
>>
>> That's still destroying an object from within its member function, just
>> not directly. It's fine, though; `delete this` has always been legal, it's
>> just never been a sign of good code. One library I use regularly
>> (rapidjson) uses destroy-and-rebuild for (lvalue) assignment, and it works
>> fine - as long as you sort out exception safety.
>>
>
How am I destroying from a member function?
Is the reloc-assignment such a blocking point for this proposal? Can't we
leave it out at first, knowing that users can still easily write their own
(albeit it won't be defaulted nor aliased)? Can't a second proposal solve
this specific point once we have more user experience as you mentioned?
Regards,
Sébastien
I admit I never saw it in production code :/ Thank you for clarifying that
point.
Having that in mind, adding extra initializers between "try" and ':' seems
like a viable solution.
Hm, but you can write:
>
> T& operator=(T rhs) { std::destroy_at(this); return *new (this) T(reloc
> rhs); }
>
> If we permit operator=(T), as a special member function, the same aliasing
> power as the relocating constructor, then it invokes precisely one
> destructor call and one relocating constructor call, which is perfect. If
> it doesn't have aliasing on its prvalue rhs parameter (admittedly that
> might be tricky from an ABI standpoint), then there's potentially two calls
> to the relocating constructor, but they can be elided together.
>
Indeed, but for that to hold we do need to have a destroy-and-relocate
implementation behind the scene right? copy-and-swap, even with the
aliasing, costs at least two relocations + one destruction:
In: `T x, y; y = reloc x;`
A possible swap-based implementation would look like (considering the
reloc-assignment is inlined):
alignas(T) std::byte buff[sizeof(T)];
std::relocate_at(&y, &buff);
std::relocate_at(&x, &y);
std::relocate_at(&buff, &x);
std::destroy_at(&x); // destroy previous `y` content
Note that the last std::relocate_at call could be alleviated, as 'x' cannot
to be reused:
alignas(T) std::byte buff[sizeof(T)];
std::relocate_at(&y, &buff);
std::relocate_at(&x, &y);
std::destroy_at(reinterpret_cast<T*>(&buff)); // destroy previous `y`
content
Having written that, I don't understand how this is better than:
std::destroy_at(&y);
std::relocate_at(&x, &y);
> What feels unsafe about destroy-and-relocate is that we destruct an object
>>> within a member function. We can tweak things around and turn the
>>> reloc-assignment operator into a static function behind the scene:
>>> T& operator=(T src) = reloc; // same as: static T& assign_reloc(T& dst,
>>> T& src) { std::destroy_at(&dst); return *std::relocate_at(&src, &dst); }
>>>
>>> Here we no longer have the scary std::destroy_at(this), and we avoid
>>> self-destruction in a member function (which may be an UB?).
>>>
>>
>> That's still destroying an object from within its member function, just
>> not directly. It's fine, though; `delete this` has always been legal, it's
>> just never been a sign of good code. One library I use regularly
>> (rapidjson) uses destroy-and-rebuild for (lvalue) assignment, and it works
>> fine - as long as you sort out exception safety.
>>
>
How am I destroying from a member function?
Is the reloc-assignment such a blocking point for this proposal? Can't we
leave it out at first, knowing that users can still easily write their own
(albeit it won't be defaulted nor aliased)? Can't a second proposal solve
this specific point once we have more user experience as you mentioned?
Regards,
Sébastien
Received on 2022-08-17 16:41:12