Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of std::launder?

P0137 introduces the function template std::launder and makes many, many changes to the standard in the sections concerning unions, lifetime, and pointers.

What is the problem this paper is solving? What are the changes to the language that I have to be aware of? And what are we laundering?

like image 449
Barry Avatar asked Sep 08 '16 04:09

Barry


Video Answer


2 Answers

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:

aligned_storage<sizeof(int), alignof(int)>::type data; 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.

like image 184
Nicol Bolas Avatar answered Sep 21 '22 17:09

Nicol Bolas


std::launder is a mis-nomer. This function performs the opposite of laundering: It soils the pointed-to memory, to remove any expectation the compiler might have regarding the pointed-to value. It precludes any compiler optimizations based on such expectations.

Thus in @NicolBolas' answer, the compiler might be assuming that some memory holds some constant value; or is uninitialized. You're telling the compiler: "That place is (now) soiled, don't make that assumption".

If you're wondering why the compiler would always stick to its naive expectations in the first place, and would need to you to conspicuously soil things for it - you might want to read this discussion:

Why introduce `std::launder` rather than have the compiler take care of it?

... which lead me to this view of what std::launder means.

like image 24
einpoklum Avatar answered Sep 24 '22 17:09

einpoklum