The following code example is from cppreference on std::launder
:
alignas(Y) std::byte s[sizeof(Y)];
Y* q = new(&s) Y{2};
const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined behavior
It seems to me that third line will result in undefined behaviour because of [basic.life]/6 in the standard:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated ... The program has undefined behavior if ... the pointer is used to access a non-static data member or call a non-static member function of the object.
Why didn't placement new start the lifetime of object Y
?
The placement-new did start the lifetime of the Y
object (and its subobjects).
But object lifetime is not what std::launder
is about. std::launder
can't be used to start the lifetime of objects.
std::launder
is used when you have a pointer which points to an object of a type different than the pointer's type, which happens when you reinterpret_cast
a pointer to a type for which there doesn't exist an object of the target type which is pointer-interconvertible with the former object.
std::launder
can then (assuming its preconditions are met) be used to obtain a pointer to an object of the pointer's type (which must already be in its lifetime) located at the address to which the pointer refers.
Here &s
is a pointer pointing to an array of sizeof(Y)
std::byte
s. There is also an explicitly created Y
object sharing the address with that array and the array provides storage for the Y
object. However, an array (or array element) is not pointer-interconvertible with an object for which it provides storage. Therefore the result of reinterpret_cast<Y*>(&s)
will not point to the Y
object, but will remain pointing to the array.
Accessing a member has undefined behavior if the glvalue used doesn't actually refer to an object (similar) to the glvalue's type, which is here the case as the lvalue refers to the array, not the Y
object.
So, to get a pointer and lvalue to the Y
object located at the same address as &s
and already in its lifetime, you need to call std::launder
first:
const int f = std::launder(reinterpret_cast<Y*>(&s))->z;
All of this complication can of course be avoided by just using the pointer returned by new
directly. It already points to the newly-created object:
const int f = q->z;