What is the purpose of std::launder?

std::launder is aptly named, though only if you know what it’s for. It performs memory laundering.

Consider the example in the paper:

struct X { const int n; };
union U { X x; float f; };
...

U u = {{ 1 }};

That statement performs aggregate initialization, initializing the first member of U with {1}.

Because n is a const variable, the compiler is free to assume that u.x.n shall always be 1.

So what happens if we do this:

X *p = new (&u.x) X {2};

Because X is trivial, we need not destroy the old object before creating a new one in its place, so this is perfectly legal code. The new object will have its n member be 2.

So tell me… what will u.x.n return?

The obvious answer will be 2. But that’s wrong, because the compiler is allowed to assume that a truly const variable (not merely a const&, but an object variable declared const) will never change. But we just changed it.

[basic.life]/8 spells out the circumstances when it is OK to access the newly created object through variables/pointers/references to the old one. And having a const member is one of the disqualifying factors.

So… how can we talk about u.x.n properly?

We have to launder our memory:

assert(*std::launder(&u.x.n) == 2); //Will be true.

Money laundering is used to prevent people from tracing where you got your money from. Memory laundering is used to prevent the compiler from tracing where you got your object from, thus forcing it to avoid any optimizations that may no longer apply.

Another of the disqualifying factors is if you change the type of the object. std::launder can help here too:

alignas(int) char data[sizeof(int)];
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));

[basic.life]/8 tells us that, if you allocate a new object in the storage of the old one, you cannot access the new object through pointers to the old. launder allows us to side-step that.

Leave a Comment