[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)