Date: Sat, 5 Aug 2023 22:05:04 -0400
On Sat, Aug 5, 2023 at 8:30 PM Mykola Garkusha via Std-Discussion
<std-discussion_at_[hidden]> wrote:
> This is still an inconclusive answer, which is the central question on this thread really.
>
> alignas(T) std::byte storage_[sizeof(T)];
> T *ptr_ = ::new (&storage_) T;
> T* ptr2 = reinterpret_cast<T *>(&storage_)
>
> Is using ptr2 well-defined just on the grounds that according to the aliasing rules accessing objects through std::byte, unsigned char or the DnamicType is well-defined?
Not in general; [basic.lval] para. 11 is specifically about reading
and writing scalar values through a glvalue, and nothing else. Since
ptr2 is a T* pointer pointing to an std::byte object, you can't do
most things with ptr2 that you could do with an arbitrary T* pointer,
e.g., you can't access its members with a ptr->field class member
access expression ([expr.ref] para. 8). Most (but not all) usual
operations require that the dynamic type of the pointee actually
matches the type of the pointer.
> On the contrary, the following is not well-defined and would need std::launder to provide the correct behavior given the size and alignment for T1 and T2 match.?
>
> T1 storage;
> T2 *ptr_ = ::new (&storage_) T2;
> T2* ptr2 = reinterpret_cast<T2 *>(&storage_) // UB?
> T2* ptr2 = std::launder(reinterpret_cast<T2 *>(&storage_)) // well-defined?
> ptr->~T2();
> ::new (&storage_) T1; //needed if T1 got non-trivial destructor
Yes, std::launder() is needed to have well-defined behavior in this example.
<std-discussion_at_[hidden]> wrote:
> This is still an inconclusive answer, which is the central question on this thread really.
>
> alignas(T) std::byte storage_[sizeof(T)];
> T *ptr_ = ::new (&storage_) T;
> T* ptr2 = reinterpret_cast<T *>(&storage_)
>
> Is using ptr2 well-defined just on the grounds that according to the aliasing rules accessing objects through std::byte, unsigned char or the DnamicType is well-defined?
Not in general; [basic.lval] para. 11 is specifically about reading
and writing scalar values through a glvalue, and nothing else. Since
ptr2 is a T* pointer pointing to an std::byte object, you can't do
most things with ptr2 that you could do with an arbitrary T* pointer,
e.g., you can't access its members with a ptr->field class member
access expression ([expr.ref] para. 8). Most (but not all) usual
operations require that the dynamic type of the pointee actually
matches the type of the pointer.
> On the contrary, the following is not well-defined and would need std::launder to provide the correct behavior given the size and alignment for T1 and T2 match.?
>
> T1 storage;
> T2 *ptr_ = ::new (&storage_) T2;
> T2* ptr2 = reinterpret_cast<T2 *>(&storage_) // UB?
> T2* ptr2 = std::launder(reinterpret_cast<T2 *>(&storage_)) // well-defined?
> ptr->~T2();
> ::new (&storage_) T1; //needed if T1 got non-trivial destructor
Yes, std::launder() is needed to have well-defined behavior in this example.
Received on 2023-08-06 02:05:16