When a language's creator and its owners strongly disagree [pdf]
isocpp.org
isocpp.org
I remain unconvinced as why this is a necessary feature over the already existing `assert` statements.
This line is where I realised it's Claude. Also note the emdashes.
Bjarne’s opposition to contracts might have tipped the scale but there is definitely growing weariness (and wariness) about large half-baked features that are left as exercises to the implementors (looking at you modules).
Ignoring that historical context when analysing this vote seems like a mistake.
It also lists 6 possible paths forward for C++26 with pros and cons. I think they are roughly:
A. Skip an official C++26 standard but roll these features into C++29.
B. Remove contracts from C++26 and pass it.
C. Say "please" and vote again.
D. Tweak contracts regarding virtual functions and vote again.
E. Pass C++26 as many different countries' national standards rather than an international standard.
F. Do E and also consider leaving ISO and moving to ANSI/INCITS instead.
If, on the other hand, you mean only that, regardless of the status of a contract, if the compiler can detect that it would be violated, it must have failed to compile, I guess that would be all for that. On the other hand, the state of theorem provers that are able to prove things will and won't work is ... evolving. There will a lot of people be frustrated when they upgrade their compiler to a minor point release and suddenly a lot of code fails to compile because it always was broken but likely in an obscure situation that never happens in the real world.
I'm not sure I can express it better than the proposal to add the corresponding feature [0]:
> This extension addresses the Romanian National Body's (NB) concern by enabling the writing of an individual contract assertion that guarantees the program execution will not continue past the contract assertion if it is violated, regardless of the semantics of other contract assertions. This addition to the C++26 Contracts facility allows reliable use of enforced contract assertions in large codebases with mixed contract semantics and also in hardened libraries.
> Is it worth the trade-offs?
Apparently some people/NBs feel quite strongly that it is.
> There's a reason why most code turns assert off in production and only does it in debug builds.
There's also a reason some codebases define asserts that aren't turned off in production in addition to debug-only asserts. The proposal lists multiple under Section 3.1 Existing Practice.
> and so if the code is trusted to work, you wouldn't want to check that it's working correctly.
Right, but sometimes you might think the consequences of such blind trust being misplaced are worse than the performance hit.
Static detection of impossible-to-satisfy contract conditions would be interesting, but as you say the technology is probably not ready for the kind of widespread use that inclusion in C++ would entail.
[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p39...
But these proposed contracts... I just don't really see much benefit it offers over just explicitly writing your own assertions.
I suppose you can see the requirements in the declarations, but I would wager that fancy modern C++ declarations quickly become so long and verbose that you'd get quickly fatigued from reading them. Perhaps in the LLM era they are more useful?
It's basically everyone learning from everyone else on what works and what doesn't work.
C++ 11 wants to model two non-trivial orderings, Acquire-Release and Consume-Release, but only Acquire-Release is actually usable. So yes, C and Rust both also choose to offer Acquire-Release ordering like C++ but it's also clear that the universe of possibilities is actually smaller than WG21 realised when C++ 11 was developed.
If on your first random stab you guess 65537 is prime, you're correct and you might foolishly assume all those Fermat Numbers are prime and then try say, 4294967297. Nope, that's divisible by 641 for some reason. There are only Five of these Fermat Primes so far as we know, they weren't common after all.
I sometimes wonder how difficult it would be for a compiler that was designed from the outset to implement Consume correctly.
I've sketched what I think an ideal "language of today" would look like, and one of the core tenets is making concurrent code explicit and part of the design surface.
You might imagine a webserver with main code like:
spawn(on_core: 0) {
await receive_packet(input)
}
Or iterating over "num_cpus" and doing the same thing, with hashing based on CPU number for shared-memory resources.The vast, vast majority of software, and software developers, simply do not care about core affinity, NUMA, noisy neighbors, etc.
Even on systems or situations where this matters (massive number of cores and memory, multiple sockets, thousands of threads, etc.) the juice is rarely worth the squeeze.
Doubly so when you end up being I/O bound anyways.
Nearly every software developers wants their software to work across multiple deployment targets. Maybe you just target x86-64, and you don't care about the number of CPUs because you'll be upgrading your servers every couple of years.
No one wants to deal with difficult-to-reproduce issues related to scheduling in niche configurations. It's much more efficient for runtime or compiler engineers to solve these problems for us, leveraging the vast amount of resources of corporations to develop and test these features. Then you have a place your code can live happily ignorant of its runtime constraints.
And that, my friend, is the beauty of our industry.
The effects can be subtle and lurk for years until a seemingly unrelated change exposes them. It starts with optimizations like instead of spawning a thread, you use a thread pool, possibly abstracted as some kind of executor service. Other code might use different thread pools to isolate them because each pool has its own limits on the number of threads. Some might grow but usually they won't.
Until one day you end up with thread starvation and a deadlock through a series of benign seeming optimizations. There are threads around but not in the right pools and the entire thing grinds to a halt.
Cooperative async/await on a single thread is the ideal solution for most use cases, particularly with complete environment teardown between requests. Yes, this is basically the PHP model.
count := AtomicU32.new(0)
spawn {
loop {
input := try race {
sock.receive()
console.receive()
}
handle_packet(input)
count += 1
} match {
Error e -> log.error(e, "main loop failed")
nil -> log.info(fmt"Finished with {count} requests")
}
}
The receive functions are async so they can only be used inside an async context, like the one created by race. Race is a keyword from Verse that returns the result of the first finished task and cancels the other.In my opinion, that's really the best place for it. From what I understand, that's always been FORTRAN's secret sauce.
I've been doing threaded code since Day One (Machine Code), and have gotten it wrong, quite often. There are a million things that can cause concurrent code to actually perform worse than single-thread code.
These days, I usually just let the platform pick the threads, and generally write code that will play along.
Anything else heavily uses concurrency in all kinds of ways.
It seems like part of the concern is that the feature needs to be shipped right the first time because it'll live forever. Perhaps allowing features to be deprecated and then removed could help?
But idk, I haven't written anything that lasted 36 years, so maybe the current approach is for the best!
> pre-LLM, so maybe it's better now?
I came back to the toy tool some time later and used GLM 4.x to implement it further. I had a good experience with it and I understood the code it generated. From my experience, in general, LLM is a good mentor, if you are willing to ingest the content step by step. For me, it is no different from when I used StackOverflow (and other forums) to try and ask how a thing is done.
And was it the language itself or the libraries?
C++ definitely has complexity, but I'm surprised by the two examples specifically chosen. Which pointer? C++ only has 2: unique_ptr and shared_ptr (or 2.5 if you want to include weak_ptr but that necessarily means you already chose strong_ptr first). While this might be a complicated choice if you're just comparing to Python, but it shouldn't be if you're familiar with Rust since Rust has you make the same choice.
And then "how should I iterate" is similarly the same as any other language? Either a for or a for-each loop?
There's definitely baggage from 38 years of lessons learned, to be sure, just those examples are odd choices
When the language got operator overloading there was the question of how to convert the arguments from the operator itself to its implementation. You wanted to write a+b, not &a+&b but most versions of operator+ were going to want const pointers for efficiency. One option was to make the & implicit, another was to introduce a type of argument that didn't need & but acted like a pointer. So references were born.
I ignore people who advise to use references when something can't be null. A null reference is UB just like a null pointer, it just shifts where the UB is, but the compiler is likely to produce the same code either way and the program is just as likely to crash. Instead, I use pointers to make it obvious that something is a pointer, and references when it should be invisible, just like with operator overloading.
My understanding was that const references don't provide that much in the way of optimization opportunities (if any?) due to aliasing and the existence of const_cast.
And even without such a change, such a restriction is pretty easily worked around without a const reference with something like T&& temp = get_daily_prices(); sum(temp);. I don't think that'll result in different codegen than either passing a temporary to a const ref or allowing binding a temporary to a non-const ref.
Didn't they say they use references when the pointer/reference should be invisible?
Type& GetReference(Type*);
(Type is a placeholder, it was not templated). It worked exactly how you'd fear it would, and as a result we had to chase down null dereferences of our "can't be null" references all over the codebase.EDIT: by "exactly how you fear it would", it just did a dereference and return. It didn't check for null, didn't throw, and couldn't otherwise signal a failure.
https://en.cppreference.com/cpp/language/pointer
When I started C++ nearly 15 years ago this was already a huge problem. I bought some books and looked up online references and after a while I discovered that both were already out of date.
C++ is a chimera. And it hasn't improved since.
Other languages are far more stable, conceptually. New features rarely undermine or reinvent the older ones.
C++ is unique in the way it keeps modifying its own core. There is no formal deprecation or EOL process, so the language is more like an archaeological dig than a living standard.
Yes there is or we'd have been talking about std::auto_ptr as well.
> New features rarely undermine or reinvent the older ones.
Many languages have mistakes they've later tried to sweep under the rug. JavaScript arrow functions trying to convince everyone that bind(this) wasn't ever a thing, for example.
C++, for whatever reason, is consistently the only language where people are determined to always dig up the skeletons and pretend they are at all still relevant. It's not unique in having skeletons. It's not unique in having language spec mistakes that can't realistically be changed, just ammended with a newer, better alternative.
But like the saying goes, there are only two kinds of languages: the ones people complain about and the ones nobody uses
for (ignoring do and while) and foreach loops, std::for_each, ranges for_each at the very least
Java: for, for-each, do-while with iterator, list.forEach(..), list.stream().forEach(..), etc...
C++ does have many ways to do anything. Most of that is historical, or due to features and library facilities with overlapping capabilities, or inherent to multi-paradigm programming languages.
Every C++ project is written in its own language. Its own "house flavor." This is true of all languages, but is particularly stark for a language as old, complicated, and powerful as C++.
- it shines for some tasks where Rust isn't capable enough
- it's impossible to get rid of because of all the code so we may as well modernize it
- there's an active community of people who built a career of being C++ experts and have no interest to see the language disappears.
Even in the LLM era, I find it's difficult to code in C++. The language has so many corner cases that LLMs make mistakes. My experience 6 months ago was that the code written by my agent had tons of complex linter warning (exceptions, lambda leaking references, futures whatever... that I just can't avoid because the codebase mixes all of this). Whenever possible, it's probably time to either modernize it or port it to Rust, component by component, until the C++ part is small or inexistant.
Modern best practice for C++ is to crank the warnings to maximum, and to treat warnings as errors. You absolutely should treat linter-like warnings as an error, because they invariably are. So you know.
what should be conclude from this? this is like saying you tried to become a professional trapeze artist for a couple of days. C++ is complex and deep. what did you seriously hope to accomplish in a "few weeks"?
i started writing C++ professionally 4 years ago, i.e., i write it every single day at $JOB. there are still large parts of the language i'm not familiar with.
Yes, that's the point. Much of this complexity would not be necessary to achieve the same things, given the ability to redesign from scratch.
The answer is almost always: no pointer
Do you know C? Everything that works in C works in C++, modulo a few minor syntactic changes (void* doesn't implicitly convert). You can get acquainted to C++ features on an as-needed basis. Other people will surely disagree with me on this.
With pointer types in particular, I'd say use C-style pointers long enough to know how frustrating they are but also how they work. Then you'll know what unique_ptr and shared_ptr are doing to help you. (And those are the only two you need 99% if the time. weak_ptr is a spin-off of shared_ptr that you'll know if you need it. observer_ptr is just stupid ivory-tower stuff.)
weak_ptr is like shared_ptr but doesn't add to the count. When the last shared_ptr is destroyed it destroys the object even if there are still weak_ptrs. Any weak_ptrs pointing to that object become null. I won't dive into how that's actually implemented. Obviously it costs some CPU cycles to make that happen, so you don't see shared_ptr or weak_ptr a lot in code that's written with a performance mindset. Although you're still getting 90% of the performance benefit by writing C++ instead of JS even if it's "inefficient" C++.
I agree with GP's point and add, if this isn't something you need to do, then don't even think about it. C++ is like a toolbox with a bunch of tools. I don't study all the tools before building a table, I look up how to build a table, and then see if the tools are in my toolbox. Studying the triangle screwdriver would be a waste of time and only confuse me more, unless I'm trying to drive triangle screws. Only when I need to drive a triangle screw then I can look for the triangle screwdriver.
https://docs.python.org/3/library/weakref.html
Anyone that thinks Python is "simple", to come back to some of the suggestions on how it is easier than C++, they really should learn the language reference, standard library, and CPython FFI manuals.
Python is more "simple" in the sense that
- it is more forgiving with respect to badly written code,
- it has collected less historical baggage than C++.
Otherwise I agree.
Yeah, we all know how cleaning up Python has worked out, and unfortunately a few ecosystems failed to learn from it as well, e.g. Java 8 => 9, .NET Framework => Core.
You can of course use Python. But we are talking about languages with different applications. You want fast development hand have no expectations on speed? Use Python! You want to control your computer to get maximum performance with a safe type system? Use C++!
Since you are already talking about pointers: In Python you are not even given the chance to do all the stuff you can do in C++. You have to rely on garbage collection (and "del"). That is an entirely different thing than C++ where you exactly control how long your objects should live.
For the pointers (order I would suggest): 1) No pointers, use references whenever possible 2) Use std::unique_ptr 3) Use std::shared_ptr
You can also try to use shared_ptr all the time if it makes your transition from Python more easy. It is not optimal, but it may get you started.
And do not start wrapping your head around rvalue references right from the beginning. This is a very special topic, which you can ignore as a beginner.
Which also has plenty of way to do the same thing.
People often mistake the easiness to write Hello World, with the real expressiveness that is available in any given language.
1. C++ is unsafe. Bjarne keeps giving talks that effectively say "use it perfectly and don't make any mistakes and it's safe". That's not useful anymore. In a world where LLMs can find every mistake to exploit it, all the C++ projects are struggling to keep up and keeping them in C++ is basically guaranteeing there will be more exploits in the future. Yes, not every bug gets solved by switching to a safer language but reducing the attack surface in the new reality is no longer something that can be put off.
2. LLMs can re-write. The previous arguments used to be "there are C++ legacy code bases so C++ will never die". That was a valid argument before an LLM would just rewrite it all. I get people will say they aren't perfect, neither are humans though. Further they get better all the time, and we see their progress. People pointing an LLM at some project and it magically making it work on some other platform etc... It's clear it's happening.
It will probably take a lot longer to die than I expect, though, too many legacy third party libraries are written in C++ and most people don't really like the syntax for the logical alternative (Rust).
I'm not ashamed to admit that I occasionally ask my LLM to find and fix all the bugs in a file. And not ashamed to admit that it often does. Most usually algorithmic problems, not memory problems, fwiw, because I use modern C++.
Plus, there are a lot of pros to C++ which aren't so easily waved aside. The breadth and duration of support makes it almost impossible to kill quickly, imo. If a magical LLM bullet shot C++ dead tomorrow, many established orgs and projects would keep it rolling. It also sits in a funny spot where you can get pretty good performance from not-great code. And it is incredibly fun to write! Reading it is... another matter ;).
Edit: I'm sure dumping piles of money into abliterated models can get you quite far. ROI and correctness are the parts I'm still sus about, however I hope to be proved wrong.
It very obviously does not. Stating what you want to be true as "facts" does not make it so but rather discredits the one making the assertion.
auto fib = [](this auto self, int n) -> int { return n < 2 ? n : self(n-1) + self(n-2); };
auto v = std::views::iota(0, 20) | std::views::transform(fib) | std::ranges::to<std::vector>();
for (auto&& [i, x] : v | std::views::enumerate) std::println("{} -> {}", i, x);
auto add = []<typename... Ts>(Ts&&... ts) { return (... + ts); };
static_assert(add(1, 2, 3) == 6 && requires { std::format("{}", 'x'); });The implementations are the language.
One learns this when their standards-compliant code fails to compile.
You will find, though, that every major compiler considers those features that don't work either. Don't work because of bugs or they're not implemented yet. But either way, it's something they plan to implement.
Your objection reads like 1995. Of course, the C++ standard didn't exist until 1998, which is my point. Compilers have generally considered it a bug to not have all features implemented - the exception that proves the rule is export, which was removed from C++11 because all compilers agreed they weren't going to implement it.
Or you do the BDFL model like Python, D, Zig, Odin, etc.
I think it works better. A language that reflects the vision of an individual deeply thinking about the interactions of language features, and being able to say "no" to anyone/everyone has many benefits.
Committees make me think of the Web, C++, Swift, etc., and they have a lot of "odd" decisions that have had a heavy cost. Swift has https://www.swift.org/swift-evolution/, but a lot of ideas with minority support were pushed through by Apple (for SwiftUI mainly), against overwhelming community feedback.
And the Web has had a lot of "duds" when you think of WebGPU, WASM (not saying it's bad, but people clearly want to stop writing TS/JS and they haven't made that a reality yet, at least in a way I'd call "successful"), etc.. Oh and DRM in the browser sucks. I forget the details but I was goofing around with FreeBSD Desktop and couldn't get various things to work due to some browser DRM bullshit. https://en.wikipedia.org/wiki/Widevine if curious.
Well I mean you're just wrong, you can look at any of those languages (or https://en.wikipedia.org/wiki/Benevolent_dictator_for_life). Python is over 30 years old and it's going fine. You don't see people ragging on Guido everywhere. But everyone pokes fun at the C++ committee and the wacky shit they cook up.
Design by committee just sucks. Coordinating and working with people is expensive, and a drag. If you have to, you have to, but for many projects you don't and shouldn't. An individual's design is always going to be more cohesive, and we can see it clearly looking at PL history IMO.
Guido stepped down from that role 8 years ago. It now has a 5-member steering council along with the core developers as the primary maintainers and decision makers.
That said, I'd agree with GP that design by committee does suck and I'm not impressed with several of the decisions the SC has made since.
Python doesn't have a BDFL[1] and the others are toys.
1: Guido stepped down in 2018 and stopped involvement entirely in 2020 after an extended conflict with the community at large over PEP 572
Thanks, I relinquish that point and accept it as a success story for BDFL succession instead.
How many Python, D, Zig, Odin, etc. implementations are there?
And considering the number of users, only Python has an even measurable fraction of the number of C++ users.
And the article also mentions that divergence is a real possibility in situations like this.
The most self-descriptive comment I've seen in a long time.