C++26 is done: ISO C++ standards meeting Trip Report
herbsutter.com
herbsutter.com
Here's a quote from Bjarne,
> So go back about one year, and we could vote about it before it got into the standard, and some of us voted no. Now we have a much harder problem. This is part of the standard proposal. Do we vote against the standard because there is a feature we think is bad? Because I think this one is bad. And that is a much harder problem. People vote yes because they think: "Oh we are getting a lot of good things out of this.", and they are right. We are also getting a lot of complexity and a lot of bad things. And this proposal, in my opinion is bloated committee design and also incomplete.
Nobody wanted it.
The reason I started D in the first place is the C++ community was uninterested in any of my ideas for improvement. Ironically, C++ would up adopting a lot of them one by one! Such as contracts!
Contracts did find an enthusiastic constituency in the D community.
Some imperfect data points on how to judge if a language feature is wanted (or not):
- Discussion on forums about how to use the feature
- Programs in the wild using the feature
- Bug reports showing people trying to use the feature and occasionally producing funny interactions with other parts of the language
- People wanting to do more complex things on the initially built feature by filing features requests. (Showing that there is uptake on the feature and people want to do more fancy advanced things)
This is actually a good point. Yes, LLMs have saturated the conversation everywhere but contracts help clarify the pre-post conditions of methods well. I don't know how good the implementation in C++ will be but LLMs should be able to really exploit them well.
Arguably that's better (more predictable misbehavior) than the status quo. But it's not remotely going to fix the problem with LLM-generated code, which is that you can't trust it to behave correctly in the corner cases. Contracts can't make the code magically behave better; all they can do is make it misbehave better.
Regardless, I think a habit of putting contracts to make pre- and post-conditions clear could help an AI reason about code.
Maybe instead of suggesting a patch to cover up a symptom, an AI may reason that a post-condition somewhere was violated, and will dig towards the root cause.
This applies just as well to asserts, too. Contracts/asserts actually need to be added to tell a reader something.
The fact that the C++ standard community has been working on Contracts for nearly a decade is something that by itself automatically refutes your claim.
I understand you want to self-promote, but there is no need to do it at the expense of others. I mean, might it be that your implementation sucked?
Also, in my view the committee has been entertaining wider and wider language extensions. In 2016 there was a serious proposal for a graphics API based on (I think) Cairo. My own sense is that it's out of control and the language is just getting stuff added on because it can.
Contracts are great as a concept, and it's hard to separate the wild expanse of C++ from the truly useful subset of features.
There are several things proposed in the early days of C++ that arguably should be added.
I would say some of the features that are truly useful in some niches are les s imoortant in others and viceversa.
Boy, this makes me feel old... oh wait :)
(I agree with your point; early 90s vs. mid-10s are two very different worlds, in this context.)
So what was it like back in the Egyptian age? :)
Not a very fair assumption. However, even if your not so friendly point was even true, I'd like people who have invented popular languages to "self-promote" more (here dlang). It is great to get comments on HN from people who have actually achieved something nice !
> why have a keyword that means nothing, but also anything?
googling void: "A void is a space containing nothing, a feeling of utter emptiness, or a legal nullity, representing a state of absolute vacancy or lack."
Sounds perfect!
In my experience, this is absolutely true. I wrote my own metaprogramming frontend for C and that's basically all you need. At this point, I consider the metaprogramming facilities of a language it's most important feature, by far. Everything else is pretty much superfluous by comparison
It should be done at some point. People can always develop languages with more or less things but piling more things on is just not that useful.
It sounds cool in the minds of people that are designing these things but it is just not that useful. Rust is in the same situation of adding endless crap that is just not that useful.
Specifically about this feature, people can just use asserts. Piling things onto the type system of C++ is never going to be that useful since it is not designed to be a type system like Rust's type system. Any improvement gained is not worth piling on more things.
Feels like people that push stuff do it because "it is just what they do".
It is difficult to overstate just how important these features are for high-performance and high-reliability systems software. These features greatly expand the kinds of safety guarantees that are possible to automate and the performance optimizations that are practical. Without it, software is much more brittle. This isn’t an academic exercise; it greatly reduces the amount of code and greatly increases safety. The performance benefits are nice but that is more on the margin.
One of the biggest knocks against Rust as a systems programming language is that it has weak compile-time and metaprogramming capabilities compared to Zig and C++.
Aren’t Rust macros more powerful than C++ template metaprogramming in practice?
Declarative macros deliberately don't share Rust's syntax because they are macros for Rust so if they shared the same syntax everything you do is escape upon escape sequence as you want the macro to emit a loop but not loop itself etc. But other than the syntax they are pretty friendly, a one day Rust bootstrap course should probably cover these macros at least enough that you don't use copy-paste to make those seven functions by hand.
However the powerful feature you're thinking of is procedural or "proc" macros and those are a very different beast. The proc macros are effectively compiler plugins, when the compiler sees we invoked the proc macro, it just runs that code, natively. So in that sense these are certainly more powerful, they can for example install Python, "Oh, you don't have Python, but I'm a proc macro for running Python, I'll just install it...". Mara wrote several "joke" proc macros which show off how dangerous/ powerful it is, you should not use these, but one of them for example switches to the "nightly" Rust compiler and then seamlessly compiles parts of your software which don't work in stable Rust...
While I agree that, generally, compile time metaprogramming is a tremendously powerful tool, the C++ template metaprogramming implementation is hilariously bad.
Why, for example, is printing the source-code text of an enum value so goddamn hard?
Why can I not just loop over the members of a class?
How would I generate debug vis or serialization code with a normal-ish looking function call (spoiler, you can't, see cap'n proto, protobuf, flatbuffers, any automated dearimgui generator)
These things are incredibly basic and C++ just completely shits all over itself when you try to do them with templates
Aside from this being trivial in C++26, imo it isn't actually that tricky. Here's a very quick implementation I made awhile ago: https://github.com/Cons-Cat/libCat/blob/3f54e47f0ed182771fce...
In the space of language design, everything "more powerful" is not necessary good. Sometimes less power is better because it leads to more optimisable code, less implementation complexity, less abstraction, better LSP support. TL;DR More flexibility and complexity is not always good.
Though I would also challenge the fact that Rust's metaprogramming model is "not powerful enough". I think it can be.
And not only for performance but also for thread safety (eliminates initialization races, for example, for non-trivial objects).
Rust is just less powerful. For example you cannot design something that comes evwn close to expression templates libraries.
I understand some of these kinds of features are because Rust is Rust but it still feels useless to learn.
I'm not following rust development since about 2 years so don't know what the newest things are.
1. We didn't want to give the thing we're returning a name, it does have one, but we want that to be an implementation detail. In comparison the Rust stdlib's iterator functions all return specific named Iterators, e.g. the split method on strings returns a type actually named Split, with a remainder() function so you can stop and just get "everything else" from that function. That's an exhausting maintenance burden, if your library has some internal data structures whose values aren't really important or are unstable this allows you to duck out of all the extra documentation work, just say "It's an Iterator" with RPIT.
2. We literally cannot name this type, there's no agreed spelling for it. For example if you return a lambda its type does not have a name (in Rust or in C++) but this is a perfectly reasonable thing to want to do, just impossible without RPIT.
Blanket trait implementations ("auto impl trait for type that implements other trait") are an important convenience for conversions. If somebody wrote a From implementation then you get the analogous Into, TryFrom and even TryInto all provided because of this feature. You could write them, but it'd be tedious and error prone, so the machine does it for you.
Async/await is just fundamental to making efficient programs, I'm not sure what to mention here. Reading a file from disk, waiting for network I/O, etc are all catastrophically slow in CPU time and having a mechanism to keep a thread doing useful other work is important.
Actively writing code for the others you mentioned generally isn't required in the average program (e.g. you don't need to create your own proc macros, but it can help cut down boilerplate). To be fair though, I'm not sure how someone would know that if they weren't already used to the features. I imagine it must be what I feel like when I see probably average modern C++ and go "wtf is going on here"
A shoutout to Eiffel, the first "modern" (circa 1985) language to incorporate Design by Contract. Well done Bertrand Meyer!
Note that this is not the end of contrats. This is a minimun viable start that they intend to add to but the missing parts are more complex.
From my outside vantage point, there seems to be a few different camps about what is desired for contracts to even be. The conflict between those groups is why this feature has been contentious for... a decade now?
Some of the pushback against this form of contracts is from people who desire contracts, but don't think that this design is the one that they want.
C++ will never, ever be modern and comprehensible because of 1 and 1 reason alone: backward compatibility.
It does not matter what version of C++ you are using, you are still using C with classes.
Even C++23 is largely usable at this point, though there are still gaps for some features.
https://en.cppreference.com/w/cpp/compiler_support.html
Funny how gcc seems to be the top dog now, what happened to clang? Thought their codebase was supposed to be easier and more pleasant to work with? Or maybe just more hardcore compiler devs work on gcc?
Also modules was a lot and was kind of the reason it took so long. They are wonderful and I want them but proper implementations (even with many details being implementation defined) required a lot of work to figure out.
Most of the time all the compilers get ahead of the actual release but in that case there were so many uncertainties only rough implementations were available beforehand and then post release they had to make adjustments to how they handled incremental compilation in a user facing way effectively.
There are also things like template for or inplace_vector. I think it has useful things. Just not all things are useful to everyone.
But I do think the frustration that C++ can no longer be a super set of C is overblown by C++.
C++ is sub-optimal for almost any task. For low level stuff plain C or maybe Rust. for higher level Python, Lua, or some Lisp. C++ is a weird in-between language that's impossible to hold correctly.
The nice thing about C++ is that you can more or less turn it into C, if you want. My C++ code is closer to C than idiomatic, modern C++, but I wouldn't want to miss the nice parts that C++ adds, such as lambda functions and the occasional template for generalization. Pretty much the only thing I'm missing from C are order-independent designated initializers, which became order-dependent in C++, and thus useless.
> "Haven't seen anything I'd rather used" reads like "Haven't gotten over the initial learning curve with any other tool"
What an odd thing to say. I simply don't like certain design decisions in other languages that I've checked out and tried, and therefore do not see any reason to switch. E.g. I tried Rust, but it's absolutely terrible for quick&dirty prototyping, which is my main job.
Just beat it. Ah, not so easy huh? Libraries, ecosystem, real use, continuous improvements.
Even if it does not look so "clean".
Just beat it, I will move to the next language. I am still waiting.
I for one can write C++ but I cannot write a single program in C. If the overlap was so vast, I would be able to write good C but I cannot.
I've done things with templates to express my ideas in C++ that I cannot do in other languages, and the behaviour of deterministic destructors is what sets it apart from C. It is comprehensible and readable to me.
I would argue that C++ is modern, since it is in use today. Perhaps your definition of "modern" is too narrow?
Finally, it certainly helps to have a standardized mechanisms instead of everyone rolling their own, especially with multiple libraries.
You are passing in a memory location that can be read or written too.
That’s it.
But like modules and concepts the committee has opted for staggered implementation. What we have now is effectively syntax sugar over what could already be done with asserts, well designed types and exceptions.
An argument can be made that C++26 features like reflection add complexity but I don't follow that argument for contracts.
This should also clarify the complexity issue.
This is a common sentiment about C++, but I find it very interesting that everyone seems to have a different feature in mind when they say it.
Half Life 3 rules apply. Every time someone complains about complexity in C++, the committee adds a new overly complex feature. It remains a problem because complexity keeps getting shoveled on top of the already complex language.
I don't think this opinion is well informed. Contracts are a killer feature that allows implementing static code analysis that covers error handling and verifiable correct state. This comes for free in components you consume in your code.
https://herbsutter.com/2018/07/02/trip-report-summer-iso-c-s...
Asserting that no one wants their code to correctly handle errors is a bold claim.
Modern C++ contracts are being sold as being purely for debugging. You can't rely on contracts like an assert to catch problems, which is an intentional part of the design of contracts
Different TUs can be compiled with different settings for the contract behavior. But can they be binary compatible? In general, no. If a function is declared in-line in a header, the comoiler may have generated two different versions with different contract behaviors, which violates ODR.
What happens if the contract check calls a helper function that throws an exception?
The whole things is really, really complex and I don't assume that I understand it properly. But I can see that there are some valid concerns against the feature as standardized and that makes me side with the opposition here: this was not baked enough yet
It sounds like it should solve your problem. At first it seems to work. Then you keep on finding the footguns after it is too late to change the design.
Contracts as they are today won't solve every problem. However they can expand over time to solve more problems. (or at least that is the hope, time will tell - there is already a lot of discussion on what the others should be)
In Rust these types of proposals are common, in C++ less so. The incredibly tedious release process encourages everyone to put in just as much complexity as they can safely get away with.
I've been thinking that way for many years now, but clearly I've been wrong. Perhaps C++ is the one language to which the issue of excess complexity does not apply.
I think what you meant to say is popular. If a feature is popular it doesn't matter how bad it turns out in hindsight: you can't remove it without breaking too much code (you can slowly deprecate it over time, I'm not sure how you handle deprecation in D, so perhaps that is what editions give you). However if a great feature turns out not to be used you can remove it (presumably to replace it with a better version that you hope people will use this time, possibly reusing the old syntax in a slightly incompatible way)
Now I'm old and I believe D has missed its opportunity. Kinda sad.
So perhaps I think the issue is not committees per se, but how the committees are put together and what are the driving values.
It worked remarkably well. r7rs-small was done in 2013 and is enjoyed by many. The large variant is still not done and may never be done. That's no problem though, the important point was that it created a place to point people with ideas to instead of outright telling them "no".
https://en.cppreference.com/w/cpp/language/function.html#Fun... is vaguely better, but still quite dense.
IMO the syntax makes things hard for a newcomer to the syntax to understand, which I see as core to any programming language's goals of community.
double square_root(double num) asserts_pre(num >= 0)
would have been far more self-evident than just double square_root(double num) pre(num >= 0)
But I suppose brevity won out.We already have some primitive ways to define preconditions, notably the assert macro and the 'restrict' qualifier.
I don't mind a more structured way to define preconditions which can automatically serve as both documentation and debug invariant checks. Though you could argue that a simpler approach would be to "standardize" a convention to use assert() more liberally in the beginning of functions as precondition checks; that a sequence of 'assert's before non-'assert' code should semantically be treated as the functions preconditions by documentation generators etc.
I haven't looked too deep into the design of the actual final contracts feature, maybe it's bad for reasons which have nothing to do with the fundamental idea.
It sounds (and probably is) insane. But if a feature breaks backwards compatibility, or can't be implemented in a way that non-negligibly affects compiler/IDE performance for codebases that ignore it, what's the issue? Specifically, what significant new issues would it cause that C++’s existing bloat hasn’t?
C++20 isn't fully implemented in any one compiler (https://en.cppreference.com/w/cpp/compiler_support.html#C.2B...).
Offtopic, but this is a problem in the web world, too. Once something is on a standards track, there are almost mechanisms to vote "no, this is bad, we don't need this". The only way is to "champion" a proposal and add fixes to it until people are somewhat reasonably happy and a consensus is reached. (see https://x.com/Rich_Harris/status/1841605646128460111)
And then people complain about C++ for doing it wrong, or its complexity, and show language 'X' that does it better/right, but only because they saw C++ do it first, and 'not quite right'.
I expect contracts to be similar - other languages will watch, learn, and do version two, and then complain about c++, etc.
It took 'quite a while' to get rid of auto_ptr, for example.
If it wasn't for the fact this is a language feature, it would be better off in boost where it can be tested in the wild.
But in fairness, when was D&E first published? Argued for auto there, long before their acceptance. Argued for implicit template instantiation - thank god the "everything-must-be-explicit" curmudgeons were vanquished there, too.
He's got a pretty good batting average - certainly better than Herb Sutter.
Initializes lists suck mainly because of C source compat constraints, too. In fact, most things that suck in C++ came from B via C.
I’m fascinated by the concept of deciding how much complexity (to a human) a feature has. And then the political process of deciding what to remove when everyone agrees something new needs to be accepted.
> bloated committee design and also incomplete
That's truly in that backdoor alley catching fire
Also almost every feature added to C++ adds a great deal of complexity, everything from modules, concepts, ranges, coroutines... I mean it's been 6 years since these have been standardized and all the main compilers still have major issues in terms of bugs and quality of implementation issues.
I can hardly think of any major feature added to the language that didn't introduce a great deal of footguns, unintended consequences, significant compilation performance issues... to single out contracts is unusual to say the least.
It does have a runtime cost. There's an attribute to force undefined behavior on read again and avoid the cost:
int x [[indeterminate]];
std::cin >> x;But if you really, really want to leave it uninitialized, write:
int x = void;
where you're not writing that by accident.This requires that there is a default. Several modern languages (such as Go) insist on this, it means now your types don't even model reality in this very fundamental way. Who is a person's default spouse? Even where you can imagine a default it's sometimes undesirable to have one, for example we already live in a society where somebody decided default gender is male - and it might look too much like real data, default birthday being 1 January also matches hundreds of thousands of Americans...
The most likely place you go after "Everything has a default" is the billion dollar mistake because you're inclined to just incorporate "or it's default invalid" into the type definition to get your default, and when you do that everywhere needs to have "check it's valid" code added, even if we already just checked that a moment ago.
Either way no, that can't work.
Kotlin has explicit nullable types. Rust has no null, but has option types. Both languages work fine.
I think your point was that neither approach could reasonably be retrofitted to C++, do I have that right?
It can be easier, but not always. But it is almost always a better design.
Redesigning software so that whole classes of problems simply can't exist is absolutely better than software that needs to handle all kinds of problems. Many of those problems might not even ever happen in real circumstances!
> Requiring all things to have a zero value, even when they do not have one, makes it harder to be correct by construction, not easier.
Don't require a "zero value". Require a "correctly-constructed" value. Sometimes zero is correctly constructed, sometimes not.
D default initializes floats to NaN.
Thus the current erroneous. It means this isn't a bug (compilers used to optimized out code paths where an uninitialized value is read and this did cause real world bugs when it doesn't matter what value is read). It also means the compiler is free to put whatever value they want there - one of the goals was the various sanitizers that check for using uninitialized values need to still work - the vast majority of the time when an uninitialized value is read that is a bug in the code.
There are a lot of situations where a compiler cannot tell if a variable would be used uninitialized, so we can't rely on compiler warnings (it sometimes needs solving the halting problem).
It's an explicit choice in C++ to always accept correct programs (the alternative being to always reject incorrect programs†). The committee does not have to stick by this bad decision in each C++ version, of course they aren't likely to stop making the same bad choice, but it is possible to do so.
If you're allowed to take the other side, you can of course (Rust and several other languages do this) reject programs where the compiler isn't satisfied that you definitely always initialize the variable before it's value is needed. Most obviously (but it's pretty annoying, so Rust does not do this) you could insist on the initialization as part of the variable definition in the actual syntax.
† You can't have both, by Rice's Theorem, Henry Rice got his PhD for figuring out how to prove this, last century, long before C++ was conceived. So you must pick, one or the other.
The `= void` syntax can be because it is currently not valid.
D (unlike C++) always has a default initializer, but does not allow a default constructor. This is sometimes controversial, but it heads off all kinds of problems.
The default initializer for floating point values is NaN. (And for chars it is 0xFF.) The point of this is for the value to not "happen" to work.)
...But the change to EB in this case does initialize everything by default?
The only difference is some optimizer used to eliminate code paths where they could prove that path would read an uninitialized variable - causing a lot of weird bugs in the real world.
I'm surprised the "= void;" wasn't discussed. People liked it immediately in D, and other alternatives were not proposed.
1. If there’s no initializer and various conditions are met, then “the bytes have erroneous values, where each value is determined by the implementation independently of the state of the program.
What does “independently” mean? Are we talking about all zeros? Is the implementation not permitted to use whatever arbitrary value was in memory? Why not?
2. What’s up with [[indeterminate]]? I would expect “indeterminate” to mean that the variable has a value that happens to be arbitrary (and may contain sensitive data, etc), not that it turns back into actual UB.
It can pick whatever value it wants and doesn't have to care what the program is doing.
Also the value has to stay the same until it's 'replaced'.
> Are we talking about all zeros?
It might be, but probably won't be. What makes you bring up all zeroes?
> Is the implementation not permitted to use whatever arbitrary value was in memory? Why not?
(Edit: probably wrong, also affects other things I said) It can. What suggests it wouldn't be able to?
> 2. What’s up with [[indeterminate]]? I would expect “indeterminate” to mean that the variable has a value that happens to be arbitrary (and may contain sensitive data, etc), not that it turns back into actual UB.
"has a value that happens to be arbitary" would be the default without [[indeterminate]]. Well, it can also error out if the compiler wants to do that.
"Whatever value was in memory" would be depending on the (former?) state of the program, wouldn't it?
And would that mean there's still no way to say "Don't waste time initializing it, but don't do any UB shenanigans either. (Basically, pretend it was initialized by a random number generator.)"
I'd argue that once you get the memory it's now part of the state of your program, which precludes it from being involved in whatever value you end up reading from the variable(s) corresponding to that memory.
> If I want a fixed init value per address, is that allowed as a hardening feature or disallowed as being based on allocation patterns?
I'd guess that that specific implementation would be disallowed, but as I'm an internet nobody I'd take that with an appropriately-sized grain of salt.
> And would that mean there's still no way to say "Don't waste time initializing it, but don't do any UB shenanigans either. (Basically, pretend it was initialized by a random number generator.)"
I feel like you'd need something like LLVM's `freeze` intrinsic for that kind of functionality.
It means what it says on the tin. Whatever value ends up being used must not depend on the state of the program.
> Are we talking about all zeros?
All zeros is an option, but the intent is to allow the implementation to pick other values as it sees fit:
> Note that we do not want to mandate that the specific value actually be zero (like P2723R1 does), since we consider it valuable to allow implementations to use different “poison” values in different build modes. Different choices are conceivable here. A fixed value is more predictable, but also prevents useful debugging hints, and poses a greater risk of being deliberately relied upon by programmers.
> Is the implementation not permitted to use whatever arbitrary value was in memory?
No, because the value in such a case can depend on the state of the program.
> Why not?
Doing so would defeat the purpose of the change, which is to turn nasal-demons-on-mistake into something with less dire consequences:
> In other words, it is still an "wrong" to read an uninitialized value, but if you do read it and the implementation does not otherwise stop you, you get some specific value. In general, implementations must exhibit the defined behaviour, at least up until a diagnostic is issued (if ever). There is no risk of running into the consequences associated with undefined behaviour (e.g. executing instructions not reflected in the source code, time-travel optimisations) when executing erroneous behaviour.
> What’s up with [[indeterminate]]?
The idea is to provide a way to opt into the old full-UB behavior if you can't afford the cost of the new behavior.
> I would expect “indeterminate” to mean that the variable has a value that happens to be arbitrary (and may contain sensitive data, etc), not that it turns back into actual UB.
I believe the spelling matches how the term was used in previous standards. For example, from the C++23 standard [0] (italics in original):
> When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced.
[0]: https://open-std.org/JTC1/SC22/WG21/docs/papers/2023/n4950.p...
What nasal demons?
UB is permitted to format your disk, execute arbitrary code, etc. But there’s lots of room between deterministic values and UB. For example, taking a value that does depend on the previous state of the program and calling it the “erroneous” value would give a non-UB, won’t format your hard disk solution. And it even makes quite a lot of performance sense: the value that was already in the register or at that address in memory is available for free! The difference from C++23 would be that using that value would merely be erroneous and not UB.
And I think the word “indeterminate” should have been reserved for that sort of behavior.
Those that result from the pre-C++26 behavior where use of an indeterminate value is UB.
> But there’s lots of room between deterministic values and UB.
That's a fair point. I do think I made a mistake in how I represented the authors' decision, as it seems the authors intentionally wanted the predictability of fixed values (italics added):
> Reading an uninitialized value is never intended and a definitive sign that the code is not written correctly and needs to be fixed. At the same time, we do give this code well-defined behaviour, and if the situation has not been diagnosed, we want the program to be stable and predictable. This is what we call erroneous behaviour.
> And I think the word “indeterminate” should have been reserved for that sort of behavior.
Perhaps, but that'd be a departure from how the word has been/is used in the standard so there would probably be some resistance against redefining it.
Just like when I was learning rust and trying to read some http code but it was impossible because each function had 5 generics and 2 traits.
So I would have to look it up and be very careful about it since I can break something easily in C++.
This just makes things more difficult from the perspective of using/learning the language.
Similar problem with "unsequenced" and "reproducible" attributes added in c. It sounded cool after I took the time to learn exactly (/s) what it means. But it is not worth the time to learn it. And it is not worth the cognitive load it will put on people that will read the code later imo.
const int a = 10; // Just an immutable variable named a
constexpr int b = 20; // Still an immutable variable named b
static constinit int c = 30; // Now it isn't even immutable
For functions const says this function promises it doesn't change things, constexpr says this function is shiny and modern and has no other real meaning (hence "constexpr all the things" memes, you might as well) but consteval does mean that we're promising this must always be evaluated at compile time, so the evaluation is frozen by runtime, however only a function can have this label.Volatile is a mess because what you actually want are the volatile intrinsics, indeed you might want more (or fewer) depending on the target. If your target can do single bit hardware writes it'd be nice to provide an intrinsic for that, rather than hoping you can write in code REG |= 0x40 and have that write a single bit... which on platforms which do not have this single bit write feature that's going to compile to an unsynchronized read-modify-write which may cause problems. However instead of having intrinsics C's volatile was hacked into the type system instead and C++ tries to keep that.
How far is Clang on reflection and contracts?
It has been on compiler explorer for a while
This has been used to add reflection to simdjson
It's nice to have new features, but what is really killing C++ is Cargo. I don't think a new generation of developers are going to be inspired to learn a language where you can't simply `cargo add` whatever you need and instead have to go through hell to use a dependency.
In the interest of pedantry, locating source files relative to the crate root is a language-level Rust feature, not something specific to Cargo. You can pass any single Rust source file directly to rustc (bypassing Cargo altogether) and it will treat it as a crate root and locate additional files as needed based on the normal lookup rules.
For most crates, yes. But you might be surprised how many crates have a build.rs that is doing more complex stuff under the hood (generating code, setting environment variables, calling a C compiler, make or some other build system, etc). It just also almost always works flawlessly (and the script itself has a standardised name), so you don't notice most of the time.
And Mesons awesome dependency handling:
https://mesonbuild.com/Dependencies.html
https://mesonbuild.com/Using-the-WrapDB.html#using-the-wrapd...
https://nibblestew.blogspot.com/2026/02/c-and-c-dependencies...
I suffered with Java from Any, Maven and Gradle (the oldest is the the best). After reading about GNU Autotools I was wondering why the C/C++ folks still suffer? Right at that time Meson appeared and I skipped the suffering.
* No XML
* Simple to read and understand
* Simple to manage dependencies
* Simple to use options
Feel free to extend WrapDB.In C++ you don't get lockfiles, you don't get automatic dependency install, you don't get local dependencies, there's no package registry, no version support, no dependency-wide feature flags (this is an incoherent mess in Meson), no notion of workspaces, etc.
Compared to Cargo, Meson isn't even in the same galaxy. And even compared to CMake, Meson is yet another incompatible incremental "improvement" that offers basically nothing other than cute syntax (which in an era when AI writes all of your build system anyway, doesn't even matter). I'd much rather just pick CMake and move on.
> I’m still surprised how people ignore Meson. Please test it :)
I did just that a few years ago and found it rather inconvenient and inflexible, so I went back to ignoring it. But YMMV I suppose.
> After reading about GNU Autotools
Consider Kitware's CMake.
C and C++ are usually stuck in that antiquated thinking that you should build a module, package it into some libraries, install/export the library binaries and associated assets, then import those in other projects. That makes everything slow, inefficient, and widely dangerous.
There are of course good ways of building C++, but those are the exception rather than the standard.
The standard was initially meant to standardize existing practice. There is no good existing practice. Very large institutions depending heavily on C++ systematically fail to manage the build properly despite large amounts of third party licenses and dedicated build teams.
With AI, how you build and integrate together fragmented code bases is even more important, but someone has yet to design a real industry-wide solution.
It's the same with compilers, there's not one single implementation which is the compiler, and the ecosystem of compilers makes things more interesting.
The fact that building C++ is this opaque process defined in 15 different ways via make, autoconf, automake, cmake, ninja, with 50 other toolchains is something that continues to create a barrier to entry.
I still remember the horrors of trying to compile c++ in 2004 on windows without anything besides borland...
Standardizing the build system and toolchain needs to happen. It's a hard problem that needs to be solved.
I agree, and I also think it’s never happening. It requires agreeing on so many things that are subjective and likely change behaviour. C++ couldn’t even manage to get module names to be required to match the file name. That was for a new feature that would have allowed us to figure out esports without actually opening the file…
You cannot cargo add Unreal, LLVM, GCC, CUDA,...
What'll spur adoption is cmake adopting Clang's two-step compilation model that increases performance.
At that point every project will migrate overnight for the huge build time impact since it'll avoid redundant preprocessing. Right now, the loss of parallelism ruins adoption too much.
yes you have CPM, vcpkg and conan, but those are not really standard and there is friction involved in getting it work.
Personally I use them in new projects using XMake and it just works.
Meanwhile C++ build system is an abomination. Header files should be unnecessary.
1. Standardize a `restrict` keyword and semantics for it (tricky for struct/class fields, but should be done).
2. Uniform Function Call Syntax! That is, make the syntax `obj.f(arg)` mean simply `f(obj, arg)` . That would make my life much easier, both as a user of classes and as their author. In my library authoring work particularly. And while we're at it, let us us a class' name as a namespace for static methods, so that Obj::f the static method is simply the method f in namespace Obj.
3. Get compiler makers to have an ABI break, so that we can do things like passing wrapped values in registers rather than going through memory. See: https://stackoverflow.com/q/58339165/1593077
4. Get rid of the current allocators in the standard library, which are type-specific (ridiculous) and return pointers rather than regions of memory. And speaking of memory regions (i.e. with address and size but no element type) - that should be standardized too.
Someone has to bring a written spec to WG21 meetings and push it through.
And like in every open source project that doesn't go the way we like, the work is only done by those that show up.
In many ways, it isn't.
> Someone has to bring a written spec to WG21 meetings and push it through.
That is one way it is not like (most) other FOSS projects. In a typical FOSS project, there are bug reports and feature/change requests that people file. They don't have to write a full article merely for their idea to be given the time of day. Certainly not have to appear physically at meetings held elsewhere in the world. Of course, the question of the extent to which ideas and requests from the public are considered seriously and fairly is a spectrum - some FOSS projects give them more attention and consider them seriously, others do not. vis-a-vis WG21 the "public" is, to some extent: Compiler author teams, standard library author teams, national bodies, and large corporations using C++. This is perhaps not entirely avoidable, since there are millions of C++ users, but still.
Anyway, what I described isn't just some personal ideas of mine, it is for the most part ideas which have been put forward before the committee, either directly in submitted papers or indirectly via public discussion in fora the committee is aware of.
2. Name lookup and overload resolution is already so complex though! This will likely never be added because it's so core c++ and would break so much. imo, it also blurs the line between what's your interface vs what I've defined.
3. This is every junior c++ engineers suggestion. Having ABI breaks would probably kill c++, even though it would improve the language long term.
4. Again, you make solid points and I think a lot of the committee would agree with you. However, the committee's job is to adapt C++ in a backwards supporting way, not to disrupt it's users and API each new release.
There are definitely things to fix in c++ and every graduate engineer I've managed has had the same opinions of patching the standard, without considering the full picture.
* Don't pay for what you don't use;
* Not leaving for another language between C++ and assembly (or to phrase it differently: "when you use an abstraction mechanism appropriately, you get at least as good performance as if you had hand-coded using lower-level constructs")
and the lack of `restrict` breaks both of these, significantly. Because compilers are forced to implement even simple functions with repeated re-reading of data - due to the possibility of aliasing - which the software developer knows is entirely unnecessary, and would have avoided had he been writing the same function in, say, C (and of course compiler IR or assembly)
Re (2): It's not really "core C++": It would not make any existing program non-well-formed, nor change its semantics, at all. But it's true that this would have an impact on how we design classes - and that's the exact intent. And it does far more than "blur the line between what's your interface vs what I've defined" - it deletes most of this line, ,and that is exactly the point. The line we should have is the line of acccess restriction: Does a method have access to the class' private data, or doesn't it. If it doesn't, then, there are simply functions which take an oject of the class; and it doesn't matter if the class author defined them or if someone else defined them.
Re (3.): I didn't say lack of backwards compatibility, just that going forwards, ABIs would allow some things which are currently prevented [1]. I am not an ABI expert in the least, but IIUC, use of new ABI can be marked, so that nothing gets mixed up.
I would also claim that ABI stability should cede to the design principles I mentioned above.
This should be your default stack on any small-to-medium sized C++ project.
Bazel, the default pick for very large codebases, also has support for C++20 modules.
But once it works and you setup the new stuff, having started a new CPP26 Project with modules now, it's kinda awesome. I'm certainly never going back. The big compilers are also retroactively adding `import std` to CPP20, so support is widening.
[1] https://gitlab.kitware.com/cmake/cmake/-/work_items/27706
Clang says "we don't need them", GCC says "we'll ship them in libstdc++", and MSVC says "you are supposed to provide them".
I didn't know about that when I was working on finishing import std for CMake and accidentally broke a lot of code in the move to a native implementation of the module manifest format, so everything got reverted and put back into experimental.
Header-only projects are the best to convert to modules because you can put the implementation of a module in a "private module fragment" in that same file and make it invisible to users.
That prevents the compile-time bloat many header-only dependencies add. It also avoids distributing a `.cpp` file that has to be compiled and linked separately, which is why so many projects are header-only.
That being said, while it looks better than CMake, for anything professional I need remote execution support to deviate from the industry standard. Zig doesn't have that.
This is because large C++ projects reach a point where they cannot be compiled locally if they use the full language. e.g. Multi-hour Chromium builds.
https://github.com/bazelbuild/remote-apis
Once you get a very large C++ project with several thousand compilation jobs over hundreds of devs, you need to distribute the build across multiple computers and have a shared cache for object files.
Zig doesn't seem to support that.
But headers are perfectly fine to deal with and have been for decades and decades! Next you'll be arguing that contents pages in all books should be removed.
"But headers are perfectly fine to deal with and have been for decades and decades!"
I would have thought this was a joke... but your follow-up indicates otherwise.
So what are you going to float next? "So what if people broke their arms hand-cranking their cars to start them! People did it for years, so we shouldn't move to electric starters! Or automatic chokes!"
"And these electric ice boxes are for the birds! Speaking of which: People flying through the air in machines? POPPYCOCK!"
I know C# does some magical things (I know not what) to avoid this since there are no headers so you can refer to something in the same assembly/namespace thingy, but it's very different to how C++ compiles and links?
I wasn't saying that progress should be stinted, but that complaining about something with the expectation that it magically be solved is a strange view of the world. Do you also just assert that poverty and war shouldn't exist?
There are edge cases where destructive moves are not safe and it is impossible for the compiler to know they aren't safe. C++ uses non-destructive moves when it can't prove the safety of destructive moves, even if destructive moves may in fact be safe. C++26 adds a type annotation that guarantees destructive moves are safe in cases where you can't prove they are un-safe.
The concept of relocatable types is actually a bit broader in scope than just destructive moves, but destructive moves are one of the things it enables. It is a welcome change.
I thought those were removed? For example, see Herb's 2025-11/Kona trip report [0]:
> For trivial relocatability, we found a showstopper bug that the group decided could not be fixed in time for C++26, so the strong consensus was to remove this feature from C++26.
[0]: https://herbsutter.com/2025/11/10/trip-report-november-2025-...
auto value = create_value();
if (some_cond) {
consume_value(std::move(value)); // not sure whether it's move here, but I guess my point is clear
}
use_value(value);eg. B = std::move(A); // You are worried about touching A when it's in this indeterminate state?
Currently move semantics in C++ requires that A is left in a 'moved from, but valid state' which means that:
1. The compiler must still generate code that calls the destructor.
2. Every destructor need have to have some flag and a test in it like: if(moved_from) // do nothing else { free_resources(); }
(Granted, for some simple types the compiler might inline and removed redundant checks so it ends up with no extra code, but that is not guaranteed)
With destructive moves the compiler can just forget about the object completely, no need to call it, destructurs can be written as normal and only care about the invariants established in the constructor.
A = 0; B = 0; A = move(B); ++B; ++A;
What should happen at ++B, and what should A be at the end? How would the compiler enforce this? I can see this being complicated.
The compiler can forget about it, but the code doesn't, so you mismatch between what is on screen and what the compiler does, which seems even more confusing.
A quick dirty hack would be to have a static analyzer drop B from the symbol table after it sees std::move and give a warning, but that obviously wont catch other references to it, but maybe it will catch the low hanging fruit.
Generalizing Overloading for C++2000
Addresses are one thing, but the inverse has its own logic. In terms of (mental) planning you want to know that you need to go to the UK then London then Croydon, otherwise there's an element of "where's that?" as you read left to right.
Date in Europe: 30/03/2026
Date in China: 2026/03/30
Then you have Little Endian and you have Big Endian.
TL;DR: Some humans like to talk about the specific and then the general and others vice versa.
But here is really why I think the author referred to it as "London, Croydon"
"London, Croydon" communicates "Hey we had this C++ standards meeting in London, one of the coolest cities in the world. (Be jealous!). We were helping add more complexity to the most complex language in the world in the lovely environment of London, England. Croydon is a piece of irrelevant detail... meeting was in London, remember that !
"Croydon, London" communicates "Hey we had this C++ standards meeting in gritty Croydon... it was in London so I guess it was OK ?? Sorry our budgets could not put us up in Westminister, London"
[End of Joke]
Most things "in" London aren't in the centre unless they're tourist destinations or they're extremely old. The most surprising thing I ran into right in the centre was the International Maritime Organisation's headquarters, which is right on the Thames because historically that makes sense in a way that arguably it already didn't when that was built, and certainly not today.
https://www.hpcwire.com/2022/12/05/new-c-sender-library-enab...
Overlapping communication and computation has been a common technique for decades in high-performance computing to "hide latency", which leads to better scaling. Now standard C++ can be used to express parallel algorithms without tying to a specific scheduler.
Proper reflection is exciting.
Also, useful: https://gcc.gnu.org/projects/cxx-status.html
Also, some of the implementation details and semantics do matter in an application dependent way, which makes it a bit of an opinionated feature. I would guess there is a lot of arguing over the set of tradeoffs suitable for a standard, since C++ tends to avoid opinionated designs if it can.
Someone has to write a proposal, bring it to the various meetings, and getting it to win the features selection election from all parties.
Also WG21 tends to disregard C features that can already be implemented within C++'s type system.
Embedded is such a perfect fit for interface-based programming, but because it cant determine call resolution outside of a single source file, EVERYTHING gets vtable'd, which ruins downstream optimizations.
There's some ugly workarounds.... CRTP, c-style (common header + different source files. To the person who says "use templates!".... no. I dont like templates. They are verbose, complex, and every time i try to use them they I end up foot-gunning myself. Maybe its a skill issue, but if you designed something that most people cant figure out, I'd argue the design is wrong.
C++ is SOOO close to doing compile-time polymorphism. If just needs a way to determine type across source files, which LTO sorta-kinda-but-not-really does.
I've seen some examples of C++ contracts replacing CRTP, but it used templates, which again, not a fan of.
I think you meant concepts.
C++ Concepts are the right answer in my opinion, if you want compile time polymorphism against an interface.
I don't think, there is a way around templates, they are C++'s way of compile-time polymorphism. Other languages, which allow for compile-time polymorphism, have similar mechanisms with similar constraints. I get where you come from, when you say that you're not a fan of templates, though. At least concepts help with clearer error messages for templates.
One advantage, that concepts have over CRTP is, that only consumers of your interface, not implementers, need to know about your concept.
The US government still uses C++ widely for new projects. For some types of applications it is actually the language of choice and will remain so for the foreseeable future.
Can you give an example please? And how does it correspond to government ONCD report and other government docs "recommending" "safe" languages like: Rust (noted for its ability to prevent memory-unsafe code), Go, Java, Swift, C#, Ruby, Ada
Among other things I design and implement high performance C++ backends. for some I got SOCS2 Type II certification but I am curious about future. Do not give a flying fuck about what the criteria for military projects as I would not ever touch one even if given a chance.
An increasingly common requirement is the ability to robustly reject adversarial workloads in addition to being statically correct. Combined with the high-performance/high-scale efficiency requirements, this dictates what the software architecture can look like.
There are a few practical reasons Rust is not currently considered an ideal fit for this type of development. The required architecture largely disables Rust's memory-safety advantages. Modern C++ has significantly better features and capabilities under these constraints, yielding a smaller, more maintainable code base. People worry about supply chain attacks but I don't think that is a major factor here.
Less obvious, C++ has strong compile-time metaprogramming and execution features that can be used to extensively automate verification of code properties with minimal effort. This allows you to trivially verify many correctness properties of the code that Rust cannot. It ends up being a comparison of unsafe Rust versus verification maximalist C++20, which tilts the safety/reliability aspects pretty hard toward C++. Code designed to this standard of reliability has extremely low defect rates regardless of language but it is much easier in some languages than others. I even shipped Python once.
A lot of casual C++ code doesn't bother with this level of verification, though they really should. It has been possible for years now. More casual applications also have more exposure to memory safety issues but those mostly use Java in my experience, at least in government.
Would you be willing to share some more information about this? Interested in learning more since this sort of thing rarely seems to come up in typical situations I work in.
Now do C++27. Why do we need every year a standard ? CADT ?
Of course, this is not completely guaranteed safety - but safety has certainly mattered.
Yes, this what Stroustrup said and it makes me laugh. IIRC he phrased with a more of a 'we had safety before Rust' attitude. It also misses the point, safety shouldn't be opt-in or require memorising a rulebook. If safety is that easy in C++ why is everyone still sticking their hand in the shredder?
As for the Core Guidelines - most of them are not about safety; and - they are not to be memorized, but a resource to consult when relevant, and something to base static analysis on.
But C++ projects are usually really boring. I want to go back but glad I left. Has anyone found a place where C++ style programming is in fashion but isn't quite C++? I hope that makes sense.