I was tinkering with the example given on the cppreference launder web page.
The example shown below suggest that either I misunderstood something and introduced UB or that there is a bug somewhere or that clang is to lax or too good.
- In doit1(), I believe the optimization done by GCC is incorrect (the function returns 2) and does not take into account the fact that we use the placement new return value.
- In doit2(), I believe the code is also legal but with GCC, no code is produced ?
In both situations, clang provides the behavior I expect. On GCC, it will depend on the optimization level. I tried GCC 12.1 but this is not the only GCC version showing this behavior.
#include <new>
struct A {
virtual A* transmogrify(int& i);
};
struct B : A {
A* transmogrify(int& i) override {
i = 2;
return new (this) A;
}
};
A* A::transmogrify(int& i) {
i = 1;
return new (this) B;
}
static_assert(sizeof(B) == sizeof(A), "");
int doit1() {
A i;
int n;
int m;
A* b_ptr = i.transmogrify(n);
// std::launder(&i)->transmogrify(m); // OK, launder is NOT redundant
// std::launder(b_ptr)->transmogrify(m); // OK, launder IS redundant
(b_ptr)->transmogrify(m); // KO, launder IS redundant, we use the return value of placment new
return m + n; // 3 expected, OK == 3, else KO
}
int doit2() {
A i;
int n;
int m;
A* b_ptr = i.transmogrify(n);
// b_ptr->transmogrify(m); // KO, as shown in doit1
static_cast<B*>(b_ptr)->transmogrify(m); // VERY KO see the ASM, but we realy do have a B in the memory pointed by b_ptr
return m + n; // 3 expected, OK == 3, else KO
}
int main() {
return doit1();
// return doit2();
}
Code available at: https://godbolt.org/z/43ebKf1q6
The UB comes from accessing
A i;to call the destructor at the end of scope without laundering the pointer. That can let the compiler assumeihas not been destroyed by the storage reuse before then.You need something more like: