In the classic talk An (In-)Complete Guide to C++ Object Lifetimes by Jonathan Müller, there is a useful guideline as follows:
Q: When do I need to use std::launder?
A: When you want to re-use the storage of:
- const heap objects,
- base classes, or
[[no_unique_address]]
members.
The first bullet is obvious, but the other two bullets are a bit hard to grasp.
For example, consider the following code:
struct Empty {};
struct A {
[[no_unique_address]] Empty e = {};
char c = {};
};
int main() {
auto a = A{};
auto p1 = &a.e;
auto p2 = new (&a.c) char;
(void)p2->e; // OK.
(void)*p1; // UB due to bullet 3.
}
The UB is originally due to this C++ standard rule ①:
An object o1 is transparently replaceable by an object o2 if ... and neither o1 nor o2 is a potentially-overlapping subobject.
What I cannot understand are:
- Why does the C++ standard impose such a special restriction on potentially-overlapping subobjects?
- What bad consequences would follow without this restriction?
① The rule covers both bullet 2 (in case of Empty Base Optimization) and bullet 3.
According to cppref:
If the
[[no_unique_address]]
member is not empty, any tail padding in it may be also reused to store other data members.
The following code can demonstrate why std::launder
is necessary in such a scenario:
struct alignas(std::uint16_t) A { std::uint8_t x; };
struct B {
[[no_unique_address]] A a = {};
std::uint8_t y = {};
};
static_assert(2 == sizeof(A)); // likely true
static_assert(2 == sizeof(B)); // likely true
int main() {
auto b = B{.a{.x = 7}, .y = 8};
assert(8 == b.y); // true
assert(&b.a.x + 1 == &b.y); // likely true
b.a.~A();
new (&b.a) A{.x = 9};
// Now the value of y is indeterminate.
(void)std::launder(&b)->y; // Well-defined. Read the latest value of y.
(void)b.y; // UB. The compiler may assume the value of y is still 8!
}