I was doing some reading on std::launder
, and I thought of a scenario that the standard didn't seem to address.
cppreference.com defines std::launder(T *p)
like this:
Formally, given
- the pointer
p
represents the address A of a byte in memory- an object
x
is located at the address Ax
is within its lifetime- the type of
x
is the same as T, ignoring cv-qualifiers at every level- every byte that would be reachable through the result is reachable through p . . .
Then
std::launder(p)
returns a value of typeT*
that points to the objectx
. Otherwise, the behavior is undefined.
My reading of this is that "if p
points at a memory location where there lives a T
, then std::launder(p)
returns a pointer to that T
".
The examples I saw talked about std::launder
's utility in situations where one object has perished and another has begun its lifetime in a memory location, and you just need to tell the compiler about it. But if I'm reading it correctly, std::launder
can be used for more than that.
struct S {
int i;
S(int i, int j) : i(i), j(j) {}
private:
int j;
};
int main() {
S s(10, 20);
int *pi = reinterpret_cast<int*>(&s);
int *pi_good = std::launder(pi);
}
In this example, pi
is a int*
that points at a memory location where there lives an int
, but pointers don't just point to memory locations; they point to objects. And since reinterpret_cast
doesn't change the value of the pointer (because S
is not pointer-interconvertible with int
), pi
still points at the S
object. So dereferencing pi
is undefined behavior. This is where std::launder
comes in, and it gives us a pointer to the int
that actually lives there.
Now this extends the scope of std::launder
beyond the original case I mentioned earlier, where one object has died and another has taken its place because in this situation, we have two separate objects living in the same memory location at the same time, and we can use std::launder
to switch from one to the other and back*.
(This would also mean that std::launder(reinterpret_cast<T*>(std::rand()))
can sometimes be well defined.)
Now, a natural next question would be: If two objects of the same type live in the same memory location at the same time, and you launder a pointer to that memory location, which one does the new pointer point to?
#include <new>
struct Holder {
// unsigned char required to provide storage
unsigned char storage[4];
// make it a non-trivial type
~Holder() {};
template <typename T>
void hold() {
::new(static_cast<void *>(storage)) T;
}
};
int main() {
Holder h;
h.hold<Holder>();
Holder *ph = &h;
[[maybe_unused]] Holder *ph_laundered = std::launder(ph);
}
In this example, both the Holder
and the held object live in the same memory location at the same time and have the same type.
*The last bullet in the definition requires that an S*
cannot access any bytes that the int*
could not, which we can accomplish while maintaining S
's non-pointer-interconvertibility with int
by changing S
to class S { int i; };
where i
is private.
There are never supposed to be two different objects of similar type at the same address with overlapping lifetimes (Per [intro.object]p10). So std::launder
is not defined to tell you 'which one' it points to.
This is a standard defect, CWG2744.