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.
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.
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.
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"});
}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.
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).
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).
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.
> 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.
"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.
Also, 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.
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.
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
URL seems to be https://www.youtube.com/watch?v=JZE3_0qvrMg ?
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.
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.
It's sort-of recorded. It's not in the version list since there wasn't a release, just distros making a mess:
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.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?
$ 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.
A good example is atomics. C++11 introduced `std::atomic`, C11 introduced the keyword `_Atomic`
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?
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 :)
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.
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 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.
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.
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.
all these things are sometimes the best solution to a given problem
I can see Reflection as problematic, but what's wrong with streams?
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.
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.
Additionally the C++ ABI doesn't tell anything about how each binary library was compiled regarding compiler and linker switches that affect runtime behaviour.
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.
(Like, literally, going by the size of the language spec.)
- 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
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.
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.
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.
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.
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.
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?
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.
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.
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.
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.
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.
Perhaps you missed this part
> > Decided to try the last part in rust...
"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.
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.
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
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.
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.
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.
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.
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.
Also, when you say structured, do you mean errors over LSP, or do you mean more structure in the formatting when reporting in CLI?
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
They always claims so.
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...
Hurrah for competition!
(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.
> 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...
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.
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.
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.
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.
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.
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.
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
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.
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).
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.
But for pretty much everything else (see the caveats in my comment above) you are better off, a lot better off, using Go.
Go is very good for many things but it does not offer competitive performance for these types of applications because design elements that have a large impact on throughput are poorly supported.
The compiler is a huge assemblage of C++ templates, stitched together with a little Python.
The generated code is in an assembly language for a highly parallel machine, and needs to be heavily optimized down to the cycle.
But the compiler itself does NOT need to be so minutely tuned. It needs to be maintainable and it needs to allow easy development of complex code generators.
Go would be a great choice for the compiler.
Instead, we struggle with C++ templates, CMake'ing our code slowly and with absurd complexity, modifying the compiler slowly and with great difficulty...
Also, you should not discount compiler speed completely. You, yourself, were upset at the speed of a compile process. All of your users are going to be waiting to get things done while the compiler is running, so you might want to do some math as to how much a 10% slowdown would cost in developer salaries. The makers of gcc, LLVM, and clang spend a lot of effort on improving the speed of their compiler to make sure that you have minimal time waste.
Get a handle on your code quality, and put the language features to work to solve problems for you. Then Go will increasingly look pitifully inadequate.
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.
For example, the C/C++ implementations use arena allocators, and they could do the same for the Go implementations, but they don't.
The Benchmark Game is a joke. Here it is, for anyone who wants a good laugh: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Using c++17 (don't know 20 yet) for very small embedded targets works fine, easy FFI into the BSP, small to no runtime depending on usage.
Trying to target that with go would add a lot of extra challenge.
I think something like rust or preferably a subset of it for c++ ise cases makes sense. My next project without a GPU I have penciled in rust on the plan. The other developers are excited. Still too hard to use gpu with it. Or rather too immature.
The other developers like go too, but throughput will be critical without the gpu, and every little bit will count. And for numeric code it wouldn't just be a little bit of a hit. I wouldn't even suggest julia, and that is as close to a do-it-all language as I have seen.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
You can use mark and sweep, you can us reference counting, you can use arenas, you can use local stack, you can use shared memory, you can have custom allocators for any object, you can avoid memory allocations altogether, and any combination of these.
C is the same. Java can do some of this, if you twist it hard enough. Go cannot. That's ok -- often, even most of the time, you don't care, and then Go is a fine language.
But C++ is useful in a much wider set of domains than many people think, because it offers a level of control that many other languages don't.
My favourite analogy? Go (etc) is like an Apple product: smooth, and shiny, and tries to be idiot-proof, but you can only do it the Go way. C++ is more Linux: a mess of incompatible, incomplete, infuriating things that let you do absolutely anything you want, if you invest the effort.
Are you saying Go has portability problems?
Can you elaborate on that very surprising claim?
The issue is that, for example, you might be getting people with poor software engineering training per se. You risk hiring some very smart phd that writes code that works and runs really fast, but isn't that readable, extendable, maintainable, testable, etc.
You could just use C.
The new features are added specifically because they enable writing better programs. Avoiding them means you are choosing not to write better programs. It is allowed, but not a thing to brag about.
That does not mean every program has to use every feature. But when there is a choice between the new way and the old way to do something, the new way is very probably better.
Passing a reference, where you can, is better than passing a pointer. Passing a lambda is better than passing a function pointer. New for-loops are better than old for-loops. A unique_ptr is a better member than a naked pointer. A compiler-generated destructor is better than a hand-coded one.
And so on.
C is the language that invites in mistakes. The less C we code, the fewer mistakes we ship.
I have worked on template-heavy codebases that would really benefit from concepts, though.
That plus structured bindings return types. :)
Any big-enough system will naturally have some OO-ish parts, just because it has some of almost everything. Most small programs will have none.
They're techniques after all, models, tools.
Someone needs to write a c++ 101 book, followed by 201 and 301 for beginner, intermediate, and advanced users.
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.
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.
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.
What's the alternative for quick ML prototyping?
Java/Scala I've also seen, specifically things around Spark ML, and Spark compute.
That said, Python is definitely the most common nowadays I'd say.
We are doing L4, so if something goes wrong, the manufacturer is responsible. Once we were in a test car. It was behaving great. At one point, the car "makes the decision" to accelerate and pass. The manager to which we were showing asks "we did the car not waited a little bit? can we change that behavior?". A very long discussion ensued, about data sets, labeling, training... long story short: we have to be able to change behavior (even for perception) in a deterministic way. So the amount of NNs went down dramatically after that.
Even if the given reason is simple, like "I decided to slow down because the car in front of me is red", explicit override of the learned rule ("don't slow down when you see red cars") might potentially increase the chance of the accident by a lot more than 0.0002% because we are messing with the model decision making process.
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.
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 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.
We have a modern toolchain too :)
# install compiler and build tools
alr toolchain --select
# make, build and run a project
alr init --bin my_project
alr build
alr runThey might both have a rule like: AV Rule 1 Any one function (or method) will contain no more than 200 logical source lines of code (L- SLOCs).
But Ada would probably not have something like: AV Rule 59 (MISRA Rule 59, Revised) The statements forming the body of an if, else if, else, while, do...while or for statement shall always be enclosed in braces, even if the braces form an empty block.
Or: AV Rule 193 (MISRA Rule 61) Every non-empty case clause in a switch statement shall be terminated with a break statement.
Those two arise from deficiencies in the basic C/C++ syntax. They are probably not fixable in a future revision of the spec because they would break too much code. So we create a coding standard, configure a linter to check for it, wire up our source repository to scan for it on checkin, and make all the leads code review for it.
Ada might have different language specific standards. For example, around not allowing breaks out of infinite loops. Requiring, instead, a loop construct with a testable condition. But even then, you can pragma things out of the compiler to help you enforce behavior. That's "built in."
My comment with Rust is not that Ada is bad or too hard, but rather even a small amount of additional work might be enough for developers and organizations to avoid Rust. There's a lot of Rust advocacy, which is a good thing. However, there was also a lot of activity on comp.lang.ada.advocacy (if I remember the newsgroup correctly). One good thing is that Ada felt "imposed" by a number of people and that automatically stimulates a kind of rejection response. Rust's introduction is definitely more "bottom up."
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.
A lot of command line tooling is being written in Go along with systems software (like basically all of container land). Maybe some of these would have otherwise been written in C or C++. For example, if you use the Azure storage GUI to manage storage the underlying "thing" that copies data from your filesystem to azure storage is written in Go and that's why it works on Windows, Linux, MacOS. C++ is somewhat more portable than C so maybe something like that would have been written in C++? Who knows? But Go is definitely showing up in all sorts of native, command-line stuff. And Rob Pike - one of the people who developed Go - intended it to be a new home for C++ developers as well.
It isn't that hard to define something like MyType_t which is a pointer. Then some C code to allocate to wrap the MyType so the type is usable from any language.
> 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.
GC overhead is always hard to measure except end-to-end, because it is distributed over everything else that happens. Cache misses, TLB shoot-downs. Mitigations are very difficult to place.
Practically, you usually just have to settle for lower performance, and most people do. Double or triple your core count and memory allocation, and bull ahead.
I'm with you. One should look at latency requirements and the ratio of profit vs server costs when making a decision. AKA when your product generates $250k/month, you're paying three programmers $40k/month, and your AWS bill is $500/month isn't the time to try and shave pennies.
While some applications written in C++ are not performance sensitive, performance tends to be a major objective when choosing C++ for many applications.
My comment is about the thinking behind making this decision, C++ or not. It wasn't "is it speculative that GC will add a cost?" or something like that.
I wonder how much of the thinking that leads one to conclude "I need so much performance here that I can't afford a managed language", for example, is real carefully thought vs. speculative.
I think this might have been fixed in latest versions of Java, though, not sure if value types are already in the language or just coming soon.
Aside from that, it's my understanding that GC can be both a blessing and a curse for performance (throughput), that is, an advanced-enough GC implementation should (theoretically?) be faster than manual memory management.
There are a few different ways a GC impacts code performance. First, even low-latency GCs have a latency similar to a blocking disk op or worse on modern hardware. In high-performance systems we avoid blocking disk ops entirely specifically because it causes a significant loss in throughput, instead using io_submit/io_uring. Worse, we have limited control over when a GC occurs; at least with blocking disk ops we can often defer them until a convenient time. To fit within these processing models, worst case GC latency would need to be much closer to microseconds.
Second, a GC operation tends to thrash the CPU cache, the contents of which were carefully orchestrated by the process to maximize throughput before being interrupted. This is part of the reason high-performance software avoids context-switching at all costs (see also: thread-per-core software architecture). It is also an important and under-appreciated aspect of disk cache replacement algorithms, for example; an algorithm that avoids thrashing the CPU cache can have a higher overall performance than an algorithm that has a higher cache hit rate.
Lastly, when there is a large stall (e.g. a millisecond) in the processing pipeline outside the control of the process, the effects of that propagate through the rest of the system. It become very difficult to guarantee robust behaviors, safety, or resource bounds when code can stop running at arbitrary points in time. While the GC is happening, finite queues are filling up. Protecting against this requires conservative architectures that leave a lot of performance on the table. If all non-deterministic behavior is asynchronous, we can optimize away many things that can never happen.
A lot of modern performance comes down to exquisite orchestration, scheduling, and timing in complex processes. A GC is like a giant, slow chaos monkey that randomly destroys the choreography that was so carefully created to produce that high-performance.
What made it hard? For example, Rust seem to fit your bullet list.
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.
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.
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).
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
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
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.
A lambda needs a heap allocation only if any of its captures do.
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.
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.
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
So, backwards compatibility is there (if we ignore newly introduced keywords).
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"
This doesn't look very fun.
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.
https://www.joelonsoftware.com/2000/04/06/things-you-should-...
In just one notable example, a company I was at had a team develop an important platform in Node.js when the rest of the company was hired for and familiar with Java/Ruby. This app ran our 3rd party API gateway and was a central part of how our company was attempting to grow.
The Node.js team left wholesale to go found CockroachDB, which left nobody at the company who had the expertise to take over. You'd think that someone at a fairly large company would have Node.js experience, but it wasn't the case that we could staff the team back up easily.
There were several major production outages and the app lagged behind in development for the entirety of its life. We also had to port our protoc changes and traffic stack just to serve this one app. This despite being a central part of our upmarket strategy.
Ultimately it was completely rewritten. And nobody regretted that decision. We carefully weighed the pros and cons.
There's something to be said for a company that standardizes on one or two languages. Letting engineers have free reign leaves you with Haskell and Erlang littered in important places, with a very tiny bus factor.
But leaving things untouched is sometimes impossible or an even bigger drain.
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.
Also it’s just rewriting the logic. One could theoretically write unit tests for every function to make sure the inputs/outputs match.
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.
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)
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.
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.
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...
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...