Consider the following code:
#include <iostream>
#include <string>
union U
{
std::string s;
U()
{
new(&s) std::string;
}
void Set(std::string new_s)
{
s.~basic_string();
new(&s) std::string(std::move(new_s));
}
~U()
{
s.~basic_string();
}
};
int main()
{
U u;
u.Set("foo");
std::cout << u.s << '\n'; // Do I need `std::launder` here?
}
I know that if I used a byte array instead of a union, I'd have to std::launder(&s)
to access the string. But do I need it here?
The common sense answer seems to be "no", but then where does the standard bless union
s to not need std::launder
?
The member access expression u.s
is simply defined to result in a lvalue to the s
member subobject of the union. (That is true even if u.s
is not in its lifetime, i.e. not active.) See [expr.ref]/6.2.
So there can't be any need to launder. You already have a glvalue to the object you want.
Even if you start from a pointer to the union object instead and attempt to use a pointer cast approach, more similar to the byte array approach, then it will still work without std::launder
, e.g.:
std::cout << *reinterpret_cast<std::string*>(&u) << '\n';
That is because a union object is pointer-interconvertible with any of its member subobjects per [basic.compound]/4.2, meaning that the reinterpret_cast
will produce a pointer to the member subobject immediately per [expr.static.cast]/14.
std::launder
is required if the result of a cast still points to the original object due to lack of pointer-interconvertibility between the source and the target object.
And also, per [basic.object]/2 together with [basic.life]/8 the objects you create with new
will become member subobjects of the union and s
in u
will designate the most recent such subobject, because they satisfy all of the requirements mentioned in those paragraphs (in particular exact overlap with s
and exact type match with s
).
Therefore s.u
will also not accidentally refer to the previous std::string
subobject that has been replaced with a later new
.