360

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?

L. F.
  • 19,445
  • 8
  • 48
  • 82
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    Are you asking about the paper itself or about [`std::launder`](http://en.cppreference.com/w/cpp/utility/launder)? `std::launder` is used to "obtain a pointer to an object created in storage occupied by an existing object of the same type, even if it has const or reference members." – txtechhelp Sep 08 '16 at 04:25
  • 9
    useful [link](https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/ko5ceM4szIE) on the subject. Also this question http://stackoverflow.com/questions/27003727/does-this-really-break-strict-aliasing-rules – Paul Rooney Sep 08 '16 at 04:30
  • This has now been released in VC2017 in version 15.7.0 – Damian May 08 '18 at 00:47
  • According to the std, pointers are trivial types so launder doesn't do anything. ;) – curiousguy Dec 01 '18 at 10:26

3 Answers3

378

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.

Community
  • 1
  • 1
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 55
    So is my tl;dr correct: "laundering is basically for non-UB type punning"? – druckermanly Sep 08 '16 at 05:16
  • 2
    But you _have_ done a reinterpret_cast. Why isn't it something like `std::launder(&data)` (where the actual formal parameter is a void pointer)? – Random832 Sep 08 '16 at 06:23
  • 10
    @Random832 Because you don't always need the cast. The purpose of launder is not to perform a type-cast. It's to make the type-cast (and various other forms of cleverness which do not involve casts) legal. If you just did the type-cast by itself, you would have UB by the strict aliasing rule. – Kevin Sep 08 '16 at 06:58
  • 7
    Is there a reason why you need the union in your example? Wouldn't the same be true already for ordinary structs? – ComicSansMS Sep 08 '16 at 07:44
  • 22
    Could you explain why this is true? *"Because `n` is a `const` variable, the compiler is free to assume that `u.x.n` shall always be 1."* Where in the standard does it say that? I ask because the very problem you pointed out would seem to imply to me that it is false in the first place. It should only be true under the as-if rule, which fails here. What am I missing? – user541686 Sep 08 '16 at 08:39
  • "But that's wrong, because the compiler is allowed to assume that a truly const variable ... will never change. But we just changed it." Why can't the compiler figure that out on its own (at least in this example)? – jamesdlin Sep 08 '16 at 08:57
  • 11
    @Mehrdad [basic.life]/8: "*If, [...] a new object is created at the storage location which the original object occupied [...] the name of the original object will automatically refer to the new object [...] if: [...] the type [...] does not contain any non-static data member whose type is const-qualified or a reference type [...]*" – ecatmur Sep 08 '16 at 08:59
  • 10
    `&u.x` is a pointer to const int. Why is it allowed to use a pointer to const data in a placement new? – Claas Bontus Sep 08 '16 at 09:02
  • @ecatmur: We need the "else" clause of that second "if" here though, don't we? Like, for example, what if there was such a data member, but it was never accessed? Would its existence really matter? – user541686 Sep 08 '16 at 10:02
  • 11
    @ClaasBontus It is a pointer to a ``X``, not a ``const int``. And you need to allow this if you want placement new to work on structures with ``const`` members, which you generally do want. Even ``const int`` kind of makes sense. Think about custom allocators for vast amounts of small, equally sized objects which place them all in a block of memory allocated for that purpose. – Jonas Schäfer Sep 08 '16 at 11:54
  • 7
    @Mehrdad: "*Like, for example, what if there was such a data member, but it was never accessed?*" Irrelevant. [basic.life]/8 spells out when you need to launder your memory. The fact that a particular compiler might, on a particular structure, not require it doesn't matter. The *standard* requires it. – Nicol Bolas Sep 08 '16 at 12:30
  • 16
    How much can we sidestep that aliasing rule? Like `template T* alias_cast(U* ptr) { return std::launder(reinterpret_cast(ptr)); }` How UB is that? – Barry Sep 08 '16 at 13:49
  • 17
    @Barry Very; if there's no objects of type T located at the address `ptr` represents, then you break `launder`'s precondition, so there's no point talking about the outcome. – T.C. Sep 08 '16 at 17:52
  • 9
    @user2899162 Well, no. It's for 'punning' names or pointers, such that they can legally be used to access different object instances/lifetimes than their original referents. It's not type-punning, which remains as UB as ever. – underscore_d Sep 08 '16 at 18:35
  • @NicolBolas: I'm not sure where I said anything about particular compilers or particular structures. I was only talking about the usage, and I was saying that sentence needs an "else" clause (or we need more context) to address this situation. – user541686 Sep 09 '16 at 11:42
  • 3
    @Mehrdad: We don't need an else clause there. The situation you want to be addressed is addressed as is. The standard makes it very clear that it *does not matter* if the `const`-qualified member is ever accessed or not. The fact that the member exists at all is sufficient to require `launder` on the entire containing type. – Nicol Bolas Sep 11 '16 at 04:20
  • @underscore_d: What is the proper way to do in-place type punning if not by laundering the pointers – supercat Sep 11 '16 at 22:06
  • 3
    @supercat: C++ does not allow you to do "in-place type punning". Not in a general sense. The closest you can get is the new wording for unions, but that doesn't allow you to access the value through an object of a different type. C++ only supports type punning by copy. – Nicol Bolas Sep 11 '16 at 23:08
  • 5
    @NicolBolas: Can you think of any reason why people who are supposedly interested in "optimization" would fail to provide a means by which programmers can safely use techniques that would provide a 2x or better speed boost (and in some cases could allow for orders of magnitude speed boost when dealing with certain APIs). I understand the need to have compilers make optimizing assumptions *in cases where they have no reason to believe they're unsafe*, but requiring programmers to write inefficient code doesn't seem like a recipe for good performance. – supercat Sep 11 '16 at 23:17
  • 10
    @supercat: "*Can you think of any reason...*" You're complaining to the wrong person. If you don't like how C++ is defined in this regard, take it up with the standards committee. I'm just explaining how the standard says things work. – Nicol Bolas Sep 11 '16 at 23:30
  • 21
    @NicolBolas A good optimising compiler will optimise your correct solution of `memcpy` into an in-place reinterpretation on supported (i.e. lax alignment) platforms _anyway_. – underscore_d Sep 12 '16 at 08:59
  • 10
    To confirm that I understand this properly, if you rewrote that last section as `int* totallyNewPtr = new (&data) int;`, there would be no need to launder anything because `totallyNewPtr` is a fresh pointer to the object that doesn't need to be accessed through any expired objects? – templatetypedef Jan 13 '17 at 01:14
  • 6
    @templatetypedef So long as you don't use the other pointer, yes. – Nicol Bolas Jan 13 '17 at 01:55
  • 1
    I wonder whether future implementations will chase [this use](http://stackoverflow.com/a/41655864/1000282) and [try to rule it out](https://www.reddit.com/r/cpp/comments/5noem9/a_personal_tale_on_a_special_value/dcdkwtg/) at compilation time, because currently it works :) – oblitum Jan 15 '17 at 20:52
  • 1
    @pepper_chico Nice question, that you linked. Pity that so many people around here worship the language standards so much as their gods that they forget that at some point, the standards have to be implemented on real machines which may require doing things that are undefined by the letter of the standard. Dereferencing null pointers may definitely be among these things. I would have upvoted both your question and your answer, had it not been deleted already. – cmaster - reinstate monica Sep 15 '17 at 12:11
  • 1
    @cmaster I've saved it [here](http://nosubstance.me/post/dereferencing-null-pointers/). The question got closed in less than 15 minutes, and deleted in less than 24 hours. Meta didn't care too. And, I actually expected and did on purpose. As safeguard I've prepared both question and answer to post at the same time before it could get closed. This happened and I was aware people were already trying to answer and couldn't anymore. I've stopped posting question/answers after this. I just wanted to see how unfriendly it has got. There was even someone asking if I know Latin to use a given term. – oblitum Sep 15 '17 at 13:43
  • @NicolBolas I believe I have found an other use case for std::launder but I do get no [answer](https://stackoverflow.com/questions/48189026/using-stdlaunder-to-get-a-pointer-to-an-active-object-member-from-a-pointer-to?noredirect=1#comment83365020_48189026). You might be interested in answering it. – Oliv Jan 10 '18 at 17:04
  • 1
    @cmaster: The more important point that people miss is that the one of the goals of the Committee's classifying various things as UB was "to allow a certain variety among implementations which permits quality of implementation to be an active force in the marketplace as well as to allow certain popular extensions, without removing the cachet of conformance to the Standard." The fact that a *conforming* implementation would be allowed to jump the rails in a certain case doesn't imply any judgment that such behavior would be appropriate in quality implementations. – supercat Aug 09 '18 at 19:15
  • 2
    Are there known cases that actually require it on known compilers? The example above doesn't show a need for launder in clang with --std=c++11 or --std=c++17 even with -O3. – Ben Sep 19 '18 at 20:28
  • 1
    @Ben: Does it matter? If all you care about is whether compilers will "work" today with some piece of code, you can find out by just checking them. But without removing the UB from your code, you have no effective guarantee that a compiler won't suddenly decide to make your code not work. Or that it might work in one instance and not in another. Or whatever. – Nicol Bolas Sep 19 '18 at 20:47
  • 1
    It matters in that while I don’t want UB in my code, it’s particularly satisfying to see a language feature like this actually solve a problem. I’m all for language pedantry and solving hypothetical problems, but seeing behavior get fixed is much more satisfying. – Ben Sep 20 '18 at 21:49
  • 1
    I didn't get this answer at all. `launder` is a library function. What does it actually do? – einpoklum Apr 27 '19 at 13:36
  • 1
    @einpoklum: It allows you to access the object behind a pointer when the standard would otherwise say that you can't. As in the case described above. After placement-new, `u.x.n` cannot be used to access the newly-created object, because of the cited rule. Using `launder` allows you to access that object. – Nicol Bolas Apr 27 '19 at 13:39
  • @NicolBolas: I don't see how a library function, as opposed to a language feature, can do that. – einpoklum Apr 27 '19 at 15:11
  • 4
    @einpoklum: It can do that because the standard *says it can*. There is no distinction between the language and the library; they are all one thing, and they all have de-jure authority over C++. So if the standard says that `launder` provides a pointer to the live object in that memory, then it provides a pointer to the live object in that memory. – Nicol Bolas Apr 27 '19 at 15:12
  • @NicolBolas: But standard library functions are implemented as C++ code, are they not? Is this one an exception? – einpoklum Apr 27 '19 at 15:21
  • 3
    @einpoklum: That's a matter of what "implemented" means. The behavior of `launder` with regard to the generation of code is that you get well-defined behavior in certain circumstances. That behavior is generated by the *compiler*; it sees that you have performed memory laundering and it therefore can no longer assume things it would otherwise be able to assume. That is, when the optimizer is going through and turning your `u.x.n` uses into the literal `1`, it hits `launder` and goes "Oops, can't see through that" and stops. – Nicol Bolas Apr 27 '19 at 15:26
  • 1
    Isn't this just a problem of the compiler failing to recognise that placement new modifies the object? While `n` is const, `x` is not const. The placement new modifies the object `u.x`, therefore the compiler should not assume that any part of `u.x` has the same value it did before. No laundering should be needed. – Miral Jun 05 '19 at 08:24
  • 1
    @Miral: But placement `new` *doesn't* modify the object; it ends the old object and creates a new one in its place. Variables in C++ name *objects*; if you placement-`new` over an object, then the variable doesn't name it anymore. C++ allows the name of the old object to refer to the new one under certain circumstances. But outside of those circumstances, the name refers to the old object. What you would want is a system where names don't name objects, and therefore, any `const`-declared name could not be assumed to refer to name the same `const` object. So what would `const` actually do? – Nicol Bolas Jun 05 '19 at 13:25
  • 3
    Regardless of whether it's the same object or not, the compiler can see the placement new and can know that whatever the label `u.x` refers to has either been modified or replaced. Thus anything that refers to this afterwards should not assume it has not been changed. Again, no laundering needed. Asking the programmer to put this sort of thing in explicitly is silly. – Miral Jun 05 '19 at 23:37
  • 4
    @Miral: Except for when it *can't* see it, because it's behind a pre-compiled library. Or something loaded from a DLL. Or for any other reason that the compiler would choose *not* to inline through a function call. Or... if the placement-`new` is inside of a *conditional* statement, so the compiler *cannot possibly know* at compile time which branch happened. Asking a compiler to know about things it cannot possibly know about is silly. – Nicol Bolas Jun 05 '19 at 23:39
  • Those are all cases that the compiler can't optimise anyway. – Miral Jun 06 '19 at 00:18
  • 1
    @Miral: Can't optimize what? If you call an opaque function between creating `u` and accessing it, even if you pass a reference to `u` as a parameter, the compiler certainly can optimize `u.x.n`. Why? Because `const` objects *cannot* be altered, and therefore any code that did so has elicited UB. So returning the original value is valid "undefined" behavior. What you're asking is just to make it not undefined under some poorly specific circumstances. Why bother? Why not just respect that the name of a `const` object always refers to the original `const` object? – Nicol Bolas Jun 06 '19 at 00:20
  • 1
    If you pass `u` or `u.x` as a mutable pointer or reference to an opaque function, then the compiler *absolutely* has to assume that `u.x.n` may have been changed by the function. It can **never** assume otherwise. If you pass `u` or `u.x` as a const pointer or reference, then yes, it can assume that it has not changed -- but any well defined program should not be changing it anyway. Thus there is still no requirement for explicit laundering annotations. – Miral Jun 06 '19 at 00:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/194518/discussion-between-nicol-bolas-and-miral). – Nicol Bolas Jun 06 '19 at 00:44
  • 3
    Coming very late, but why do we need union in motivating example? Wouldn't a simple placement new in a single object be exactly the same thing? – SergeyA Jun 10 '19 at 21:22
  • 1
    @SergeyA: The example makes it clear that the issue manifests based both on how you get the pointer/reference as well as the nature of the type. Without the `const`, the code would be fine, but with it, you need `launder` to get a pointer to the object. Whereas with just some uninitialized storage, you'd always need to `launder` to get the pointer from the address of the storage. – Nicol Bolas Jun 10 '19 at 21:37
  • `launder` is implemented by calling a compiler builtin. There's nothing magical about it. – Brice M. Dempsey Aug 18 '20 at 12:21
  • 1
    @BriceM.Dempsey: First, nobody said anything about "magic", so I don't know why you brought it up. Second, the term "compiler magic" (which again, was not brought up on this answer or its comments) typically refers to anything you cannot do within the language itself. Compiler builtins *are* compiler builtins in most cases because the language has no mechanism to do that particular thing, so there has to be an implementation-specific hook to that functionality. – Nicol Bolas Aug 18 '20 at 13:35
  • 2
    According to the newest standard draft (see link in answer) the [basic.life] paragraph which this answer links to now makes launder unnecessary in this answer's example, as the union and the const int are all trivially-replaceable by their new counterparts. For a more fitting example, see the std::launder section of that draft, linked at the bottom of the same paragraph. – JMC Sep 07 '20 at 23:18
  • In Scott Schurr's [CppCon talk](https://www.youtube.com/watch?v=sCjZuvtJd-k&t=0s&index=80&list=PLHTh1InhhwT6bwIpRk0ZbCA0N2p1taxd6), he seemed to think that `std::launder` had to be used at the site of the _access_ through the `reinterpret_cast`ed pointer `p`, not at the creation, in which case trying to return a laundered pointer from a function would be futile and a Bad Idea. – Nicolas Holthaus Oct 10 '20 at 00:22
  • 2
    @NicolBolas: Worth mentioning -- the first motivating example is no longer relevant [since C++20](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1971r0.html). – Valentin Milea Apr 05 '21 at 12:11
  • As for "in-place" type punning, C++23 has `std::start_lifetime_as()` – Weijun Zhou Oct 15 '21 at 11:24
  • If the compiler can assume `u.x.n` will *always* be `1`, what happens if you do `u.x = {5};` later in the code? No placement `new` involved, just a simple assignment. Would you have to use `launder` there too? That doesn't seem right. – nog642 Apr 15 '22 at 16:33
  • 1
    @nog642: `u.x = {5}` is a compile error. This will create an object of the type `X` and assign it to the `u.x` member. However, `X` cannot be assigned to because it has a `const` member (unless you give it an assignment operator, in which case you haven't changed the `const` member). – Nicol Bolas Apr 15 '22 at 16:41
  • @NicolBolas: Based on this answer, can we say `std::launder` and `volatile` has overlapping/similar purpose if not exactly same? – Nawaz Jan 14 '23 at 18:41
  • @Nawaz: They have nothing to do with each other. `launder` is about taking an address and converting it to a pointer to a living object of a known type which has that address. `volatile` is about forcing the compiler to read or write memory even if it thinks such reads or writes would be redundant because the backing store for that memory may have changed outside of the memory model. Neither can do anything the other is doing. – Nicol Bolas Jan 14 '23 at 18:45
  • @Nawaz: That is, if you have a global variable of type `T`, and you get a pointer to it and `launder` it... the compiler still knows that it's pointing at that global object (because there can only be one object of type `T` at any given address). So the compiler is completely free to assume that any reads from that address would be the same as reads from that global and thus optimize them out if it had previously read from that location. `volatile` would prevent that; `launder` cannot. – Nicol Bolas Jan 14 '23 at 18:48
  • @NicolBolas: Sorry, I don't follow you in the last two comments here. Quoting from your answer: **"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."** In other words, `std::launder` forces compiler to read from the backing memory which is same as `volatile`? I mean, `*std::launder(&u.x.n) == 2` is true (because here the compiler doesn't optimize)? And, `u.x.n == 1` _could_ also be true (because here the compiler _could_ optimize it)? – Nawaz Jan 14 '23 at 20:08
  • @Nawaz: "*forces compiler read from the backing memory*" That sentence says nothing about "backing memory". "*I mean, `*std::launder(&u.x.n) == 2` is true (because here the compiler doesn't optimize)?*" Because the compiler isn't allowed to assume that the address at `u.x.n` is talking about the same object as expressed by the name `u.x.n`. That is, the text `u.x.n` refers to a specific object. But if the compiler can see that you *haven't* created a new object at that location... the laundering doesn't matter, and the compiler doesn't have to do the read. – Nicol Bolas Jan 14 '23 at 20:13
  • @Nawaz: That is, nothing is guaranteed if the compiler can see that nothing has *actually* changed. `volatile` *does not care* what the compiler can or cannot see; the compiler is not allowed to assume *anything*. – Nicol Bolas Jan 14 '23 at 20:14
  • @NicolBolas: _" Because the compiler isn't allowed to assume that the address at u.x.n is talking about the same object as expressed by the name u.x.n."_. This part is difficult to understand. `o` is an object, `&o` points to that object, and `std::launder(&o)` points to the _same_ object. How is it even possible that `std::launder(&o)` could point to a different object which is not pointed to by `&o`? – Nawaz Jan 14 '23 at 20:54
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251146/discussion-between-nicol-bolas-and-nawaz). – Nicol Bolas Jan 14 '23 at 20:55
11

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 led me to this view of what std::launder means.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 10
    I dunno, seems to perform exactly laundering to me: it's removing the provenance of the pointer so that it's clean, and needs to be (re-)read. I don't know what "soiling" means in this context. – Barry Feb 13 '21 at 15:00
  • @Barry: Memory into which anybody might have thrown/written stuff is dirty, not clean. If I give you an article of clothing without provenance information - who knows where it's been? You would definitely put it in the dirty laundry hamper to be washed. – einpoklum Feb 13 '21 at 17:30
  • 2
    I agree that `std::launder` is named exactly backwards if it's meant to refer to money laundering, but I don't think you should say that it soils the memory. Dirty money is dirty whether "laundered" or not, but the laundering makes people wrongly assume that it's clean. Dirty memory is dirty whether `std::launder`ed or not, but the laundering makes the compiler *stop* wrongly assuming that it's clean. – benrg Mar 22 '21 at 19:52
  • 4
    Re: "_That place is now soiled, don't make that assumption_" - Or, "that place _is_ soiled, please `std::launder` it" – Ted Lyngmo Jul 25 '21 at 09:13
  • 1
    @benrg: Money that has been laundered *is* clean. If it can be proven that someone stole $7,500, laundered it, and then used the money to buy a used car for $7,500, the government may seize the car, but unless the seller of the car was an accessory to the theft or money laundering, the seller would be entitled to keep the $7,500 from the sale. – supercat Aug 27 '21 at 19:56
  • @einpoklum: Marking something in a cache dirty creates an obligation on the part of the cache manager to clean it even when doing so would might otherwise seem necessary. Because the abstraction model used by the C++ Standard doesn't recognize the idea of compiler register caching, it ends up without the vocabulary to describe how things should be processed by compilers that use real-world execution models. – supercat Aug 27 '21 at 19:57
  • 2
    *"This function performs the opposite of laundering: It soils the pointed-to memory,"* No it doesn't. It doesn't affect the memory at all. The compiler is allowed to continue to hold its outdated assumptions about existing pointers to that memory. But it must not transfer those assumptions to the new pointer returned from `std::launder`, even though its numeric value is the same. The new pointer has been **laundered**. Any old pointers have not. Only accesses through the new pointer are affected. Calling `launder` only produces a new "laundered" pointer, and only affects accesses through that. – Jonathan Wakely Feb 20 '23 at 12:01
10

I think there are two purposes of std::launder.

  1. A barrier for constant folding/propagation, including devirtualization.
  2. A barrier for fine-grained object-structure-based alias analysis.

Barrier for overaggressive constant folding/propagation (abandoned)

Historically, the C++ standard allowed compilers to assume that the value of a const-qualified or reference non-static data member obtained in some ways to be immutable, even if its containing object is non-const and may be reused by placement new.

In C++17/P0137R1, std::launder is introduced as a functionality that disables the aforementioned (mis-)optimization (CWG 1776), which is needed for std::optional. And as discussed in P0532R0, portable implementations of std::vector and std::deque may also need std::launder, even if they are C++98 components.

Fortunately, such (mis-)optimization is forbidden by RU007 (included in P1971R0 and C++20). AFAIK there's no compiler performing this (mis-)optimization.

Barrier for devirtualization

A virtual table pointer (vptr) can be considered constant during the lifetime of its containing polymorphic object, which is needed for devirtualization. Given that vptr is not non-static data member, compilers are still allowed to perform devirtualization based on the assumption that the vptr is not changed (i.e., either the object is still in its lifetime, or it is reused by a new object of the same dynamic type) in some cases.

For some unusual uses that replace a polymorphic object with a new object of different dynamic type (shown here), std::launder is needed as a barrier for devirtualization.

IIUC Clang implemented std::launder (__builtin_launder) with these semantics (LLVM-D40218).

Barrier for object-structure-based alias analysis

P0137R1 also changes the C++ object model by introducing pointer-interconvertibility. IIUC such change enables some "object-structure-based alias analysis" proposed in N4303.

As a result, P0137R1 makes the direct use of dereferencing a reinterpret_cast'd pointer from an unsigned char [N] array undefined, even if the array is providing storage for another object of correct type. And then std::launder is needed for access to the nested object.

This kind of alias analysis seems overaggressive and may break many useful code bases. AFAIK it's currently not implemented by any compiler.

Relation to type-based alias analysis/strict aliasing

IIUC std::launder and type-based alias analysis/strict aliasing are unrelated. std::launder requires a living object of correct type to be at the provided address.

However, it seems that they are accidently made related in Clang (LLVM-D47607).

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
F.v.S.
  • 167
  • 1
  • 8
  • "As a result, P0137R1 makes the direct use of dereferencing a reinterpret_cast'd pointer from an unsigned char [N] array undefined" -- where, exactly, in the paper is this defined, please? I don't see it... – Marc Mutz - mmutz Feb 09 '23 at 21:52
  • @MarcMutz-mmutz The paper made some pointers have different pointer value, even if they are of the same type and represent the address of the same byte. – F.v.S. Feb 13 '23 at 15:46
  • @MarcMutz-mmutz In the case of `unsigned char[N]` array and the object created within it, pointers to both objects are not "pointer-interconvertible", so `reinterpret_cast` can't turn one pointer value to another. And deferencing a wrong pointer value results in UB. – F.v.S. Feb 13 '23 at 15:48
  • "`reinterpret_cast` can't turn [a `unsigned char*`] into [a `T*`]" - assuming the correct use of `launder()` here is to apply it to a `reinterpret_cast` (if not: what would be the correct use), you write, however, that "std::launder and type-based alias analysis/strict aliasing are unrelated. " Does it follow like this, maybe: https://eel.is/c++draft/basic.life#8.2 does not apply, so https://eel.is/c++draft/basic.life#note-4 applies? But the latter's just a note.. – Marc Mutz - mmutz Feb 13 '23 at 16:37
  • @F.v.S. The constant folding/propagation issue would remain for `const`-qualified complete objects with dynamic storage duration, which can still be replaced, but not transparently. – user17732522 Feb 18 '23 at 01:28