443 comments

[ 12.3 ms ] story [ 651 ms ] thread
Writing Chromium grade C++ seems like a hard job with all these extrinsic rules and regulations to make it work
You'll see these kind of rules at every place that cares about their C++ codebase. It's just not a language like Java, C# or JS where you can throw stuff at the wall and it'll probably work out.
> Java, C#

Maybe.

> JS

Hell no. "Frameworks" like React or Vue exist solely for the purpose of retaining developer sanity in the face of unregulated JS code.

Anyone serious with TypeScript/JavaScript has many Eslint rules to keep the codebase sain.
Is there a case for or against incrementally adopting Rust ?
Is there any common module that can be shared amongst browser engines? I can feel though it sounds hard to extract these common stuff into .. "browser engine common core"? (e.g. https://github.com/SerenityOS/serenity/tree/master/Userland/...). That would be nice for next gen browser invention.
Which of these things is Rust immune to? How stable is it over a 10, 15, 20 year life? How old can the code be that Rust compiler still compiles and links successfully and bug-free?
Don't worry your little brain about these silly things. Programming is hard, let's go (language) shopping instead!
Rust does well on this front. There's a new release every 6 weeks, and majority of users jump on it straight away (to complete shock of everyone not used to it).

Rust has editions which keep old code working without any changes, even if you mix it with new code, even if you do it with macros. It has rustfix that automatically migrates majority of the old code.

Rust has a standard project layout, standard test runner, and a central code repository, which enables testing language releases against nearly all publicly available code (see "crater run").

The language itself is stricter, with the safe subset being free of UB, so there's less stuff to break to begin with. You can't suddenly use a moved-from object.

There's even "cap lints" feature that disables `-Werror` equivalent for dependencies, so that new lints don't break your builds.

Editions only work for easy stuff, superficial grammar changes, and in a context where all dependencies are compiled from scratch with exactly the same compiler, aware of all editions.

How many editions are rustc, rust-gcc, cranelift and whatever might come, being still kept up to date in 40 years (C++ age)?

What about all the language changes that actually require semantic changes, how are epochs supposed to deal with inter-editions calls where the epochs have incompatible ABI expectations between caller and callee, regarded expected compiler behavior?

Rust is so trivially updated that claiming using the latest compiler is problematic almost sounds like a bad faith argument. Everyone in the Rust ecosystem updates withing a few days, because of how trivial it is.

Also if you go to GitHub and see a package that's not been updated in 5years, do you think enthusiastically "oh yeah, I'm gonna use this"? Because IMO, if it's not been updated in years, it's probably abandonware.

And another point is that I'm happy for them to deprecate/remove 40-year old (or less even) design decisions that have become outdated. Thinking all design decisions are immune to time decay like is foolish.

The ABI discussion is a bit worn out by now. Everyone will just tell you to use use C ABI if you need compatibility (until something better comes along?), And there's is a plethora of methods for all sorts of languages to help bridge language gaps.

Plenty of enterprise software has not been updated in 5 years, and keeps delivering business value.

Suggesting to stick with the OS C ABI (there isn't such thing as C ABI), assuming it was written in V, for compatibility between Rust libraries is kind of ironic.

It is a matter to which kind of industry domains Rust folks want to cater to.

> Everyone in the Rust ecosystem updates withing a few days, because of how trivial it is.

I love Rust, but this attitude is terrible. Luckily it's also not accurate – it's perfectly fine to stay on your distro's compiler.

This how i got traumatized by rust: i had a simple task, 1 day long. Write a routine, use a standard output format, theb parse the results. It was to be presented in front of 20 peers the next day. Decided to try the last part in rust...

Did a few tutorials, not all of them "worked," but i scratched my head and moved on. Started writing the parser. No examples worked. Couldn't put together any reference code. Even copying straight from web pages!

Switched to python. Done in 45 minutes. Told the whole story to the group. Had a laugh. Manager quipped, "that was when you went from leading edge... to bleeding edge." :)

Later i learned rust was still making breaking changes, leaving a wake of dysfunctional tutorials. Canttrustthatlanguage.jpg. Thought i'd go back someday when they get it straightened out.

You were set up for failure: "do something for tomorrow in a new complex technology, and present in front of multiple peers".

Your solution - to use a different technology designed with different requirements, that you were already familiar with, and that you knew could do the job, was the right solution in your situation.

Your conclusion could be revised and improved.

> You were set up for failure: "do something for tomorrow in a new complex technology, and present in front of multiple peers".

Perhaps you missed this part

> > Decided to try the last part in rust...

I don't know when you tried it, but I had a similar need (build a query parser for a tiny SQL subset in a day) a few weeks back, and it took me may be a couple of hours. Actually, picking the library was the more painful part, and this is a problem with Rust IMO, because for most non-trivial new things you (at least I) typically need to dig through crates.io, research, experiment, etc. Anyway, FYI I ended up using PEST (pest.rs) if you still have a need.
Embarrasing that you are so unaware that you present this story as anything but a failure on your own part. Yeah, so you chose a language that you didn’t know for a task that you needed done in 24 hours? Whew, that’s an unforced-error story for the campfire.
I dont feel embarrassed. I'm glad I took the chance. I take chances like that regularly. I don't consider it a failure at all. My team and manager considered me a badass and leader for taking the leap, and we all had a good laugh, too.

"If you want to succeed, you must double your number of failures."

I have had so many suprises and successes by taking chances like that. And even when I don't come out ahead in the short term, I at least get exposure to and practice on new things.

Oh, a badass too? Well carry on then, coding stud.
> Rust does well on this front.

Rust needs to exist for at least 20 years before you can say it "does well" on 20yo projects.

> majority of users jump on it straight away

This is a sign of a language with a tiny mostly-hobbyist user-base.

> This is a sign of a language with a tiny mostly-hobbyist user-base.

Or that the tooling is so trivially easy to update that people don't need to think about it. Sometimes if I want to upgrade GCC I need to upgrade the whole os, or have multiple versions installed and be careful where they're installed lest I incur Ubuntu's wrath

Not true. I picked up a git repo 18 months old. I tried to set up my rust dev stack to match the version in cargo. Then hit a lot of issues with dependencies due to abandoned repos used for the crates. I gave up and moved on.
A random person on github abandoning their pet project is not the kind of language stability we're talking about here. It's not the kind of complaint you'd write to the ISO committee.

You may have run into a project using nightly Rust, which is an explicitly unstable version for testing of experimental not-yet-finished features. Using it requires users to intentionally opt out of having language stability. C++ also has GNU and Clang extensions and experimental implementations of not-yet-standardized features.

However, the normal workflow is using a stable version of the compiler. It is backwards compatible with the 1.0 release from 2015, except handful of edge cases that you won't run into an average project.

Users are encouraged to use crates.io and keep Cargo.lock which guarantees they get the same dependencies that worked last time.

How on earth can you predict that rust will be stable in 20 years?
> Rust has a standard project layout, standard test runner, and a central code repository, which enables testing language releases against nearly all publicly available code (see "crater run").

I'm not sure why this is treated as a selling point. It helps projects up to a point, but the minute you want to do something that doesn't fit the mold, it makes things a lot harder. All of these products are orthogonal to a language, and yet they are very tightly bundled with the idea of using the Rust language. It certainly makes "business logic" systems and web backends easier to implement, but I'm not sure many people were building those in C++ to begin with (except in legacy systems, where you are stuck with what you have).

Also, I'm not sure how a language that is less than 10 years old (in its released, 1.x versions) has any claim to having no problems with evolution over the next 10 years.

> the minute you want to do something that doesn't fit the mold, it makes things a lot harder

This is not my experience at all; it's been extremely helpful for doing things outside the mold:

In a work codebase that is millions of lines of first-party Rust code with thousands of third-party Rust dependencies from crates.io, we're building with Buck not Cargo because that is what the rest of the monorepo uses for C++ and other languages. It's fantastic for this that all the third-party projects describe their dependency graph in the same standard Cargo manifest format and follow a standard project layout, even though our builds do not use Cargo, because we can programmatically translate them to Buck targets.

Ok here come the rust apologists.

Well when rust makes changes every 6 weeks we all hold hands and update all of our code.

Betting on carbon would allow transparent FFI like Kotlin, typescript or graalvm achieve, which is economically disruptive. Rust seems more like a plan B
Rust is too hard to learn.

I like it, but it only aims at replacing ada, not c++.

I asked around and there is currently no good way to make modern native ui apps with rust.

It would be okay if the syntax of rust looked a bit more like C, but it doesn't, it can be a bit difficult to read.

Rust's learning story has some interesting dichotomies in it. For sure, no one disputes that learning Rust is harder than say Python or Java. But how does it compare to C or C++? My take (which I have no proof for, to be clear) is that learning Rust is much easier than either of those if you're truly starting from zero. Some scattered thoughts about this:

- Experienced C and C++ devs have to unlearn certain patterns when they start Rust. C++ experience can be a huge help, but if you insist on using the patterns you're used to in Rust, you often have a terrible time and feel like the compiler can't handle useful programs.

- Particularly with C, what we usually mean when we say "learn" has gotten kind of out of date. If someone with a few years of programming experience says they "know" Python, we might assume they can make an HTTP request and parse some JSON with a couple minutes of googling the relevant API docs. But of course in C, those tasks are much more challenging, and we often allow that someone has "learned" C even if they can't do those things without great difficulty. Part of C's reputation for being (comparatively) easy to learn is that we don't expect programmers to be able to do the same variety of tasks with it.

- A lot depends on what standard of correctness and security we want to apply. For example, writing multithreaded code in Rust has an extra steep learning curve, but the resulting code is data-race-free once it compiles. Writing big multithreaded programs without data races takes many years of experience in C and C++, and it might be genuinely impossible for sufficiently large projects. So depending on what we understand "learning to write multithreaded programs" to mean, we could say that Rust is much harder but also that Rust is much easier.

As expected modules are relegated to some later time ("will be its own experiment")
Thanks clang lagging behind ISO C++, you can use them today on Visual Studio 2022.
Can't blame them. Modules are Microsoft sabotaging the standard so they can be the only conforming implementation, just like with the Office .doc format.

Modules are unimplementable shite. They would be great if this was the first iteration of the language, but they're a nightmare to fit into the existing ecosystem. If they get support in open source compilers, we can look forward to at least one or two decades of a mixed modules/headers mess in open source projects.

Yet, somehow the GCC folks manage to keep improving their modules support, slow and steady.

If they were unimplementable, there wouldn't exist already two major C++ compilers supporting them to some degree.

It is only clang with their module maps pseudo concept that keeps lagging, that and plenty of other C++20 features.

Implementable by compilers, sure. Implementable by the ecosystem at large, yes, over the course of several decades.

Edit: Also, module support better be 100% binary compatible between GCC and Clang, or it's worse than complete garbage.

As if you could expect any kind of ABI compatibility between GCC and clang binary libraries today, and apparently it doesn't make them a complete garbage, go figure.
umm, you can

edit: to elaborate both g++ and clang++ implement the Itanium C++ ABI[1]. You might get binary incompatibility by mixing standard libraries, so just don't do that.

[1] https://itanium-cxx-abi.github.io/cxx-abi/abi.html

Which is exactly what happens when using shared objects.

Additionally the C++ ABI doesn't tell anything about how each binary library was compiled regarding compiler and linker switches that affect runtime behaviour.

> Which is exactly what happens when using shared objects.

Right. This has nothing to do to gcc, clang and even C++ though. I would be surprised if you could freely link C shared objects linked to different libc implementations.

On most Linux distros both gcc and clang link to libstdc++, so everything works out of the box. I imagine this is not the case for MacOS xcode and gcc from homebrew.

Standard libraries is what makes most of C++.

(Like, literally, going by the size of the language spec.)

And the same applies to C and libc.
What are some ways in which they are bad/broken?
modules were my #1 wanted feature in C++, but they are very disappointing to me:

- they allow `.` in their names to signify hierarchy, but have no built-in notion of hierarchy or submodule. This means possible error messages will have to be suboptimal

- they are orthogonal to namespaces, meaning if you have a module `foo.bar` any identifier it will export will be in the global namespace unless you put it in the `foo::bar` namespace in the `foo.bar` module file. As a user this means that I can import a `foo.bar` module and expect symbols to be imported in any namespace.

- btw, what happens if different modules import the same name? I'll let you guess:-D. Modules were the chance to eliminate this kind of issues by eliminating the notion of shared namespaces, but oh well...

- modules have interface units, implementation units, partitions, interface partition and implementation partition. There must be exactly one non partition interface unit, called the primary module interface. Instead of, you know, just having modules and submodules files like all modern module systems in existence.

- Modules add new and fun ways to get ill-formed, no diagnostic required programs. Such as not re-exporting all interface partitions.

I really hoped that modules were the chance of introducing a simpler compilation model for C++. I'm not pleased with the result.

Some references:

[0]: https://vector-of-bool.github.io/2019/01/27/modules-doa.html

[1]: https://vector-of-bool.github.io/2019/03/10/modules-1.html

Yikes, I was so excited for modules back when I was still writing C++17 and a bit of early 20. Sad they have so many problems with them and/or are just plain not implemented still. Honestly I'm happy I'ved moved on from the language
Popular languages that went for strict 1:1 mapping between modules and filesystem, like Java and Python, often end up moving away from it, or at least adding various hacks (e.g. the way you can split submodules across packages in Python).
I don't know about Java, but in Python, 99% of the modules I create respect the 1:1 mapping between modules and filesystem.

Same in Rust, the overwhelming majority of modules I create is in the standard filesystem <-> modules mapping. For generated code, I use the special syntax that allows not respecting this mapping, but that's once in a blue moon.

IMO, C++ should have taken the same steps: providing sane, correct and easy defaults, while allowing the flexibility to override them when necessary (with special annotations).

I'm disappointed that a modern C++ feature was designed in the long tradition of having bad defaults instead.

That's just usual legacy driven design, like timespec. It's actually a very good move that module separator is '.' instead of something ugly like '::' or even worse ':::'. Not even new languages can easily let go of angle braces and double colons.
Clang had modules ages ago. Still does I think, but not the same thing as was standardised.
What it has are pseudo modules implementated via configuration files called module maps, completely unrelated to how C++20 modules work.
It's easier to say goodbye C++!
A full rewrite seems extremely expensive and long. I’m afraid C++ isn’t going anywhere in our lifetime.
C++ can stay, but people can make a decision not to use it for anything and not to take any jobs involving C++ (or other systems/bare-metal-ish languages).
C is 10 years younger than COBOL, C++ is 20 years younger than COBOL, COBOL is still around.
It's more easy to just don't adopt the new standard.
I've written a fair amount of C++ in my day, and this seems totally unacceptable to me.

Also my opinion seems unpopular on this topic, so I suspect some group of people that really want to get behind C++ don't like that kind of talk.

Whatever though. Rust and other newer languages are kind of giving the spaces C++ works well in a run for its money and in other ways just completely blowing it out of the water in my opinion.

It might be time to look at D again too.

I stopped using C++ after lambdas were introduced (10 years ago or so?)

Is there anything I can read to get up to date quickly?

Bjarne's "Tour of C++", 3rd edition.
Is one release really causing so many problems? I thought C++ treated backwards compatibility as a holy thing.
It doesn't cause problems as in old things don't work as in it takes time to get advantage of newly introduced ones and replace anything considered deprecated (edit: and rename variables clashing with new keywords).
From what I can gather, a majority of the problems reported there are new compiler warnings being triggered. Especially if you enable all compiler warnings to catch potential defects, any upgrade to a new compiler version will typically introduce a ton of new warnings that you have to work around.

Edit: There are also quite a few deprecation warnings, which are also not errors per se, but nevertheless you'd want to avoid using deprecated stuff of course.

It generally does although they have made technically breaking changes in the past (e.g. `auto`). This is the first release I've used where they broke something in actual code I use because of the comparison operator changes.

However it was easy to fix and actually it was because whoever wrote it did it wrong. Google also found a breakage that was hiding a bug.

So maybe their new stance is "only break incorrect code"? I dunno. I feel like a small amount of breakage is reasonable anyway. Breaking change absolutionists are generally just being dogmatic, and haven't really thought through what zero breaking changes really implies.

E.g. in languages with introspection it technically means you can't change anything.

They also broke the code of the few users that made use of exception specifications.
All languages break backwards compatibility, just not to the extent of Python 3.

Heck even Go folks are discovering their stable guarantee isn't as stable as they thought.

Re Go, sure, but most (all?) of those have to do with security updates that may impact features. The discussion is how to make them less painful or opt-in/out in the standard library. None of this has to do with the language itself.
A break is a break, regardless of the cause.
No.

There is a practical difference between a language break and a package break (even in the standard library).

There is a practical different difference between a break due to an explicit security issue and a break due to a design change.

When you have large codebases, everything causes problems. When I worked with Unreal Engine, minor MSVC releases (same C++ standard, same everything, just going from lets say 17.0 to 17.1) would sometimes make the code not compile anymore. Either because of new MSVC bugs, or because of incorrect code that was previously allowed.
Damn. May as well have used a language that isn’t released yet like Zig. That’s a ton of problems.
Um no. When I upgraded it was a few hours of work at most, for Google since its code base is extremely large maybe a few days to weeks.
Probably no big issue for a small to medium codebase. But I doubt the "just a few days" estimate for something as big as a web-browser. If I read correctly they even found use-after-move bugs during the process.
The use after move existed prior, just wasn't moving even though they had written move.

It is also written in a way that a linter should be able to detect if you disallow use after move.

https://source.chromium.org/chromium/chromium/src/+/main:com...

It's surprisingly hard to detect these sorts of cases reliably; we have a request out to the clang-tidy folks to add some improved detection of use-after-move there.
Took a couple of months actually, but it's mostly complete now and we're starting to throw the switches.
Very useful presentation. Full of details, but easy to consume.

On voices against stripped down C++ (via code style): I find it working great in practice. Makes the codebase manageable, and keeps people away from using unnecessary complex language features (imagine Java code heavy with streams or reflection, or Python code that resolves most of dependencies at runtime, javascript full of eval(), etc.). Switching to a new language is half-baked plan.

Ad-hoc black or white bans are very retrograde and costly for tje most part and usually stem from purity thinking. Case in point reflection in Java is a godsend. I use it very rarely because it'd uses only comes for very specific needs but when I use it, the alternative either doed not exist or usually would be much more uglier. As for streams well it's just regular functors (map, filter) they are used in every language and are very useful. Now I agree about two things: 1) the stream api is a bit (not that much though) verbose, which significantly contrast with Kotlin. Although e.g .toList() helps 2) yes develpers especially junior ones are eager to abuse functors in an unreadable mess. When there is complexity using loops is usually more readable. Streams however are very fit for regular ETL that represents ~70% of code for most simple apps. The pinnacle of complexity would be e.g. Reactive streams such as rxjava.

I agree python dependencies are a worldwide shame and eval() is very niche (but again should not be universality banned assuming good developers, maybe though one could conditionally ban it aka it would trigger a lint during code review that would need explicit validation.

As for the topic at hands, google style bans are insane e.g. No Exceptions lol

I agree with this comment, and cannot understand why is downvoted. I would like the one that downvoted comments on why. My thinking, more or less in line with the comment is: instead of investing energy, time and resources in writing laws of what is allowed and what no (often without rationale). Use that time, effort and energy in educating developers, so that, if those prohibitions are really sensible, they will anyway refrain from doing that. You get the benefit of not having to change the bans. By doing regular code reviews, you can detect ill formed code, and discuss with the developers. Maybe some developer has something to teach to the "big experts" who write those documents?
What's easier: writing set of rules to drop language features or educating 10k engineers? It's difficult alone to have those engineers follow the style guide (even with help from linters etc).

Average engineer, in any company, doesn't care about the language they use. Just wants to get stuff done. And that's how it should be.

My experience says, if you want to do one project. By all means, it's easier to have a standard where you drop features. -- But beware: you need either good engineers or you have to educate them, anyway.

If you have constant new projects, each one with very different requirements and needs, it will not be so easy to make a standard "one size fits all".

Also, again, you will not be doing "just one" standard, I would not subestimate the effort of such rulesets. You will have to the modify and modify it constantly (I know it from the company I'm, there are whole teams working on that, doing meetings constantly with stakeholders, and replying to exception requirements). In the long run, I would prefer to have good engineers, that do not need to be lead in each little step. --and having to check if they adhere to the rules!

I like the phrase: "If you think education is expensive, try with ignorance" Or: - I'm afraid to waste money educating our employees, and then they leave us - What if we do not educate them, and they stay?!

"no exceptions" is one of the best parts of Google style guide, IMO. Note that, banning of exceptions introduced returning status (error codes done right). It makes it easier to follow the code and makes the code more readable (but, you need a few macros, unfortunately).
OK if you like 10-15% performance loss off the top.

And incompatibility with the rest of the world.

In a language like C++ returning status codes means that callers can and will ignore it, even when they shouldn't.
Since C++17, using [[nodiscard]] can help with that.
Indeed, Google's implementation of Status/StatusOr requires explicit handling of those objects, they cannot be discarded automatically.
> imagine Java code heavy with streams or reflection, or Python code that resolves most of dependencies at runtime, javascript full of eval(), etc.

all these things are sometimes the best solution to a given problem

> imagine Java code heavy with streams or reflection

I can see Reflection as problematic, but what's wrong with streams?

It is so ironic, that now that Apple and Google decided to focus on their own language stacks, the C and C++ compiler vendors that profit from clang's license aren't that keen in making the upstream work for catching up with ISO C++.

Thus making the once famous clang having an honorable third place in ISO C++ compliancy.

Seeing this from a Google team makes it even more ironic.

gcc is better than clang on every metric. Sorry, it's the facts. ¯\_(ツ)_/¯
I've always preferred clang for its better lints, and friendlier error messages (sans some decrepit parts around templates that are equally horrendous everywhere)...

And theoretically clang has better ASM output in some cases I say theoretically, because it's been shown that GCC's "worse" ASM performs better; I'm not really an architecture aficionad, so I can't comment as to why that is.

Also, it's been a few years now since I did C/C++. So, maybe these are no longer the case.

Anyway, I've kinda pointed out what I like about clang over gcc, but I'd be curious what you prefer in gcc.

I wonder how up to date is the old belief of better error messages. The was some great redhat blogs about structured error messages in newer GCC.
Ok, that's kinda true I suppose; I remember that GCC's error messages have indeed gotten better after a major version release.

Also, when you say structured, do you mean errors over LSP, or do you mean more structure in the formatting when reporting in CLI?

GCC returns pretty nice, colored and structured error messages in CLI for quite some time.
I meant in cli a bit like rust arrows logs
GCC has gotten better indeed, but it's still in a different league than Clang. I still get into situations where I can't make heads-or-tails of what GCC is saying to me, which can usually be easily solved by switching to Clang.
I don't do C++ anymore but I will forever remember the Vtable hell "messages" when doing OOP and doing a slight unintuitive mistake about destructor or constructor. Is this still a thing in clang?
One problem here is that GCC emits certain warnings as part of the optimizer, which results in many false positives that are essentially impossible for the lay programmer to understand. For example, jump threading might duplicate some code path and propagate constants in it, and then warn about an uninitialized variable / out of range access / etc in that code-path, even though it does not exist in that form in the original program.
Funny enough, this is exactly the kind of situation where other people cry "why did the compiler not warn me about basing optimizations on UB".
clang doesn't output asm
I'm not sure what you mean? You can use `-S`
What is the status of ubsan, msan, tsan and others support for GCC though? Last time I checked they were a bit behind. I agree GCC make clang obscolete regarding C++ support and even performance. I don't know a comprehensive alternative to clang linter but I'm sure there are a few.

Given that llvm receive more human resources than gcc by far, I once expected it to outperform gcc generally (e.g support for polyhedral optimizations, BOLT, etc) Unfortunately weirdly it seems llvm performance is mostly stagnant. I personally suspect we are reaching increasingly diminishing returns with AOT and that the performance graal would be a hybrid that also does JIT at runtime (beyond PGO therefore) and more interpretable than BOLT

At work GCC is a lot slower than clang, the project uses a bit much template magic but still, clang is faster.

Where does this GCC is faster thing come from? I personally havent experienced it

Old hype thing created by gcc fanboys :)

They always claims so.

I mean faster runtime performance, I have no clue about compilation time. Well I'm basing this on the countless benchmarks I've seen, e.g. on phoronix over the decade. Also you have to understand that Clang -O2 is (was) "unfair" as GCC did not enable autovectorization until -O3. This has (is being?) changed.
Whoa, really? Since when??

After a lifetime of using GCC, I (like many others) moved to clang a few years ago, out of frustration with the slow development of GCC, a desire to use new C++ features, and stayed because of the superior error messages and, in my use cases anyway, superior code generation.

In addition, I gather it's a much cleaner and easier to maintain code base. As a result, we get to have cool things like emscripten, llvmpipe, all sorts of static analysis tools that would be more challenging to build in the GCC universe, and much more.

Honestly, I thought GCC was slowing down development wise.

Is GCC worth trying again? Can you name a few "cool new things" I can do with GCC that I can't with clang? There's plenty of the opposite...

LLVM always delivers the cooler stuff I'll admit but GCC is ahead as an optimizing compiler, general lang support and generating cryptic messages.
Interesting, I've generally found that clang is better at optimising, especially when it comes to auto-vectorisation.
Agreed, autovec is in my experience where clang really shines.
Gcc pretty consistently delivers slightly faster code.
Not in my experience! Though it was a few years ago, I'll have to re-evaluate. In the past, when doing numeric/scientific programming -- lots of dataflow, it was much easier to "coax" clang to generate the right (performant) assembly than GCC. I guess my use is less typical -- the internet agrees with you. :)

Hurrah for competition!

(of course, neither GCC or clang could _ever_ beat ICC at some of my tests.. grumble)

"(of course, neither GCC or clang could _ever_ beat ICC at some of my tests.. grumble)"

They never tried, honestly - ICC had plenty of defaults that were targeted at performance above correctness.

That's fine - but it wasn't what either clang or gcc was going to go for. Even when trying to compare apples to apples, folks often compared them by trying to get GCC/Clang to emulate the correctness level of ICC (IE give GCC/Clang more freedom), rather than the other way around :)

Which, again, similarly understandable, but also a thing that you'd have to spend a while on in GCC/Clang to get to a reasonable place)

Small-number-of-target compilers like ICC are also fundamentally easier. Lots of techniques (IE optimal register allocation[1], etc) that add up to performance gains are a lot more tractable to really good applied engineering when they don't have to be so general.

Similarly small-target-market compilers are also easier. Over time, high performance was literally ICC's only remaining market. So they can spend their days working on that. GCC/Clang had to care about a lot more.

[1] This happens to now be a bad example because advances finally made this particular thing tractable. But it took years more, and feel free to replace it with something like "optimal integrated register allocation and scheduling" or whatever is still intractable to generalize.

You sound like you know what you're talking about.

> They never tried, honestly - ICC had plenty of defaults that were targeted at performance above correctness.

Is it possible to get ICC level performance out of open source tools? Much of my CPU bound work relates to array signal processing, which, if you can code it right :) lends itself heavily to SIMD branchless pipelines. Plus some scatter gathers on a group of other cores to calculate a sparse crosscorrelation tensor.

I would love to be able to get same or better performing code out of clang or gcc, even it it takes 2-4x more work than with ICC...

This is often true for SPEC, but not true for real world code.

Or at least, we extensively tested this at Google, before, during, and after the move to LLVM.

On many thousands of libraries, binaries, etc, made up of hundreds of millions of lines of C++.

While there were wins and losses, on average, LLVM was a consistent net positive.

That was true despite having spent 5+ years of having a large team dedicated to doing nothing but finding places to improve performance of GCC compiled code (and contributing back patches), and doing so very successfully.

That said, as time approaches infinity, the compilers are going to generate the best code for the things that someone took the time to analyze and make the compiler better at.

There is, in the end, no magic that makes GCC better than LLVM or vice versa. The vast majority of it is tuning and improving things little by little for whatever targets someone is trying to improve.

I can only go by the results I measure. There certainly are cases where Clang does better than Gcc, but the difference is anyway rarely more than a couple of percent. Occasionally one finds a factor of two one way or the other, e.g. where one compiler has noticed it can use CMOV without penalty.
GCC is quite up to date. Also, the code generation used to be better. I do not know right now. But I think it still might have a slight advantage.
C++20 support comes to mind, the plethora of supported hardware as well.
> C++20 support?

Not that I think it matters but this is, of everything, the strongest argument to me. For shame, clang, for shame! I switched because it had better modern c++ support. Guess the winds are changing.

I think the main differentiators for clang were more readable error messages than the mess GCC had (and still has), and faster compilation (here it lost a bit of its advantage over time). I haven't heard of anyone praising it over GCC based on standard compliance.

That said, when I explore weird edge cases in how differently Clang and GCC parse source code, and how differently they optimise it, in my experience Clang is still the more logical one, whenever there is a disparity, whereas the way GCC parses code and what code it produces is sometimes completely baffling (and that's not just optimisation stuff - without any optimisations enabled I encountered miscompilations a lot more with GCC than Clang).

I also consider C++ a language for mostly old projects. I start new ones in other languages and don't consider repeated rewrites every two standards, because the "modern c++" crowd found a new way to initialise variables, to be a good investment of my time.

> I start new ones in other languages and don't consider repeated rewrites every two standards, because the "modern c++" crowd found a new way to initialise variables, to be a good investment of my time.

Same here, but I update my new code as I go if there is a better/safer way to do it that does not impact in any bad way my codebase.

> I haven't heard of anyone praising it over GCC based on standard compliance.

Back in the day (C++0x era), it was near bleeding edge. Clang had all the proposed 0x features ready to go before C++11 was released.

It took a while for GCC to get all the C++11 features after the release.

> Clang had all the proposed 0x features ready to go before C++11 was released.

Except garbage collection, of course.

You don’t have to rewrite anything. C++ keeps backward compatibility. You don’t have to make it sound like it’s the case, and you don’t have to shame the "modern c++ crowd" over that false claim. That’s just uncalled for, unconstructive and partisan discourse. I expect better from HN.
Did you see the original post? Lots of code changes were required for Chrome. That’s not backwards compatible.
It’s 99.9% backward compatible. The comment makes it sound like it’s a huge pain every 2 years, as if it was the "python 2 to 3" level of pain.

I went through a lot of these new versions, starting with c++11, and can’t remember one time I had to change anything to my code based, appart from silencing deprecation warnings for unicode stuff.

Now I get that chromium-like code bases are huge and that the 0.1% of backward incompatibility is still a pain. Now, with that level of hugeness of code bases, maintaining it when the language evolves is going to be a pain. C++ has been very careful with backward compat, breaking very few things, compared to other languages. So these criticisms really are uncalled for to me. Sounds like good ol c++ bashing trying to sound wise.

I felt the comment wasn’t fair and wasn’t an honest criticism. Sorry for my angry comment. We live in rough times.

I've done several Python 2->3 transitions, and the amount of effort looks to be in the same order of magnitude described in the post.
This has no relation to Clang's permissive license. The problem here is generally that nobody has implemented the necessary support, not that they have implemented support in their private forks but did not bother to upstream it. A GPL-style license doesn't magically force contributors to start working on a particular feature.

Google at least is one of the largest contributors to the LLVM project -- it's just that their contributions don't tend to focus on Clang frontend work.

Anymore. Google used to contribute quite a lot to libc++ especially, but have found that the language is going in a direction that doesn’t meet its needs.

Not sure why Google would continue to contribute under those circumstances.

The thing vendors care about more than anything is making upstream work supported.

Google isn't even complaining about things, this is a very dry technical document about their experience, for an amount of work they certainly expected and were possibly even pleasantly surprised about.

These sorts of things are routine in massive code bases

When a new C++ feature is proposed does the proposer have to provide a reference implementation? How can features be missing from the major compilers?
Yes to some extent, depends on how one wants to defend their paper at ISO, during the several voting sessions.

Even when they do, it is in prototype done by the paper's author, not necessarily something that you can merge right away into upstream.

Visual C++ is already there in C++20 and increasingly improving C++23 support as well.

I wonder if a feature has been accepted that turned out to not be tractably implementable!

I work on Ruby compilers and people often suggest features that they don’t realise would be catastrophic for performance if implemented, or are sometimes literally impossible to implement.

Except Visual C++ already has those ideas implemented, so....

Even exported templates, as hard as they were, the EDG folks actually implemented them, only others decided not to follow upon.

The current state with clang is a mix of MIT like license, and those that profit from it not caring about upstream, even GCC is doing better.

I'm pretty sure this rethoric is fallacious, most VMs/languages are not GPL and have MIT-like licenses and yet do not have the issue. It's just that clang lack human resources. Compagnies are not really secretly maintaining their own fork of clang with full support for modern c++. It's not in their economic interest to have to fo all tjis engineering. Instead of malice it'd just plain mediocrity. Yes there are trillion dollars companies that would benefit from better c++ but either they use GCC, either they fail to understand that clang needs funding by pure and quite universal mediocrity. Also the thing is, most languages do not afford to have multiple serious implementations because it is economically absurd, it divide progress by 2 and duplicate the bug surface by 2. GCC at least for the foreseeable future is the de facto C++ implementation.
The facto only for FOSS systems I guess.
Does memory_order_consume count?

> people often suggest features that they don’t realise would be catastrophic for performance if implemented, or are sometimes literally impossible to implement.

Oh, that's every language.

Template export was implemented by a single compiler vendor, and that served as a cautionary tale for all other vendors against trying the same.
EDG, right after having implemented export, successfully proposed to remove it from the standard as it was hard to implement and mostly useless!
No. Ideally there would be an implementation before it ships in a standard but that's not considered necessary.
Same reason caniuse.com exists for browsers. This just comes from the territory of a language/runtime/platform that doesn't have a monopoly implementation.
So, it seems most problems are C++ making things extra bureaucratic and annoying. Cool

The "pre/post increment of volatiles is deprecated" sounds like a huge pain. I can't imagine a worse waste of developer time than fixing such a minor thing (and to be fair C allowing both a++ / ++a should never have existed)

well to begin with if you're using volatile, you're probbaly doing something wrong already.

The valid use cases for it are ridiculously few.

But if you're using volatile correctly, then there's nothing wrong with ++*v.

I was wondering about that slide that says the meaning is unclear, I don't see what's unclear about it. What particular assembly instructions it translates into is irrelevant. It's not like ++ is guaranteed atomic or anything.

it's not guaranteed atomic, and if you ARE using volatiles, "maybe" doesn't really cut it, so now you need to do `++*v` the proper way (ex. `lock cmpxchg` / `__atomic_compare_exchange`), or explicitly write it out the long way (`*v = *v + 1`) and risk the small chance of the value changing under you between read/write
using volatile correctly usually means you're manipulating write-combining memory.

Write-combining memory has weak memory ordering where all writes are delayed so anything doing both reads and writes is most likely going to bite you in the neck.

If you're accessing any kind of I/O register or special memory, obviously you need to know the rules of engagement for the particular kind. If you don't, then you're just going to be making the same mistake in a less obvious guise. Like writing *v=*v+1 instead of *v++.
That's been undeprecated in C++23. It's mildly embarrassing, but it shows the process works.
Are all of C++20’s volatile deprecations un-deprecated in C++23? This doc from 2020 lists all:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p21...

But this proposal from 2021 suggests only undeprecating bitwise compound operations:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p23...

Sorry, you're right, my bad. People are actually writing compound arithmetic assignment ops on volatiles, huh.
You got my hopes up about un-deprecation :) but I was glad to at least learn about the proposals
Wait, so the words "concepts" and "requires" were newly made keywords, and this breaks code, but the words "yield" and "await" were determined too important and too common to standards members that they needed to be renamed to the horrifically ugly "co_await" and "co_yield"?

Also, last time I actually tried to use C++20 none of the standard library implementations had std::format; has this changed now?

(comment deleted)
std::format is available, with all C++20 Defect Reports implemented, in VS 2019 16.11.14 (and all later 16.11.x) and VS 2022 17.2 (and all later 17.x).
> last time I actually tried to use C++20 none of the standard library implementations had std::format; has this changed now?

MSVC is the only major implementation that has std::format for now.

It's so funny to me that MSVC has become the cutting edge of C++ standards implementation. I remember starting out when it was a joke compared to Clang - although I was a novice, so who knows how complete my knowledge was at the time.

In any case, it's a really impressive effort from STL and the rest of their library team. STL has some incredible CPPCon talks, too.

One way you could interpret this (definitely not the only way) would be that Microsoft - or some group within Microsoft - sees C++ as a possible legacy system, with an opportunity to make a lot of money by judging correctly what customers want and how much income you need to justify that support as existing offerings rust out (so to speak).

Do you have any particular CPPCon talks to recommend ?

My favourite is "Abseil's Open Source Hashtables: 2 Years In" by Matt Kulukundis, Matt's a fine speaker but what makes it so fun is that Hyrum Wright is in the audience yelling interjections as a result of Hyrum's law (this is scripted). For example Matt explains a significant size optimisation for people who only have a few things in their map, it's just smaller with no other consequences - right? Hyrum points out that now rehash happens earlier, so if you depend on it not to invalidate references during the first 15 insertions you are screwed. Guess whether any real Google code did that...

I really love this talk in particular: https://www.youtube.com/watch?v=4P_kbF0EbZM (On charconv)

But any of the ones I've seen from Stephan have been fantastic, I think, although I'm not actually sure if I've seen more. He seems to have a lot of talks on very specific subjects, which can be really fun.

> Hyrum Wright is in the audience yelling interjections as a result of Hyrum's law

This sounds absolutely hilarious, I'll have to take a look.

I'm glad you liked my charconv talk! Here's a complete list of my recorded conference talks:

BoostCon/C++Now 2012: Regex In C++11 And Boost: https://youtu.be/mUZL-PRWMeg

GoingNative 2012: STL11: Magic && Secrets: https://docs.microsoft.com/en-us/events/goingnative-2012/stl...

GoingNative 2013: Don't Help The Compiler: https://docs.microsoft.com/en-us/events/goingnative-2013/don...

GoingNative 2013: rand() Considered Harmful: https://docs.microsoft.com/en-us/events/goingnative-2013/ran...

CppCon 2014: STL Features And Implementation Techniques: https://youtu.be/dTeKf5Oek2c

CppCon 2015: functional: What's New, And Proper Usage: https://youtu.be/zt7ThwVfap0

CppCon 2016: tuple: What's New, And How It Works: https://youtu.be/JhgWFYfdIho

CppCon 2018: Class Template Argument Deduction for Everyone: https://youtu.be/-H-ut6j1BYU

CppCon 2019: Floating-Point charconv: Making Your Code 10x Faster With C++17's Final Boss: https://youtu.be/4P_kbF0EbZM

CppCon 2020: C++20 STL Features: 1 Year of Development on GitHub: https://youtu.be/8kjRx8vo6y4

Pure Virtual C++ 2022: MSVC C++20/23 Update: https://youtu.be/DAl37n2XOwk

That's a way better list than what I had! Thanks! Think I'll watch the rand() talk tomorrow, I love (to hate on) rand.
I love the idea of placing a gadfly in the crowd to provide a dialogue.
One day in the far away future the standard C++ hello world will use 'import std;' and 'std::print' and it will be glorious.
When I was going to university the computer science department's primary system had two compilers available, GCC 2.96[0] and TenDRA. GCC was generally more popular but it didn't have std::string and any code that threw an exception would crash, so adventurous students would use TenDRA for development.

One of the stranger behaviors of TenDRA was that it put all standard library symbols, including the C headers, into namespace std. A hello world in TenDRA looked like this:

  #include <cstdio> // no <stdio.h> available

  int main() {
    std::printf("Hello, world!\n");
    return 0;
  }
It was not glorious. If anything ... the opposite. When TenDRA deigned to compile your program it would generally work (excepting bugs in the code itself), but getting it to accept any sort of third-party code was impossible because of the std namespace thing.

I ended up writing a bunch of utilities for strings, including a unit testing library that spawned each test as a subprocess (to avoid exceptions) just so I could use GCC instead.

[0] There is no such version recorded on the GNU project website.

GCC 2.96 was a development version that incorrectly got released by Redhat on Redhat 7 (and possibly other distros).

https://gcc.gnu.org/legacy-ml/gcc-announce/2000/msg00003.htm...

This was on a DEC Tru64 system, very far from any sort of Linux.

My personal suspicion is that someone (student or faculty) got their hands on a CVS snapshot and installed it system wide. That's the only explanation I can think of for it being so broken.

Fairly sure some PS2 SDKs also shipped GCC 2.96, which is why I know this tidbit of info.
>There is no such version recorded on the GNU project website.

It's sort-of recorded. It's not in the version list since there wasn't a release, just distros making a mess:

https://gcc.gnu.org/gcc-2.96.html

C symbols are supposed to be in std if you use the cstdio style headers. It’s just that for a very long time compilers put them into the global namespace too.
GCC, Clang, and MSVC all still duplicate c* header definitions into the global namespace by default as far as I'm aware.

In fact, the last time I checked, the MSVC cstdio header was implemented more or less like this:

    namespace std {
        #include <stdio.h>
    }

    using namespace std;
It's kind of crazy how major compilers have been ignoring the standard for so long that people consider a standard compliant compiler to be weird and broken.
> It's kind of crazy how major compilers have been ignoring the standard for so long that people consider a standard compliant compiler to be weird and broken.

I had absolutely no idea this wasn't even supposed to be the case (although I was aware of the duplication in std::). Guess it makes sense that the standard would shy away from global namespace pollution - I suppose one of the compilers perhaps did this for long enough that the others ended up duplicating it to increase code compatibility?

That the as actually not conforming. It was made conforming in c++11 because it was pointless to have the standard say something that all major implementations would ignore.
2.96 exists, at least in the RedHat world.

    $ rpm -qi gcc
    Name        : gcc                          Relocations: (not relocateable)
    Version     : 2.96                              Vendor: Red Hat, Inc.
    Release     : 110                           Build Date: Fri 12 Apr 2002 10:30:47 PM UTC
    Install date: Thu 20 Jan 2011 03:34:36 AM UTC      Build Host: daffy.perf.redhat.com
    Group       : Development/Languages         Source RPM: gcc-2.96-110.src.rpm
    Size        : 8389509                          License: GPL
    Packager    : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
    URL         : http://gcc.gnu.org
    Summary     : The GNU cc and gcc C compilers.
    Description :
    The gcc package includes the cc and gcc GNU compilers for compiling C
    code.
    $ cat /etc/redhat-release
    Red Hat Linux release 7.3 (Valhalla)
(comment deleted)
(comment deleted)
Already available in C++23.
The future tense seems better here. This will (almost certainly) be available in C++ 23.

In the context of Chromium, this slide deck, that means in about 2026.

So if you're starting a three year degree next week, and you already know Google will hire you for the Chromium team straight after because you're the perfect person for that, you won't be writing std::print() calls in that codebase yet 'cos that's too early.

Well, they could convince their management to restart doing clang contributions for ISO C++ compliance, or maybe allocate 20% of their time for that.
The far away future? You mean next year?
I don't mean available as in the committee has published their final version of the standard that allows it. I mean available in production.
Oh, that'll probably be sooner, then; STL is already working on it. Also if you want to use it now, fmt has an implementation.
`std::this_thread::yield` already existed at the time, and “yield” was presumed to also be a common identifier in financial and agricultural contexts.

I’m nevertheless surprised that new noncontextual and previously-unreserved-identifier keywords were added at all. In earlier times, the approach would have been to use a reserved identifier like “_Yield” for the keyword, and to provide a standard opt-in header that would `#define yield _Yield`. Maybe they don’t want to add dependencies on the preprocessor anymore.

I’m not sure C++ has ever used the `_Yield` hack, except for C compatibility? C on the other hand uses it all the time.
It could be that I'm remembering it from C.
It's super common in C because C doesn't have namespaces and templates which means stuff C++ implements as namespaced library classes C has to implement as language keywords.

A good example is atomics. C++11 introduced `std::atomic`, C11 introduced the keyword `_Atomic`

Such a weak argument, you can fix this in your entire code base with a single call to sed. There is no reason to introduce a language feature with some weird prefix instead of changing existing code and having them prefix their variable names.
The east coast fintech folks are the reason we couldn’t have yield as a keyword. Too much of their code uses the variable name yield in the trading sense of the word, and so we’re stuck with this instead. This is what someone on the standards committee told me on the cpp slack a few years ago, at least.
Many are of the listed items are merely being deprecated. These shouldn’t really qualify as breaking changes. In fact, the ability to migrate away incrementally is precisely the point of deprecating instead of removing a feature.

What surprises me is that a lot of the deprecated functionality seems really recent — C++14 or newer. Compatibility is C++’s big thing, that’s historically why it kept almost all of C as a sublanguage. I know organizations where migrating to C++11 is still an ongoing process. It’s not great news if features become obsolete faster than many users can adopt them.

It's a branding problem. They should probably be viewed as different flavors. Using say herbs or colors instead of numbers would help.

If every time they're going to add things, remove things and break things then we're in practice talking different strands.

Imagine some preprocessor where you can mix them like

#flavor(ginger)

Instead of say c++11 and then proceed with whatever flavor as necessary.

I know you can do that at the linker and with makefiles and compile flags, this is about a more sane presentation.

> It's a branding problem. They should probably be viewed as different flavors.

They are already different language versions. They're specified in entirely different standards. I don't see what's left to be confused about. At most, perhaps the C++ standard committee could be criticized for repeatedly going out of their way to maximize backward compatibility.

> If every time they're going to add things, remove things and break things then we're in practice talking different strands.

They are already different standard. What's there to miss?

> I know you can do that at the linker and with makefiles and compile flags, this is about a more sane presentation.

This take doesn't make sense. The C++ version being used in a project is a property of the project, not of the translation unit or individual files. A project is comprised of multiple declarations and corresponding definitions, which are spread around and reused and make sense as a whole. It would not make sense to, say, have a translation unit comprised of X C++11 definitions mixed with Y C++20 definitions.

There's a lot there.

Let's get the technical part done first.

Historically after you create object files the linker doesn't care what c++ standard the source was. So you could carefully combine different standards. I guess I have to establish I'm talking about the GNU toolchain here and that it's been a few years since I've done this. I'll try it again when I get home, maybe that all blows up now.

Now about the other parts. I totally agree with you. However we're dealing with humans and if they see incrementing numbers then the word "upgrade" and "deprecated" and "unsupported", maybe even "inefficient" gets bandied about just because we're using numbers.

We have to go back to the core lesson of Perl 6, it shouldn't have been called Perl 6 because it suggests a hierarchical comparison and relationship that isn't an accurate depiction of reality.

I wish everyone was sincere and competent but there's a natural tenancy to act based on the context that a structure affords.

Churchill stated it as "we shape our buildings and afterwards our buildings shape us".

So if the standards are better understood as siblings of each other then we need to brand them accordingly and not through a structure that suggests hierarchy, quantity of features, and degrees of relevance.

Thanks for your response. I enjoyed reading it

> Historically after you create object files the linker doesn't care what c++ standard the source was.

Mechanically this is true, but just because we can link object files together doesn't mean the resulting program makes sense.

Suppose I have an object file I made with GCC's copy-on-write C++ 98 strings and then I linked that to an object file I made with GCC's modern C++ 11 short string optimised strings. If these objects think they're talking about the same string then the resulting executable is nonsense and will probably crash or misbehave badly.

It might be helpful to think of WG21's attitude to compatibility as occupying a sort of "strategic ambiguity" akin to the US stance on Taiwan. For example when it comes to ABI changes, the committee voted that they shouldn't happen... yet. Not that they will happen within some horizon, but nor that they won't happen.

Maybe you have a deeper understanding of the GNU GCC toolchain than I do but I'm able to link together languages with far more dramatic divergences than that.

For instance, Go and C : https://go.dev/doc/install/gccgo (it's pretty far down, let me quote: "The name of Go functions accessed from C is subject to change. At present the name of a Go function that does not have a receiver is prefix.package.Functionname. The prefix is set by the -fgo-prefix option used when the package is compiled; if the option is not used, the default is go. To call the function from C you must set the name using a GCC extension.")

I just had a small C program call a fmt.Println from a go library at my console to confirm. extern the declaration in C to match the calling convention of go, compile them to objects, then ld with the appropriate libs.

Of course you can break the friendship they can have, this is programming, that's easy to do.

This can be demonstrated with different C++ versions as well. The C/Go example was meant to show how extremely different languages can interact when you're careful enough in your build process.

When gcc broke the string ABI they made sure that code that uses one version wouldn't link with code that uses the other one.
Only for the most common case. Try e.g.

  struct muhahaha {
    std::string member;
  };
library.cpp98:

  oops(muhahaha param);
program.cpp20:

  int main() {
    oops({"this will link just fine and there will be nothing warning you about your fate"});
  }
Not only it is possible, it is routinely done. At $WORK we have libraries that are use std=20 features in its implementation and only expose a c++=14 interface because that's what expected by our clients.

It is mildly painful, bit not more painful than restricting to c++14.

> At $WORK we have libraries that are use std=20 features (...)

That's specified at the package/project level. You're inadvertently proving my point.

> (...) and only expose a c++=14 interface because that's what expected by our clients.

That's also configured at the package level, because the interface headers need to be included in translation units from projecta configured tu be C++14.

Again, you're also inadvertently supporting the point I made.

The real solution, which decades from now will be the eventual common-place, but no one dares to imaging the possibility today is that when languages deprecate a feature, the compilers deliver code-mods that migrate your code-base perfectly.

But people are afraid of going that route because of the bad press around Python 2 / 3. We need a new generation of developers that don't remember that.

Today, there's already static-code analysis tools that automatically migrate entire code-bases to use new language constructs. The technology is there. It's the people who lack imagination to make it happen.

Once it becomes commonplace, a large % of developers will care very little if some obscure feature of C is dropped. They'll just enjoy having a more streamlined language with all the legacy crap gone.

Transformations are difficult.

Rust provides best effort transformation for its Editions (which only touch syntax) in cargo fix --edition, but even that's only best effort. If a proc macro does something ludicrous (see Mara's whichever-compiles! macro for example) how can the transformation hope to keep up?

C++ versions are in much deeper, they not only change the language syntax, but also semantics of existing features. Sometimes the intended effect is null (but intentions may not match reality) and sometimes it is not.

C++ also substantially changes the standard library. Rust's standard library grows but doesn't get redefined in new Editions, so if you call a deprecated Rust function it's not going anywhere, whereas a deprecated C++ method might actually go away in a future version.

It's a shame that the best effort tools aren't provided with C++ compilers, but even if they were provided on a large codebase it's only the beginning for large projects.

clang-tidy is such a tool, provided with a C++ compiler - although incomplete
UB throws a huge wrench into all of this. The behavior of a C++ program is defined by a combination of the code, the compiler, the compiler flags, and the target archecture. You can't write a source to source translator that preserves the semantics of a program with UB without that extra information about how it was compiled. It's a very hairy problem.
Doesn't Google Abseil, the standard library replacement, do this?

https://abseil.io/

It is not a replacement, it is a staging ground and annex.
Swift is another one that has had a really bad migration between major versions, I can’t really remember now but it was either 2->3 or 3->4. They had a static migrator and it barely helped, it would get into loops where it would e.g. flip flop between two different uncompileable fixups.

Seems like this is a really hard problem, what is an example of a platform that has solved it perfectly or is even close? Swift was a brand new language, it should be easiest to do it there vs any system with legacy (although it was designed to interoperate with ObjC, so… maybe legacy remains).

It was 2-3. My take at the time was that they built an add-on to crash xcode.
Mostly deprecations mean something was deemed to be a bad idea, which means it's at the very least worth taking a moment to evaluate whether somehow they were a good idea when you did them.

For example should I have a method whose parameters are volatile? Well, why did I do that? It didn't do anything in C++ 17 and it still doesn't do anything in C++ 20 but now it's deprecated. Programs are written first and foremost to be read by other humans, but what was I trying to communicate by writing "volatile" on a parameter ?

The deprecation of composite operators on volatiles is an even better example. Those do something, but what they do might surprise programmers. Rewriting and then reading the rewritten example is an opportunity to discover that your code was broken anyway because now it's obvious. This obviousness is why deprecating the composites happened (and why it's sad that C++ 23 un-deprecates some of them).

> Programs are written first and foremost to be read by other humans

extremely bold assumption. plenty of write-only or use-once code in the wild.

Fair, lets modify that to just "humans" not "other humans" and say instead that programs should be written first and foremost with this in mind.

I have never discovered a reliable way to discern whether I will re-use some code. Of course I could just delete it after first use and declare it "use-once" that way, but that's cheating, if I just have to type in the same program tomorrow we should admit that deleting it and rewriting it was a pessimisation.

In the specific case of volatile parameters I can't figure out whether writing this in use-once code is more stupid or less stupid. On the one hand, you aren't misleading anybody else because the compiler knows perfectly well this doesn't do anything, on the other hand with the only human participant being yourself maybe you think it does something and whatever that is you're wrong so that's very bad.

Other humans is fine if you include "yourself, in the future" as an "other human" in the ship of Theseus sense
I think if write-only code were the norm, then APL would be more popular.
But there are APL, k, j and other array lang programs. You can't do as if those don't exist when talking about programming as a whole. Perl used to be pretty popular too.
I didn't say array programming doesn't exist, I said it's not popular. How many people actually use those languages compared to e.g. Python, where readability is one of its main features.

> Perl used to be pretty popular too

And now it’s a meme due to how illegible it is.

I’ll put it this way, there’s a reason Brainfuck is a joke lang instead of something people seriously use.

Also, reading and writing are not orthogonal skills. One has to read to be able to write. I read my programs constantly as I'm writing them.

and I am not writing about popularity, just saying that there are many in an absolute sense (which is honestly the only thing that matters in practice, relying on popularity of things relative to each other in a state of abundance is absolutely stupid)
Then I don’t understand your point. The initial claim was that programs are written for human consumption first and foremost. To me, you seem to be replying that programming languages exist that are write only. In that case, agreed. But I don’t see how that is meant to counter the claim that programs are written for humans.
I disagree with the "first and foremost" part - more precisely, I don't think it makes any sense to speak about this. There are people whose entire career will be only APL-ish or whatever funky ML-derived dialect, writing programs in a team of 1 and basically converting their thought processes directly into code, and their experience matters exactly as much as the experience of the X million developers in easier / more team/read-oriented languages matters.
K is very niche, but in its niche is a extremely popular.
> Mostly deprecations mean something was deemed to be a bad idea

"Most" maybe by count, but I'm not sure if it's "most" by usage frequency. There have been & will be lots of nonsensical deprecations that are much more common in existing code than "putting volatile on an object argument" (which I've honestly never even seen anyone do in my life)... like static (which was undeprecated, thankfully, but how were people supposed to know this ahead of time?), std::aligned_storage, std::bind1st, std::iterator. They're frustrating as heck, have questionable merits to begin with, and working around the deprecations in existing code provides practically zero business value.

So as I understand this, you don't agree that std::iterator was deemed to be a bad idea?

I mean, I personally would feel comfortable defending the idea that is a bad idea, but that's not even what I'm saying, I'm only saying it was deemed to be a bad idea, which is exactly what the proposal for deprecating it explains.

No, all I'm saying is that, as I see it, (a) it wasn't causing enough harm (if any at all) to warrant deprecation, and (b) that removing it doesn't deliver any worthwhile benefit either.
The volatile parameters deprecation is one of the stranger decisions, though, since there are plenty of constructions that don’t do anything but are still allowed by the language. I see this as a matter of style that might be useful to warn about, but shouldn’t be policed as an error by the compiler.
Deprecated features are compiler warnings, not compiler errors, unless you use -Werror or the like.
FWIW it participates in overloading. Did you ever try to overload all combinations of teplate specializations? With volatile is even worse.
Chromium compiles with -Werror, so deprecation warnings must be fixed before we can upgrade.
My introduction to c++ was a variant of this book https://www.amazon.co.uk/Sams-Teach-Yourself-21-Days/dp/0672... when I was about 12. However one thing that really stuck with me was a professor at university saying "if you think you know c++ that just means you don't know it well enough to know you don't know it"
The original quote is from Feynman about quantum mechanics.
@dang: Would it be better to have the first part of the title be "Google Chrome" or maybe "Chromium"? The content doesn't deal with Google's internal codebase.
@dang doesn't work, you're best off emailing hn@ycombinator.com
Expected the worst but pretty happy with Kasting's breakdown here. Admittedly most of these issues are self-inflicted but at least it's better than the usual "C++ bad" meme
Way too many of those points are "this sane looking code no longer works, fix by rewriting it to be significantly longer"
I kinda feel bad for taking this jab at C++, since it feels a bit below the belt... But that's kinda C++'s thing, isn't it?

You can either do the correct thing, or the succinct thing. There's hardly ever a satisfying compromise between the two either. Obviously, we want to do the correct thing most of the time, so that's why C++ ends up being full of ceremonious implementations in practice.

And alas, they're usually equally ceremonious to use, because abstractions in C++ are so incredibly leaky because of its poor type system.

I'm not sure why that is, but my gut tells me it's all this backwards [pseudo]compatibility.

That seems a fair observation to me, with the tweak that "correct" changes over time. C++ code can be written perfectly and still rot as the ecosystem changes, without changing the source.

We are really keen on preserving backwards compatibility, but break existing code anyway. We will not define a stable ABI, but also won't fix stdlib if it breaks ABI.

Also all code definitely has UB in it waiting for a compiler change to expose it as wrong and deserving of no longer working.

Ceremonious captures the state of the art accurately.

>C++ code can be written perfectly and still rot as the ecosystem changes, without changing the source.

But that's true of any system that depends on other systems with lax respect for contracts, or with no contract. You can depend on a third-party function get_time() that returns the seconds since the program started, and later on if its maintainers decide to change it to return a UNIX timestamp because they realized the wording was vague enough to allow it, any code that makes the wrong assumption will break.

C++ is, I would say, quite good as far as backwards compatibility goes. The problem IMO is that it's rather complex and some of its features have been misunderstood over time, such as the meaning of volatile or inline, and thus people have been writing subtly broken code that just happened to work when they originally wrote it.

Eh? There's the NVI comparison operator that fits that bill and maybe the std::forward_iterator one but the rest? Most of the other fixes was less code that's also simpler. Or are you getting thrown off by things like the enum slides where it's 3 different solutions all presented on a single slide?
Not mentioned here, but some stuff we bumped into:

* std::filesystem::path broke some APIs with the introduction of u8string.

* some deprecated interfaces of std::allocator got removed.

It was easy enough to resolve and we are in the process of switching to C++20.

The Boeing 377 Stratocruiser was the epitome of piston technology: 28-cylinder radial engine that needed more maintenance than flying time. It vibrated so hard that it separated from the wing.

https://en.wikipedia.org/wiki/Boeing_377_Stratocruiser

Just a note but I'm fairly certain this is purely from the perspective of Google Chrome, meaning it excludes google3 (Google's repo for nearly all Google services). I don't know this for a fact but I suspect it's the case. I only bring it up because google3 C++ is (or was; it's now been years since I've done this directly) a very different beast. It was notionally compliant with recent standards but a very restrictive subset of features were allowed.

The C++ standard currently sits at >1800 pages. Looking through these examples, I'm honestly horrified. The semantics around moving and copying and the many ways you can initialize a variable [1] and how you can mess that up so it's actually a copy instead of a move is just mind-bending.

Can we also talk about how in 2022 we're still talking about and getting wrong const-correctness? I guess we're going ever further now because this presentation touches on constexpr correctness (as in const vs constexpr).

Another thought: problems like comparisons between base and derived problems shouldn't even be problems (IMHO) because you've already messed up by wanting that behaviour.

The change about not doing arithmetic on enum values is a good one but pretty late.

TIL this is a valid way to cast:

    size_t{expanded_size}
[1]: https://en.cppreference.com/w/cpp/language/initialization
The standard includes the standard library, a tiny set of pages when compared against Python, Java, F#, C# language reference, VM reference + standard library printout.
> The C++ standard currently sits at >1800 pages. Looking through these examples, I'm honestly horrified.

I feel you're embelishing too much your personal feeling of horror. The C++20 standard doc is a hair smaller than 1900 pages, but the complete core language is specified in the first 460 pages, of which around 100 are dedicated to templates.

Thus around 1400 pages of a 1900page doc are dedicated to specify libraries that throughout the years have been adopted by the standard. We're talking about stuff that was released with Boost and since then was deemed appropriate to make it standard.

Focusing on the 460 pages that specify the core language, most of this content has not been changed since C++98. The C++14 doc covered the core language with around 420 pages. Thus it makes zero sense to claim than suddenly C++ became horrifying because of the extra 20 sheets of paper you need to print out.

You could focus on those 460 pages but I'll raise 2 points:

1. There's still a lot of complexity and ambiguity you can fit in 460 pages. This presentation notes one example of decrement operators on volatile variables being deprecated because the behaviour was undefined; and

2. Can you really separate the standard library from the language at this point? Things like move semantics depend on std. Does anyone actually use C++ without any of the standard library?

1. No, that's wrong. Incrementing a volatile isn't undefined. The problem is that some people are using volatile when they actually want atomic variables, so the behavior of the program becomes undefined when you have two threads incrementing the same volatile variable at the same time. The compiler might or might not compile volatile_variable++ into an atomic operation, but some people wrote code under the misunderstanding of the language that it always would.

2. Sure. While it's true that using certain features of the language technically requires parts of the standard library, those parts are very few and very simple. What is there? std::move(), std::pair and std::tuple, <typeinfo>, and perhaps a couple other things?

std::move is just syntax sugar over static_cast<T&&>(t) ; the language feature that needs library support afaik are:

- Overloading some of the "new" operators (need #include <new>)

- <initializer_list>

- typeid which needs <typeinfo> as you said

I don't see which parts of the language need pair and tuple at all?

I was mistaken. I thought you needed std::tuple for structured binding, but it seems the language can also destructure other types.
it's not that you need it, it's that when destructuring, std::tuple_size / std::tuple_element are implicitly checked to see how destructuring can be made to work if you have a type with some custom destructuring. (example: https://gcc.godbolt.org/z/5jq61oox7)

If they are not available or just not overloaded (e.g. when destructuring some basic struct) it just falls back to the destructuring one expects. So you need <tuple> for the specific case of implementing custom destructuring, that is one more case to add to the list :)

This whole thread is why c++ is..bad. It just leaves me with a sense of hopelessness.
You're being overdramatic. You don't need to be a language lawyer to use C++ correctly and effectively.
> This whole thread is why c++ is..bad. It just leaves me with a sense of hopelessness.

You've tried to force your personal misconceptions as some kind of gotchas that justify you irrational dislike for a programming language, and once each and every misconception you had was dispelled and debunked, your reaction was to double-down on your irrational belief.

This does not flag failures in a language.

? That's their only comment on the entire page.
> No, that's wrong. Incrementing a volatile isn't undefined. The problem is that some people are using volatile when they actually want atomic variables, so the behavior of the program becomes undefined when you have two threads incrementing the same volatile variable at the same time. The compiler might or might not compile volatile_variable++ into an atomic operation, but some people wrote code under the misunderstanding of the language that it always would.

What you're describing is a different long standing problem. The deprecation of compound volatile operations is because as volatile operations they were nonsense. A volatile operation is a single non-tearing fetch or store, but compound operations by their nature are both a fetch and a store.

By obliging users of volatiles to be explicit with the separate fetch and store, the intent was to highlight that this is not a single operation.

Abuse of volatile to mean "atomic" is so widespread it's how MSVC actually works by default on x86. Access to volatiles with MSVC /volatile:ms - which is default for x86 targets - means Aquire-Release atomic semantics.

Deprecating the compound ops doesn't change that in either direction, it's a bad idea, people, especially Windows developers, will expect it to work, Microsoft will enable it on x86 (but by default not on ARM).

1. The problem you've highlighted (pre/post increment of volatiles) is present in C, and hasn't been addressed there. This is a problem in c2x on compiler explorer for example. So i'd probably say laying this at the feet of the overly large C++ language spec isn't fair. I'm going to probably say it's been there since K&R C days (I think volatile was supported even that far back, but my memory is a bit hazy about such things).

2. This is a good point. I'd counter it by asking does anyone use all of the standard library in a project? If I were to include what i'd ever used, it's probably a subset of the standard.

I hadn't thought about std::move, and actually had to look up which header it's pulled in from, since it tends to get pulled in by other stuff i'm using!

Both the qualifiers const and volatile were new in ANSI C (which became ISO C89). In fact I believe the idea of type qualifiers in C is imported from (pre-standard) C++, thus it's Bjarne's fault.

The general idea (if we force the optimiser to emit the memory access then we can abuse that to do MMIO) is older than ANSI C and as I understand it begins when peephole optimisers begin to first make the "obvious" trick not work in C compilers, but I don't know when it became the volatile qualifier.

As in C++ the correct fix is to use dedicated intrinsics. JF Bastien wrote up C++ intrinsics for this as a template, obviously the C intrinsics would not be a template, but the general idea applies. In reality your hardware does not implement crazy nonsense like a 196-bit unaligned non-tearing memory fetch, so you don't need customisable intrinsics.

e.g. maybe C gets __volatile_load_64(ptr) and that's 64-bit aligned fetch from the address in ptr, there would be a handful you actually need for 8-bit, 16-bit, 32-bit, 64-bit, maybe 128-bit, loads plus stores and perhaps implementations are asked to offer any special cases for their platform, I can imagine unaligned 32-bit is plausible on x86 for example, maybe some DSP has 24-bit, that sort of thing.

The idea is the intrinsic emits the same CPU instructions which are what happens for volatile access today, but as intrinsics they don't give the false impression you can do other stuff, this isn't really memory even though the CPU instructions are memory access instructions.

Couldn't you still use move semantics without std::move? You'd just have to write your own cast to an rvalue-reference.
> Couldn't you still use move semantics without std::move?

That's correct, std::move is just syntactic sugar to cast objects to revalue references, i.e., the sort of object that is expected to be moved around.

(comment deleted)
> 1. There's still a lot of complexity and ambiguity you can fit in 460 pages.

That's a meaningless assertion and reads like a non-sequitur.

Your thesis is that modern C++ somehow became complex. Yet, the truth of the matter is that modern C++ is mostly comprised of formerly third-party libraries, initially released as part of the likes of Boost, that were since then added to the standard. The core language did received much needed improvements throughout the years, such as aggregate initialization with designated initializers, but the total sum of these changes barely grew the standard in around 5% of it's initial size.

Unless you come up with clear examples that you feel support your thesis, you'll have to scratch out your complains as irrational dislikes.

This is from Chromium's perspective, yes, but Chromium complies with the Google style guide. I disagree with your characterization that "a very restrictive subset of features" are allowed, though. What's banned is relatively limited, IMO: http://google.github.io/styleguide/cppguide.html
This might be a stupid question. But is there performance degradation between versions of c++?

I saw a couple of stack overflow questions last week of people complaining about c++ 17 being slower 14, and 14 being slower than 11.

But I find it hard to believe. I just started learning c++ recently and can’t find a conclusive answer.

Those are just versions of the standard. What people would be comparing is the performance of particular implementations. It's entirely possible that a less mature implementation would have problems.
If you're writing the same code, almost certainly not.

The only way there would be would be if the compiler either can't optimize something as well because the lang changes requirements (this seems unlikely), or if they have bugs or inefficiencies because they haven't spent much time on the new standard.

If you're using new stuff from the new standard, they could be slower or faster or undecidable, really depends.

There is at least one language-enforced performance improvement: C++17 enforces some amount of RVO. Thus some stuff that would have been copied in previous standard is now not anymore. More standard types supporting move semantics & such also mean that generic code which correctly calls std::move / std::forward may do fewer copies the more recent standard you use.
The "idiomatic" version is slowly getting slower because the idioms are getting higher-level and more expressive. If you don't use the idioms, you can have the same speed. Smart pointers (including unique_ptr<Foo>) have non-zero overhead compared to Foo*. The object oriented parts can introduce significant slowdowns. Template code can have huge code footprints if you are not careful, which slows things down.

If you are looking for cycle-level performance, you likely need to use a more C-like subset of the language, but you can still use many conveniences like RAII and the smart pointers with 0 overhead (when construct/destruct are not in the critical path).

The above is commonly encountered, but not good advice.

Most important, defend your hot path against incursions. E.g., don't pass smart pointer arguments around on hot paths. That is what matters.

New language features are not slower than old features. But there are slow constructs to use carefully. Which they are will always surprise you, usually pleasantly. For example, almost everything around lambdas is fast, fast, fast, except =capture.

Measure often. Look for surprises.

> Don't pass smart pointer arguments around on hot paths.

oh this so much.. I remember optimizing a particle system which would copy a shared_ptr for each particle on every update operation... IIRC just switching to references ended up being 10+ times faster

If you can design your system so you rarely if ever use pointers, that would be the single best thing you can do for your C++ codebase

I like to enlighten my interns every summer about the reality of pointers, and how much of a noob trap they are, and that yes, their professor lied to them in some ways

sadly this was not "my system" but was something for a 2-3 day project on an unknown codebase so a deeper refactoring was out-of-the-question
I literally said not to construct or destruct smart pointers on the hot path.

Also, my experience with lambdas has been otherwise. There are a lot of cases where lambdas need a heap allocation when you otherwise wouldn't. However, being able to use FP ideas can make up for that.

My objection is to the expression "C-like". That does not correctly distinguish what is fast from what isn't, and instead promotes a harmful myth.

A lambda needs a heap allocation only if any of its captures do.

The idiomatic version has gotten faster since C++03 overall thanks to move semantics.

std::unique_ptr<Foo> doesn't have any overhead over Foo* in code where semantics is the same (i.e. you want to delete when going out of scope) on any sensible ABI - it has the same storage size, and all operations are trivially inlineable to the same exact thing you'd do with a raw pointer. About the only time I can think of where it can be slower is when the ABI insists that a struct cannot be passed or returned in a register.

> std::unique_ptr<Foo> doesn't have any overhead over Foo* in code where semantics is the same

Not true. And you just refuted yourself by being up the ABI question. You should watch Titus Winters' presentation on this topic where he compiles code using unique_ptr and raw pointers, compares the assembler produced, and explains why unique_ptr has non-zero overhead. The overhead is indeed coming from the ABI. Google has been wanting the next C++ to break ABI but they didn't get enough votes in the standard committee.

https://youtu.be/rHIkrotSwcc

Not breaking binary compatibility is a pure good.

The standard could pull unique_ptr in as a built-in feature with a different name, and retain backward compatibility at cost of an unfortunate redundancy. Code using unique_ptr would gradually transition to the new thing.

Of course each would have a move constructor from the other, to ease the transition.

I mentioned to a former team mate who has been doing c++ for 30 years or so that he might take a look at go or rust for a lot of the things they're doing.

His response was, "I already have a new language to learn: c++".

The word for this is "future shock".
Sometimes you need to avoid GC or have direct control of your threading model and you need a language that is more expressive than C. Your only two options here with broad adoption are C++ and Rust, both of which are very complicated languages.

Other languages like Zig, Ocaml, and Erlang have legitimate claims to similar performance to C++/Rust with expressiveness, but they do not have the same adoption.

See my comment elsewhere in this thread, where I argue that Go is a good replacement for C/C++ for almost all purposes.
I have spent several years writing C++ in circumstances where Go is a terrible C++ replacement. In these cases, you either need manual control of memory or you have extremely tight requirements on either speed or memory usage. In both cases, Go falls short, and C++ actually works relatively well (so does Rust - everything I am saying about C++ here applies to Rust as well). Despite C++ having a large spec and being complicated, it pushes all of that complexity to compile time. At runtime, you pay nothing for the complexity of the language.

Please elaborate to me as to why Go is an appropriate C++ replacement for:

* Trading systems that use direct NIC access and need sub-microsecond execution times.

* Performance-oriented databases (like ScyllaDB or Aerospike). Like trading systems, these are characterized by having custom shared-nothing (often stackless) asynchronous runtimes that need direct control of syscalls, and optimized IPC and synchronization primitives.

* Libraries like memcpy, math libraries, compression libraries, and encryption libraries, which need both SIMD intrinsics and minimal overhead compared to assembly implementations (Go fails on the second part of this criterion - even the Go calling convention has significant overhead). And no, you do not need to write these in assembly: C and C++ versions are far more readable and equally fast.

* Memory allocators, which cannot circularly depend on another memory allocator.

* Embedded systems with constrained memory footprints.

* Hardware drivers.

* Code for non-CPU machines, like GPUs or DSPs.

Almost all the real use cases for Rust, C, or C++ are not suitable for Go. Go is only a suitable replacement for systems programming languages in places where Java is also a suitable replacement: when you have a powerful computer and fairly loose constraints, and you are primarily doing business logic.

Go was specifically designed as a language for un-demanding applications written by coders with strictly limited skills.

That is most applications, in practice. Unpleasantly often, though, applications move from un-demanding to demanding, and then having been coded in Go is unfortunate. A nimble organization will code a C++ program for that use case. Lesser shops will lumber along patching the Go code.