3 ms·
C++ is a weird language that essentially combines both a high-level type system whose details of representations and whatnot can be freely modified by a suffici
by jcranmer 3y ago
C++ is a weird language that essentially combines both a high-level type system whose details of representations and whatnot can be freely modified by a sufficiently smart compiler with a low-level bytemucking type system, and the interface between the two systems is gnarly and confusing and std::launder is lying in a pit on this border.
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.
- amluto 3y agoI'm wondering if any uses at all would be left if programs were using strict provenance, a la: https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance 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.
- jcranmer 3y agoThe issue that's trying to be solved with std::launder is essentially around lifetime issues, which strict provenance doesn't really solve. Strict pointer provenance is about fixing the semantics of int-to-pointer, which requires tweaks to pointer-to-int. std::launder, by contrast, is trying to fix cases where memory is being validly reused after the language thinks the object is dead--it's the kind of thing you might need to use to implement an allocator, for example, although you already have screwy effective type rules breaking you that std::launder doesn't fix.
- amluto 3y agoMy point, which may or may not be entirely correct, is that the examples I've seen are, in effect, uses after a lifetime is over. They seem to boil down to something like: 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);