std::launder: the most obscure new feature of C++17 (2016)
miyuki.github.io
miyuki.github.io
https://en.cppreference.com/w/cpp/utility/launder
"template <class T> [[nodiscard]] constexpr T* launder (T* p) noexcept;
Provenance fence with respect to p. Returns a pointer to the same memory that p points to, but where the referent object is assumed to have a distinct lifetime and dynamic type."
It's a function for the compiler for low-level memory management and lifetime management code (i.e. code almost no C++ "end-user" writes) to turn off compile-time tracking and optimisations that might not be correct. Typically only used if you're starting the lifetime of one object over or inside another. Essentially, when you want the compiler to be dumb you launder the pointer to make the compiler intentionally forget the complex compile-time state tracking that compilers do and make the compiler pretend that pointer is actually a brand new object it knew nothing about.
Someone brought up the volatile keyword: volatile is for turning off compiler assumptions about what a value might be at run-time. For example, if you read from a regular int twice in a row with no write in between, then the compiler would likely remove the second read and reuse the first value (better code-gen) as it knows the value could not have changed. The volatile keyword is how you tell the compiler that it cannot reliably observe all changes to the variable so every read must be performed (same for writes).
launder and volatile are similar in so much that they exist to tell the compiler not to make assumptions about the values/objects, but that's about it. They are not interchangeable.
But let's all pretend this function is something everyone using C++ needs to use to farm some internet points.
I understand how it works, and why you would even maybe want this, but this just straight-up seems like a mistake. It breaks const-invariance, which I guess is no longer invariant. Is just any proposal making into C++ these days?
> He tells us that this function might be handy when dealing with containers of const-qualified elements.
Then just.. don't.. const-qualify your elements? What a neat footgun, not even consts are safe in C++ now.
The only possible difference (from the resulting binary POV) I can think of is "static const" variables might be allocated in a read-only executable section?
Crashing is exactly what happens, thankfully
extern void mutate(int *p);
int func() {
const int foo = 7777;
mutate(&foo);
return foo;
}The compiler can indeed put const objects in the executable's .rodata section, which means trying to modify it at runtime will probably cause a segfault (but it's all Undefined Behavior so who knows).
const int a = 1;
If you try to const_cast away the const of x to change its value, you have undefined behaviour. As for pointers, if you make a const pointer to a non-const object then all it means is that you cannot modify the object via that pointer, not that the object will never be modified.
int a = 1; int b = &a; const int c = &a;
a = 2;
Both b and c are now 2 and this is absolutely fine, and the compiler won't try to optimise out any reads of *c and assume its value is still 1 as const/non-const pointer aliasing is allowed (C has the restrict keyword, it's not in C++).
Launder is a replacement for calling compiler intrinsics during certain special operations, not everyday use. For one example: If I wrote a simple benchmarking library and shipped its source code then an optimizer could easily mingle my benchmarking library into another developer's benchmarked code. Launder provides at least some rudimentary options for minimizing this so artifacts of the implementation of my benchmark impact the code being measured less and in more predictable ways.
Other places this might be useful that I can think of off the top of my head might include: code that needs predictable binaries to avoid spectre and similar issues, working with precompiled binaries with headers but without source, code that is trying to leverage specific features of the underlying hardware possibly for maximum performance. I am sure there are more but disabling optimizations is super useful sometimes.
See https://www.embedded.com/combining-cs-volatile-and-const-key... for examples, https://en.cppreference.com/w/cpp/language/cv for a more formal description.
The point of std::launder is to let you use an object that would otherwise be undefined behavior to use. The original motivating example for std::launder essentially boils down to "you can't properly make std::vector<T> if T has a const member variable without std::launder," although C++ later did change the object model (after this feature had been accepted!) so that std::vector works without std::launder.
The residual use of std::launder has to do with vtables (which are important properties of an object yet not properly part of the object model because it's a high-level thing, not a low-level thing) after placement new, which is an extremely niche thing that truly almost nobody needs to care about and probably doesn't deserve a place in the standard library. libc++ overloads it to use it as a way to indicate intentional strict aliasing violations, but this is still UB per the standard.
https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-...
If I have a pointer to an object that no longer exists because I've futzed with a union or placement new or otherwise put a different object at that address, but I know that my pointer has the correct bit pattern (i.e. address on most systems) for the new object, then a strict provenance model says that I have no business dereferencing the pointer. But an API like with_addr() gives me a very explicit way to tell the compiler (and the CPU or runtime if provenance is being enforced!) what I'm doing, and the cases that could plausibly generate valid code will be well defined and work correctly.
struct A {
const int x;
};
A *ptr = <address of object 1>
... destroy object 1 and create object 2 in its place, so <old address of object 1> == <new address of object 2>
do_something_with(ptr->x);
The language thinks ptr points to object 1, but it actually points to object 2. In a weak provenance model, maybe this is valid, and it gets the correct results if std::launder is used.But it seems to me that, in a strict provenance model, accessing ptr->x is incorrect -- ptr has provenance for object 1, and object 1 is gone. The operation needed to restore access isn't something very generic like std::launder -- it's specifically the creation of a new pointer with provenance matching object 2. So you don't want:
do_something_with(std::launder(ptr->x));
You want (very pseudo-codeish): do_something_with(with_addr(&object2, ptr)->x);
And I would hope that a good provenance implementation would not require an abomination like: do_something_with(launder(with_addr(&object2, ptr))->x);Topping the list, for me, would be memory_order_consume. It's so complicated and for such a slight extremely technical memory ordering optimization, compilers have mostly given up hope of ever implementing it.
There was even talk of outright removing it from C++, but looks like it still stands today. I've never seen it used correctly (and even if it were, compilers just throw up their hands at it)
> Note that currently (2/2015) no known production compilers track dependency chains: consume operations are lifted to acquire operations.
https://en.cppreference.com/w/cpp/atomic/memory_order#Releas...
Example would help. Most paging nowadays is implicit, transparent to the program.
Are you talking about some old-school overlays or something? Multiple objects in mass storage are mapped to the same address in memory, multiplexed in the time domain?
Almost any database.
There were magic incantations that compilers informally respected to induce the correct behavior but it wasn’t always reliable and technically not a bug when it wasn’t. This provides an official and simple way to achieve the same effect without obscure magic.
Even if they were PODs, changing the types and instances of types at a specific memory address without destroying the objects residing there can have side-effects. The compiler does not expect a live memory address to be overwritten with an arbitrary bucket of bits by a DMA operation. DMA doesn't respect lifetime and ownership models as understood by a compiler, so it is a bit of a Deus Ex Machina as far as the compiler is concerned and is assumed to never happen.
I assume you're not talking about virtual memory here, but serialisation to disk or the network
“Don’t you want to hire a top expert? It says right here that only five people in the world understand this feature. It’s the perfect filter to ensure that we don’t get random nobodies working on our legacy Win32 MFC application that manages hotel breakfast reservations. Trust the signal.”
An obvious first step is to start by writing tests, and when you do so you can build little pieces of the old code on the new compiler but sometimes it produces wrong answers. If the old compiler worked a certain way and the new compiler works a different way it is often because the new compiler has a better optimizer and leverages better understandings of the assumption that most developers make. But some "clever" developer from back in the day did some shenanigans and leveraged undefined behavior that happened to do what they wanted. The maintenance options are either to leave it on the old compiler, rewrite the whole thing and hope you make it work right, or use std::launder to make the undefined behavior go away and hopefully make a minimal changes to retain the old behavior.
In this case launder is a middle path that hopefully lets maintainers keep most of the old code with the new platform, but hopefully without having to rewrite a huge amount of it.
The attitude rings true though. I am convinced that C++ is proof that we have discovered alien technology, and it is awful.