9

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?

Community
  • 1
  • 1
DoctorMoisha
  • 1,613
  • 14
  • 25

1 Answers1

12

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::bytes. 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;
user17732522
  • 53,019
  • 2
  • 56
  • 105
  • 1
    Thank you. `the result of reinterpret_cast(&s) will not point to the Y object, but will remain pointing to the array` Why is that from compiler's point of view? Don't both pointers just point to the first byte in a sequence? – DoctorMoisha Aug 08 '22 at 06:05
  • @DoctorMoisha Since C++17 the value of a pointer in the abstract machine which describes the behavior of a C++ program in the standard is not only an address to some location in memory. C++ allows multiple objects to reside at the same address. The value of a pointer then is not just the address but instead points to one of these objects specifically. Of course in a real machine pointer values are implemented as just addresses. But the abstract notion allows the compiler to perform additional optimizations based on pointer aliasing analysis. The concept is known as _pointer provenance_. – user17732522 Aug 09 '22 at 19:14
  • @DoctorMoisha In the abstract language `std::launder` is used to switch a pointer value between such objects at the same address. Practically speaking it is an optimization barrier for the optimizations I mentioned. – user17732522 Aug 09 '22 at 19:20