std::launder: the most obscure new feature of C++17
21 Oct 2016Proposal P0137R1
introduces a new function into the C++ standard library called std::launder
.
Unfortunately, this proposal is hard to understand for mere mortals.
This is what Botond Ballo writes about std::launder
in his trip report
to the C++ Standards Commitee meeting:
Don’t ask. If you’re not one of the 5 or so people in the world who already know what this is, you don’t want or need to know.
But nevertheless let’s try to get some intuition about how std::launder
works.
The original proposal provides an example:
Here we first initialize u
with 1
(so x.n
is set to 1, and x
becomes the active member of the union). Next, 5 is assigned to u.f
and
f
becomes active. Finally, we do a placement new and overwrite u
with
the new value 2. The problem here is that n
is const-qualified and the
optimizer is allowed to assume that it will always remain equal to 1. So,
std::launder
acts as an optimization barrier that prevents the optimizer from
performing constant propagation.
This example is explained in more detail in the answer to a question on Stack Overflow: What is the purpose of std::launder?.
Alisdair Meredith also briefly mentions a real-life use case in his talk “C++17 in Breadth” from CppCon 2016. He tells us that this function might be handy when dealing with containers of const-qualified elements.
Finally, let’s look at another example. The GCC maintainers initially
implemented
std::launder
as a no-op, i.e. something similar to:
but Richard Smith (the lead developer of Clang) came up with an example which is compiled correctly by Clang, but miscompiled by GCC:
The optimizer folds h
into something similar to:
(when compiled correctly, this function should return 3).
This example defines a class hierarchy consisting of two classes: A
and B
.
A
defines a virtual member function f
which is overridden in B
.
Function f
in A
does a placement new on the this
pointer and replaces the
object with a newly created instance of B
and then returns 1. f
in B
does
the opposite, i.e. replaces the object with an instance of A
and then returns 2.
Function h
allocates an instance of A
on the stack and invokes f
twice. Note that the second call is performed on the “laundered” pointer.
This example resembles the first one, but this time the mutated const
value is the vptr
of a
(i.e., a pointer to the vtable of either A
or B
).
GCC has an optimization called “devirtualization”, i.e. the optimizer tries
to figure out the dynamic type of each object in each virtual call and
if the type is known, the compiler replaces such virtual call with a direct call.
This time the optimizer is overly optimistic and fails to recognize that even
stack-allocated objects can now change their dynamic type.
Note that C++17 support in GCC is still experimental, and such bugs are expected. This one has been spotted and a fix already exists.