[HN Gopher] std::launder: the most obscure new feature of C++17 ...
       ___________________________________________________________________
        
       std::launder: the most obscure new feature of C++17 (2016)
        
       Author : goranmoomin
       Score  : 34 points
       Date   : 2024-04-02 08:27 UTC (2 days ago)
        
 (HTM) web link (miyuki.github.io)
 (TXT) w3m dump (miyuki.github.io)
        
       | CraigRo wrote:
       | This is an April folks joke
        
         | hifromwork wrote:
         | I don't think this is true
        
         | dgfitz wrote:
         | Is it?
         | 
         | https://news.ycombinator.com/item?id=30017204
        
         | perihelions wrote:
         | The C++ language standard is not a funny joke.
        
           | MaxBarraclough wrote:
           | Agreed. Brevity is the soul of wit, and all that.
        
           | 1letterunixname wrote:
           | It's the art of making the juggling with swords seem safe and
           | easy while making a daily commute through a minefield of UB
           | seem routine.
        
         | selimnairb wrote:
         | > Don't ask. If you're not one of the 5 or so people in the
         | world who already know what this is, you don't want or need to
         | know.
         | 
         | The attitude rings true though. I am convinced that C++ is
         | proof that we have discovered alien technology, and it is
         | awful.
        
           | ericdfoley wrote:
           | The thing is that there is probably a lot of existing C++
           | code that is UB without std::launder (similarly to aliasing
           | rules.) The main problem is that the C++ object lifetime
           | rules are not well understood by most people writing C++
           | code.
        
             | sqeaky wrote:
             | I hadn't even considered the task of going through old code
             | and using this to make it more compliant, this is a great
             | use case for this feature.
        
         | MaxBarraclough wrote:
         | It is not. It links to a lengthy StackOverflow thread about
         | std::launder.
         | 
         | https://stackoverflow.com/questions/39382501/
        
         | avrionov wrote:
         | It is a feature:
         | 
         | https://en.cppreference.com/w/cpp/utility/launder
        
       | tux3 wrote:
       | As far as complicated features that very few people understand or
       | use and that are hard to implement correctly in compilers,
       | std::launder ranks pretty high.
       | 
       | 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)
        
         | ric129 wrote:
         | I recall plenty of implementations turning into the same as a
         | memory_order_acquire, which seems fine these days
        
           | loeg wrote:
           | > On all mainstream CPUs other than DEC Alpha, dependency
           | ordering is automatic, no additional CPU instructions are
           | issued for this synchronization mode, only certain compiler
           | optimizations are affected (e.g. the compiler is prohibited
           | from performing speculative loads on the objects that are
           | involved in the dependency chain).
           | 
           | > 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.
           | ..
        
       | dvt wrote:
       | > that prevents the optimizer from performing constant
       | propagation
       | 
       | 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.
        
         | amluto wrote:
         | C++'s const was never especially safe -- contemplate what
         | mutable does. And that one can cast pointers-to-nonconst to
         | pointer-to-const.
        
           | kimixa wrote:
           | It's always been a "programmer aid annotation" rather than
           | anything that really affects code generation.
           | 
           | 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?
        
             | throwway120385 wrote:
             | Probably not, given the possibility that they can be
             | const_casted back into mutable values. Locating static
             | const values in read-only pages would result in crashes
             | from valid statements in the language. In other words if
             | you put a static const in a read only page, it would
             | probably be considered a bug in your linker.
        
               | actionfromafar wrote:
               | That doesn't rhyme with my understanding of C++ (which is
               | limited, of course). This sounds like _undefined_.
        
               | secondcoming wrote:
               | Aren't they put into the .rodata section of the binary?
               | 
               | Crashing is exactly what happens, thankfully
               | 
               | https://godbolt.org/z/vKj9dcPMc
        
               | gary_0 wrote:
               | Generally yes, if you have a bunch of const data in your
               | C++ code, it will go in .rodata. Depending on the
               | compiler/flags/optimizations, of course.
               | https://stackoverflow.com/a/44938843
        
             | PhilipRoman wrote:
             | It doesn't even have to be static. Const is powerful when
             | used on non-pointer types. You can write something like
             | this and the compiler will (on appropriate optimization
             | levels) hard-code return value to 7777 regardless of what
             | "mutate" does:                   extern void mutate(int
             | *p);         int func() {             const int foo = 7777;
             | mutate(&foo);             return foo;         }
        
             | gary_0 wrote:
             | const_cast is only for removing const from references or
             | pointers that actually refer to a non-const object. You
             | can't declare a const variable and then modify it with
             | const_cast. See "Notes" at:
             | https://en.cppreference.com/w/cpp/language/const_cast
             | 
             | 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).
        
           | bingo3131 wrote:
           | It is undefined behaviour to modify objects declared as const
           | (except if those objects have mutable members, in which case
           | the mutable members can be modified).
           | 
           | 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++).
        
         | sqeaky wrote:
         | They never were safe in C++. And no serious developer was
         | expecting them to be perfectly safe, they are a tool for normal
         | well-formed software to be a little less errer-prone. Everybody
         | taking a class in C++ learns about const_cast and anyone clever
         | eventually figures out how to use reinterpreter_cast for the
         | similar things.
         | 
         | 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.
        
           | secondcoming wrote:
           | I think you're thinking of barriers?
        
         | kazinator wrote:
         | I don't understand why plain old _volatile_ wouldn 't be enough
         | to prevent constant propagation.
        
           | cryptonector wrote:
           | I would expect volatile and const to be mutually exclusive.
        
             | Someone wrote:
             | For C++, you would be wrong. You can use it for variables
             | that your code won't change (hence _const_ ), but that can
             | change due to outside events and hence can't be cached in a
             | register (hence _volatile_ )
             | 
             | 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.
        
         | jcranmer wrote:
         | 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.
        
       | hardlianotion wrote:
       | There are so many things that engineers want to simplify their
       | lives that don't appear to be prioritised for the standard
       | library, so it is rather surprising to see something like this,
       | which can only complexify.
        
         | sqeaky wrote:
         | This isn't my example, but consider this situation: you are
         | maintaining an old code base that has stuff from 30 to 40 years
         | ago in it because it's still working and still making the
         | company money. But it is old, came from before we understood
         | dependency management, and came from before we had tests as a
         | standard practice. Parts of it have to be built on some old
         | compiler because no one has taken the effort to rewrite it and
         | get it to work on the new compiler. This hasn't been done
         | because millions of developer hours have been put into it and
         | it could take hundreds of thousands of developer hours to redo
         | it.
         | 
         | 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.
        
       | renewiltord wrote:
       | Wait this is crazy. What's the legitimate use-case? Man I would
       | hate to work on codebases that have these invariants violated.
        
       | jandrewrogers wrote:
       | One of the main legitimate use cases for this feature is managing
       | how the compiler interprets the lifetimes of runtime objects that
       | are explicitly paged to disk. Many code bases rely on undefined
       | behavior where the compiler coincidentally produces the expected
       | behavior, which usually works, but I have seen occasional bugs in
       | the wild from when it doesn't. You can use std::launder to
       | guarantee that the compiler correctly interprets the lifetime of
       | objects with these properties.
        
         | kazinator wrote:
         | > _explicitly paged to disk_
         | 
         | 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?
        
           | zamalek wrote:
           | > Example would help.
           | 
           | Almost any database.
        
           | jandrewrogers wrote:
           | Paging is not transparent in high-scale and high-performance
           | databases when using direct I/O to disk. It is not possible
           | to make it transparent on many larger servers even if you
           | wanted to because the silicon does not support a large enough
           | virtual address space. This is a pretty common design problem
           | in databases, or anything with huge amounts of data and
           | storage really.
           | 
           | 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.
        
         | secondcoming wrote:
         | 'paged to disk'
         | 
         | I assume you're not talking about virtual memory here, but
         | serialisation to disk or the network
        
           | jandrewrogers wrote:
           | Direct I/O from and to user space, no serialization. There
           | are significant size and performance limits when using
           | virtual memory to give objects a consistent address, hence
           | why it is sometimes avoided.
        
       | pavlov wrote:
       | "Jim, management is complaining that we can't hire anyone for the
       | open C++ developer positions because the interviews are all about
       | std::launder. What's up?"
       | 
       | "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."
        
       | bingo3131 wrote:
       | A post about an obscure C++ feature on Hacker News? Comments are
       | going to be a dumpster-fire as usual.
       | 
       | 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.
        
       | fancyfredbot wrote:
       | std::launder has now become 'famously obscure'. I liked it before
       | it was cool but nowadays indie kids like me prefer
       | std::hardware_destructive_interference_size
       | (https://en.cppreference.com/w/cpp/thread/hardware_destructiv...)
        
       ___________________________________________________________________
       (page generated 2024-04-04 23:02 UTC)