I don't get why people get so upset about C++ optimizations. They are by nature optional. If you think an optimization is dangerous, just disable it. That's why compilers have optimization levels, so you can select what you're comfortable with.
In my older compilers, I had a cornucopia of optimization levers for the various optimizations. I discovered that the number of people who understood how to use those levers, let alone were interested in them, or cared, was zero. Including myself.
People just want to "optimize" the code.
(Also, different optimizations can have surprising effects when combined that isn't clear at all from the individual levers.)
Regardless of optimization level, the effects of undefined behavior are not optional. Adjusting the optimization level of the compiler does not adjust the semantics of the language as far as the compiler is worried. If you're worried about an optimization changing the functionality of your program, you really should be adjusting the feature/dialect settings (for clang/GCC this means the -f flags, not the -O flags)
Exactly. It's become fashionable to decry the lengths to which C/C++ compilers are able to go in the pursuit of performance because of "safety" issues, but the advantage of letting the programmer fully control every knob in the chain is immense.
> but the advantage of letting the programmer fully control every knob in the chain is immense.
That's actually not completely true.
Take the restrict keyword for instance. Super painful to use in C/C++ and very dangerous. In Rust since the language has constrains around single mutable pointers they can turn it on globally and you get it "for free". If I recall correctly it's going live in one of the upcoming stable releases.
Sometimes constraints can actually let you get better performance than a wild-west of pointers everywhere.
> going live in one of the upcoming stable releases
If I remember correctly, it was originally enabled but had to be disabled because LLVM had some bugs around how it was handled. These arose because `restrict` is comparatively rarely used in C / C++ code.
Of all the sharp tools in C, restrict is one of the last things I'd call dangerous (restrict is not a standard keyword in C++, by the way, although most compilers support it as an extension). It's completely opt-in, relatively rarely used, and to even think of using it you have to be already thinking about aliasing issues. If you write a function using restrict and suddenly completely change your intent mid way, I really don't think that's a language design issue. Of course it's still a sharp tool and explicitly meant as such - as a last resort for cases where you can't rely on the compiler to infer the right thing on its own.
Sure but my point wasn't about the ergonomics of restrict, more that having a wild-west of pointers means that some performance optimizations are now off the table vs languages that are a bit more constrained in how they approach data access patterns.
They're not off the table, but you do have a point in that they are harder to attain reliably (hence the popularity of Fortran in certain circles). That being said, I don't believe there's any optimization that Fortran (or any other general purpose AOT-compiled language) can do that, for instance, C99 couldn't. That's why restrict exists.
There are two caveats to that statement. The first is JIT compilation, for obvious reasons. The second is that some domain specific languages with very restrictive semantics could do memory layout optimizations that are off the table by definition in any general purpose language (transform AoS patterns to SoA, etc.).
C++ is not "letting the programmer fully control every knob". Quite the opposite. Buggy, insecure, broken software is usually not the intention. The developer wanted one thing, and got something else, because of arcane details of the language. C++ is just a bad programming language, and "but it's powerful" is not invalidating that fact.
I certainly wouldn't call C++ a good programming language (rather, I would call it the only viable option for most of its usecases, badly designed though it is). However, I will stick by the "letting the programmer fully control every knob" phrase I used. I never claimed it makes it easy and you can make a lot of good arguments regarding better defaults, but it absolutely eschews restricting programmer freedom, at a not inconsiderable price.
Ada and Modula-3 are two examples of languages that also let the programmers control every knob, when needed, without exposing the traditional unsafety issues that C++ has inherited from C.
But alas, one was too expensive to become mainstream and the other died when Compaq acquired DEC.
I don't know enough about optimizing Ada code to comment competently on your assertion, but part of the reason it's very hard to compete with C/C++ is simply because of the immense effort that's gone into improving the compilers. These days the only languages that are remotely comparable to C/C++ in terms of performance directly benefit from that effort - they either use the LLVM (Rust, Julia, the LDC compiler for D, etc.) or transpile to C (like Nim). The only real differentiation comes from the semantics they expose and any additional machinery they offer on top of that stack (like the Julia JIT).
In terms of language features they are safe by default, but do provide enough escape hatch to make it as unsafe as C and C++.
The big difference is that you as developer will notice that you are doing something that might eventually blow up, because it is very explicit.
For example something like doing a reinterpret_cast would be LOOPHOLE call in Modula-3 and is only allowed inside an UNSAFE MODULE implementation.
As for the rest, yes C and C++ have enjoyed 30 years of compiler research, but as Fran Allen puts it, C has brought compiler optimization research back to the pre-history.
If you read IBM's research papers on PL.8, the systems programming language used on the RISC project, it already had an architecture similar to what LLVM has. Where the multiple compiler phases were taking care of the optimizations. Then when they decided to go commercial, they rather adopted UNIX as platform.
So maybe we would be somewhere else had AT&T been allowed to sell UNIX at the same price as VMS, z/OS and others.
As for the LLVM, we aren't going to throw that code away, but as you also assert, it is possible to use safer languages while taking advantage of the optimization work that went into LLVM.
That is how I see C and C++'s future, down into a thin infrastructure layer, with userspace mostly written in something else.
Allen's point about C's impact on compiler development is certainly true, but we can't turn back history. For better or worse, the reasons why C won (in terms of adoption) have mostly been compounded further over the years, and the case against it has weakened as compilers improved.
As for the future, I'm slightly ambivalent. On the one hand, common sense would lead me to agree with you that, with the exception of some numerical code (where I'm hopeful for further improvements in JIT compilation), most userspace code should be written in languages other than C/C++, retaining most of the efficiency of the underlying infrastructure. On the other hand, there are languages out there (like D, for instance) that essentially fix all the glaring issues of C and fail to get significant adoption. Weird as it may seem from the confines of the HN bubble, usage of C has actually grown over recent years and betting against C has historically turned out poorly.
You see it in Microsoft, Apple and Google desktop and mobile OSes roadmaps, where access to classical C and C++ code is being reduced to a funnel, with constraints what user code is still allowed to do with them.
It will be impossible task to kill C on UNIX clones and embedded due to culture and existing tools.
However the world of application development is much bigger than just UNIX systems programming or embedded development.
reinterpret_cast in C++ should be read by the programmer as "UNSAFE CODE HERE!!!" It is prone to aliasing issues and all kinds of other problems.
In C code, any cast at all, of any kind, should be viewed with the utmost suspicion.
That's why unions have been chosen as the correct way to alias pointers. Because it is unlikely that a programmer accidentally used the wrong type or that code changes made a type cast obsolete.
In Modula-3 and other safe systems programming languages all the way back to NEWP in the 60's, something like UNSAFE MODULE is exposed in the type system, whereas in C and C++ you have no idea what a library might do.
The reason C++ is badly designed is exactly to let programmers control how it operates. I don't subscribe to the philosophy, but I understand it. Trying to denigrate the language because of these choices is, in my view, absurd.
I might not code daily in C++ anymore, but it would be nice that the software I rely on, specially in devices I am not able to upgrade, wouldn't suffer from such issues.
Butcher knifes are deadly sharp, yet those conscious ones do use proper gloves when handling them.
The presentation in question answers this quite clearly. The changes in code produced by optimization can produce subtle changes in behavior. Having debugged opt only production issues (C++) I am well aware that you cannot simply "select what you're comfortable with".
In C++ there is no restrict keyword. So either you're using and abusing compiler-specific behavior that you decided to do so without any test at all or you're confusing programming languages.
Either way, you're trying to pin the blame on C++ for something that has nothing to do with C++.
One downside to disabling optimizations is that it confuses the meaning of "valid C++". If your code is only correct with optimizations X, Y, and Z disabled, is it "valid C++"? Or is it "invalid C++, but valid C++-noX-noY-noZ"? Do the definitions of X, Y, and Z necessarily tie your project to a specific compiler?
The compiler does not necesarily dictate what is valid c++. If code doesn't work with optimizations enabled, it could either be a bug in the code where its expectations coincided with the compiler's behaviour on something that is undefined in the language when optimizations were turned off or it could be a bug in the compiler where it applied the optimizations incorrectly.
There is no "invalid C++, but valid C++-noX-noY-noZ". It's one or the other. The former is a bug in the code; the latter is a bug in the compiler.
That's not a hard question. If your code only works without certain optimizations, it's either not valid (as in, standards-compliant) C++ or you've found a compiler bug. That doesn't necessarily make it bad - you may have a good reason to write non-portable code for a specific platform and a specific compiler, but it's not conformant to the language standard.
You guessed wrong. I have supported large C++ codebases and while C++ is not an easy language, it gives you the tools to do what you want in millions of different situations. This diversity seems to be bad until you really need something that the language provides. That's why C++ is, and will continue to be, used in so many environments.