C++20, How Hard Could It Be
docs.google.com
docs.google.com
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.
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).
"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.
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.
extremely bold assumption. plenty of write-only or use-once code in the wild.
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.
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.
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.
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).
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.
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.
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
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.
It is mildly painful, bit not more painful than restricting to c++14.
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.
Also, last time I actually tried to use C++20 none of the standard library implementations had std::format; has this changed now?
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.
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 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/legacy-ml/gcc-announce/2000/msg00003.htm...
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.
$ 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)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.
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.
MSVC is the only major implementation that has std::format for now.
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.
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...
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.
URL seems to be https://www.youtube.com/watch?v=JZE3_0qvrMg ?
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/initializationI 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.
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?
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?
- 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?
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).
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!
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.
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.
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.
He covers learning programming and books like the one you mentioned.
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.
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
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.
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?!
And incompatibility with the rest of the world.
I can see Reflection as problematic, but what's wrong with streams?
all these things are sometimes the best solution to a given problem
https://google.github.io/styleguide/cppguide.html#Exceptions
In the end, it makes code very difficult to reason about.
It was a deliberate design goal of C++ exceptions that intermediate libraries/callers/etc do not have to be aware of, or handle, exceptions. There is no way to verify or check that all exceptions are caught by someone, etc. This is, as i said, deliberate.
This is quite nice in smaller systems, where you pretty much know every dependency and what calls will do.
But in larger, complex systems, where it is very hard to know or control every level of dependency, it is quite painful and fragile, because something 37 levels deep might suddenly throw new exceptions (or exceptions at all), violate 0 of the API guarantees that are being made[1], and start crashing your program in edge cases.
Among other things
[1] It's very easy for one library to say "we throw exceptions, something must catch them", and the dependent to say "we propagate all exceptions, we don't catch them". Even if something promises to catch them in the middle, it will very quickly get lost as it gets further away from the library actually throwing the exceptions.
The fact that you may be able to successfully assign blame or root cause to the problem doesn't help - being able to say something like "this library 36 levels deep in my dependency tree is not following best practices" is not a particularly helpful thing for development.
The cost is that cleanup operations have to be in destructors. Do that, and exceptions demand almost no attention.
Problems show up only when some prima donna declares throwing exceptions from what they call isn't allowed.
Google uses absl::Status / absl::StatusOr as a replacement for what people would usually use exceptions for.
The benefits:
1. As an end user of a large library I can tell you exactly which calls are "guaranteed" to succeed without learning anything else about the code.
2. It is impossible to ignore a Status/StatusOr which makes it impossible to ignore a failure. You have to explicitly ignore the return value which is easy to spot in code review.
3. You can return your exception across RPC an boundary with added detail so if a caller of your service.
4. You can (very easily) enrich the Status with metadata at every single point. So much so that it is very common.
5. You can enrich the way "exceptions" works in a way that makes sense for your company by changing the structure of Status.
Cons:
1. It is more unnatural than dedicated syntax like try/catch.
2. You can't bubble exceptions up the stack automatically so there are macros that do this for you (https://cs.opensource.google/search?q=ASSIGN_OR_RETURN&sq=)
2. an exception cannot be ignored either.
3. you can transform an exception into a value or even code them as values.
4. See nested_exception.
You see how that isn't particularly helpful or constructive to discussion, right?
Maybe you'd like to at least point out the very large scale systems that are using and getting benefits from exceptions, as you claim exists? I can't personally name a single one.
And, we want our functions to be small, do one thing, and be easily composed.
The problem with Result and their ilk is that they pile on overhead at function-call boundaries, exactly the place where you need design to be fluid.
The great insight behind C++ was that cleanup, packaged and applied automatically, eliminates opportunities for mistakes, freeing up our attention. Exceptions apply that to error handling by running the same, well tested code when errors happen, and gathering error events to a place where we are equipped to do something useful about them.
Now that we often don't even need to code destructors anymore -- the compiler does it better -- our attention is freed for real-world problems. And, not needing to worry about propagating errors up the stack, we can focus on arguments and control moving down and results up.
Attention is easiest to manage in a small program. There just isn't as much to think about. In a big program, with many people involved, it costs a lot more to deal with extra junk, choices about how to bubble errors out. Structuring error handling into destructors and exceptions provides a common framework that burns no discussion time. Everybody gets to concentrate on solving the actual problem, without distraction by incidentals.
We would not be having this discussion if Google had not imposed its rule banning exceptions, which we know was imposed just to accommodate legacy code that did not have destructors. Now they have a thousand times as much code, still without good destructors. Wouldn't it have been less work to fix that code, then, than to impose its costs on everyone today?
His response was, "I already have a new language to learn: c++".
* 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.
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.
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.
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.
That being said, it will make writing bindings a LOT easier, even though D already has direct binding to C functions.
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.
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.
Edit: Also, module support better be 100% binary compatible between GCC and Clang, or it's worse than complete garbage.
- 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
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.
Heck even Go folks are discovering their stable guarantee isn't as stable as they thought.
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.
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.
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 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.
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.
> 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.
Is there anything I can read to get up to date quickly?
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.
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.
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.
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 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.
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.
Well when rust makes changes every 6 weeks we all hold hands and update all of our code.
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.
- 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.
Best line in the deck:
"Only write complicated code when you truly need performance. Comment if you do...."
I guess once again you do actually end up paying for what you don't use.
> Solution: Don’t care?
:(
Would have been nice if they at least analyzed where the increase was coming from. Maybe operator<=>?
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.
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.
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.
Except garbage collection, of course.
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.
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.
Not sure why Google would continue to contribute under those circumstances.
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
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...
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.
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
Where does this GCC is faster thing come from? I personally havent experienced it
Also, I don't understand why there are so many negative views on C++. C++ is a beast, but somehow you can control it, and C++ is too popular and still in use to be replaced by anything else in the near future.
1. a well-tested language
2. can not use garbage collector due to *performance* requirement
3. easy to hire if project expands.
4. ready to use tooling support
5. widely available tutorials and info on the web.
6. language is itself alive and updated
7. project can be scaled over time.
what options do I have? I have to pick up c++ in this case. it falls to the saying "a language is either blamed, or nobody uses it".Javascript for the web, Python for machine learning, C for low level and system coding, Go for some native cloud and DevOps, Java for enterprise or Android, C# if you're a windows developer, Swift if you are doing Apple, we actually don't have a lot of choices when you need deliver things faster.
I played with nim, ziglang and Rust, but it's hard to use them for real product developments at this point for me.
> ready to use tooling support
This really depends on the use case. If the tooling you're looking for is "Unreal Engine", then Rust is definitely not there yet. But if you want to write network services, databases, CLI tools, etc., I think Rust has been fully viable with good tooling and library support for years now.
> easy to hire if project expands
No doubt hiring experienced Rust developers is hard, because there aren't that many yet. But anecdotally, training Rust developers is much easier than training C++ developers. Rust makes it easier for one or a few experienced folks to have confidence that a larger number of less experienced contributors aren't introducing stability or security issues.
Picking Java over kotlin for android, or lumping C# into "just for windows" bucket is exactly the ignorance that leads you to "we don't have choices" conclusion. If you always default to literally top1 language for given domain, then how can you expect to have multiple choices?
I'm not talking about using some brand new gimmick that came out yesterday and will die tomorrow, but if you aren't able to leave your comfort zone even just-so slightly to update your stereotypes about given given domain and its trends, then by definition you won't see the alternatives.
Python is the traditional scripting language for high-performance computing (HPC) going back a very long time, as a wrapper for highly optimized and scalable C libraries. Python on supercomputers wasn't originally used for machine learning but it naturally included support for excellent linear algebra libraries, etc for various types of large-scale modeling. When machine learning first became trendy, data scientists that wanted access to mature library implementations of critical algorithms that worked very well at large scales were mostly limited to C/C++ or Python wrappers of that C/C++. Naturally, they chose Python for the same reason Python was already being used in HPC -- ease of use.
By virtue of its use in supercomputing, Python had uniquely strong support for many types of machine learning before there was a demand for machine learning. If HPC had used some other popular scripting language instead of Python, we'd probably be using that for machine learning right now.
What made it hard? For example, Rust seem to fit your bullet list.
You could just use C.
A large amount of C++ development is still focused on enterprise and UI apps, even though one could argue that it is not the best language for that purpose.
Someone needs to write a c++ 101 book, followed by 201 and 301 for beginner, intermediate, and advanced users.
But then, it's hard to find good programmers for any language, so C++ isn't really any worse off here, and better off than some in that at least there's a bunch of candidates to pick from.
I like to live in the embedded world and there I think Rust will shortly start to push out C and C++ at the wide-spread, commercial level. This is where my head is at, maybe it helps you formulate your opinion:
First, embedded C++ is not the same a full C++ (with features or library parts missing to accommodate the constrained environment). So I might never see some of the C++2x or even C++1x features. I really don't care about the C++2x standards since I might not see them in the next 10 years.
Second, memory and concurrency issues are real, even when you don't allow dynamic memory allocation after the system is initialized. We test, then test, and then run more tests, and turn on a lot of linters and analyzers but at the end of the day, they're bandaids. You can write terrible code in Rust, but you have to work harder to do it.
Third, if you really need an environment where memory is as fungible as play dough but want something portable (like inside a kernel), there will always be C. Setting up a C callable environment from assembly is well understood. In theory, calling into Rust shouldn't be any harder. But reading disassembled object files from C is generally reasonable assembler for the C I wrote. I haven't really tried doing that with Rust.
Fourth, it will take 5 years or so, but eventually we'll see commercially supported RTOS implementations for Rust that help companies to shield themselves from liability. For example, something like Threadx (AzureRTOS now). This will help push money in a virtuous cycle of better tooling -> more projects -> more money for tool venders -> better tooling.
Fifth, there will be Rust bindings to cover just about everything you do with C++ now (like game development) with high quality, idiot-proof, and portable semantics. You will find that junior developers or fresh-outs will have their skin crawl when asked to write "unsafe" code.
Sixth, there's counter evidence to support a lack of adoption of new languages. Ada provides many of the same concurrency and memory safety guarantees as Rust, and has been available since 1983, but it is not widely adopted. It is harder to use and requires significant developer training. If Rust is significantly more challenging for developers, it may go the way of Ada.
But that's in the future. If I had to start a new project today (for a braking system on a train, for example), I would start with C, a validated RTOS, and commercial toolchain. If I had to light up my kid's halloween costume? Embedded Rust as a learning exercise. The rendering engine for a VR headset (with limited batter and compute), probably C++.
Rust is the only real competitor, but given the sheer amount of existing C and C++ code, the idea that they will become legacy languages any time soon is pretty out to lunch.
Rust is definitely difficult to learn, but I'll anecdotally say that Rust helped get me into the embedded world as someone with very little C or C++ experience. Unlike Ada, Rust has a modern toolchain and type system that feels very comfortable to developers coming from higher level languages, even if many of the concepts related to manual memory management remain quite difficult.
While some applications written in C++ are not performance sensitive, performance tends to be a major objective when choosing C++ for many applications.
Go is a good replacement for C and C++ for almost all purposes.
Most purposes where Go is inapplicable should be using explicit SIMD (GCC intrinsics) or CUDA C anyway.
The others purposes where Go is inapplicable are low-level real-time stuff that should be written in C for a specialized software stack (e.g., software in a car).
I like go, I use it on occasion. If I stick to the simple stuff it is really easy to think about and if it is just a back end on a big server, why not. But that is a niche case for me. I usually prototype in matlab, then implement in c++, and yes, often cuda too, but I think saying that go is almost always a drop in for c++ is missing the vast majority of what c++ is used for. Go is not a systems language, it benchmarks slower than many vm lamguages like java and c#. It's gimmick is that it compiles really fast and is easy to reason about, so it is good for velocity. But it sacrifices a lot for that. FFI compatibility, and runtime speed foremost. Sometimes those things don't matter. But for systems programming, dsp, embedded, or AAA games they are deal breakers.
Go also has the stop-the-world GC problem, GC is great but it does have a price tag.
I like Go a _lot_ and use it in some projects, but I certainly will not claim it can replace C++ 'generally', not at all.
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.
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).
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.
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.
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.
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
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.
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.
https://chromium.googlesource.com/chromium/src/+/HEAD/styleg...
And I noticed the ban of shared_ptr and <chrono>. I know the use of shard_ptr should be done cautiously, but just banning it from use? How do the Chromium devs solve a problem that requires shared ownership? I guess you can come a long way with singletons and consumer/producer queues. But are raw-pointers just used instead of shared_ptr when shared ownership is needed?
Also banning use of <chrono> ?
Can someone try to explain the possible reasons?
Regarding smart pointers, not sure if you also found this: https://chromium.googlesource.com/chromium/src/+/HEAD/base/m...
As some comedian used to quip, Pregnant Guppy would be a good band name.
But here I found some benchmarks (did not inspect though): https://www.phoronix.com/review/11900k-gcc11-clang12/2
Half of the "features" shown in the presentation are exceedingly niche. It's just that a 20 million LOC (or however many Chrome has right now) code base is almost guaranteed to exercise every niche feature somewhere.
Having helped with C++ standard transitions of a code base half an order of magnitude smaller, the amount of issues requiring code changes was minuscule so far. You tend to have much more boring problems, like:
- "We can't use newer C++ because C++/CLI doesn't support it"
- "We can't use newer C++ in these headers because they end up included in CUDA code, and that compiler only supports C++14"
- "We have to wait for a new version of <tool> before we can lint/sanitize/profile/format our code"
- "This third-party code has the compiler up in arms now because it does questionable things"
So, backwards compatibility is there (if we ignore newly introduced keywords).
This doesn't look very fun.
1. Google always seem to enjoy bucking the trend in terms of recommended ways to do things and end up using niche features that are likely to be deprecated 2. Chromium is a stupidly massive codebase so they have more things to fix just purely through scale
If you stick to the sane subset of the language that any competent modern C++ development shop would then you wouldn't encounter any of these problems except small/easy stuff like the new reserved keywords.
That being said, if you don't have a team that's already experienced in C++ then picking C++ is a bizarre choice.
It's a language where segfaults, memory leaks, and other problematic issues are easy to manifest.
I assume they have deep Java knowledge, since they suggested it themselves.
If a tech giant wants this to happen - e.g. because memory un-safety is mostly the root of all evil - they can use people from the current team and throw a few millions on hiring new talent.
Also it’s just rewriting the logic. One could theoretically write unit tests for every function to make sure the inputs/outputs match.
const int MAX_SIZE = 1024;
const int HEADER_SIZE = 128;
set_content_size(HEADER_SIZE - MAX_SIZE); set_content_size(MAX_SIZE-HEADER_SIZE);
Sayin'.In C++, const int x = 3 is already a constant. As of C++98 already, you can use x as a case label in a switch statement.
It cannot be any more constant.
Not cool any more?
Otherwise, const is fine.
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.
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)
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...
The valid use cases for it are ridiculously few.
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 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...