“The problem is that we attempt to solve the simplest questions cleverly, thereby rendering them unusually complex. One should seek the simple solution.”
― Anton Chekhov
alignas(T) std::byte storage[sizeof(T)]; ::new (&storage) T(); // ... T *ptr_ = reinterpret_cast<T*>(&storage); // UB
The object that is nested within the storage
array is not
pointer-interconvertible with it [basic.compound] p4.
According to the Standard users have to
call std::launder
to avoid UB.
That behavior is surprising for the language users. Moreover, popular
compilers do not require std::launder
call in that place
and produce the expected assembly without it.
This proposal attempts to remove UB in that place, standardize existing practice and simplify language usage.
boost::optional
uses aligned storage and placement new to store a value. Storing a pointer from placement new may increase the size of an class.utils::FastPimpl
from the userver framework uses byte array and placement new to store a value. Storing a pointer from placement new increases the size of an class.std::launder
is required to fix the above cases. However it
is counterintuitive, decreases the code readability
and requires in-depth knowledge of the Standard to realize that there is an
issue from the point of the Standard (but in practice there is no problem!).
Adjust [basic.compound] p4:
Two objects a and b are pointer-interconvertible if: - they are the same object, or - one is a union object and the other is a non-static data member of that object ([class.union]), or - one is a standard-layout class object and the other is the first non-static data member of that object or any base class subobject of that object ([class.mem]), or - one is an element of an array ofstd::byte
orunsigned char
and the other is an object for which the array provides storage, created at the address of the array element, or - there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible. If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_cast ([expr.reinterpret.cast]). [Note 4: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note]
Many thanks to Артём Колпаков (Artyom Kolpakov) for asking the question on that topic at STD discussion list and for drawing my attention to the problem. Thanks to Brian Bi for answering the question at STD discussion list.
Thanks to Andrey Erokhin, Timur Doumler, Roman Rusyaev, Mathias Stearn and all the mailing lists members for feedback and help!
Thanks to Andrey Erokhin and Jason Merrill for the wording help!