The Design of C++ (1994) [video]
computerhistory.org
computerhistory.org
Really? Very little? Finely tuned fabric?
Is diamond inheritance really part of that fabric? And NULL?
C++ is old and has only accumulated, never lost. When I think "finely tuned fabric", I think of Rust, which has the fortune of decades of learning. C++ piled on rvalues and move semantics. Rust is the one that "tuned" its design.
I'm not dissing C++; it's a true veteran, and has witnessed entire languages rise and fall. But let's be honest: the design of C++ is (as one would expect for a decades-old language) organic, not "finely tuned".
Diamond inheritance isn't a problem in C++ as each inheritance path is followed separately. A small price to pay to enjoy the power of multiple inheritance.
And NULL?
NULL is deprecated since C++ 11 in favor of nullptr.
C++ is old and has only accumulated, never lost.
You may want to have a look at the latest standards.
Pedantically, it's discouraged, not deprecated. NULL is a preprocessor constant defined in so many places, often in contradictory ways. There's no practical way for the C++ standards to deprecate it.
Compiler and analysis tools vendors could (and should) provide warnings when NULL is used when nullptr is more appropriate (which is always IMO).
Seeing C++ codebases using 0 for pointers instead of NULL was already a sign how up to date the respective programmers were with C++'s best practices.
I wouldn't call C++'s design organic, just severly constrained by backwards compatibility. It is finely tuned, but the results are not even close to an ideal that didn't consider C. Finely tuned doesn't imply perfection.
When I try to be objective about it though, I'd say you are glorifying too much and skipping on the darker sides of it. You and me probably now exactly how to avoid those and that's also exactly one of the reasons why for us C++ is all utter greatness. But when I see inferior, dangerous, ugly, waiting-for-UB code produced by others I realize it's not all that great. Or maybe still great - after all you could blame their lack of knowledge instead of C++ - but I really wouldn't use terms like finely tuned or extremely logcical. Seriously, I've used the language for like 10 years and it still occurs to me that I see a C++ question and corresponding answer on SO which completely baffle me because I had no clue about that particular implementation detail. There aren't many languages like that. Here's one just from today: http://stackoverflow.com/questions/39422188/difference-betwe... granted I would never write that stuff, but also I really couldn't tell 100% sure how 'const int& x = 4;' at classlevel is supposed to behave.
template<class T,class...>class C{C<T*const,T,C>a;C<T,C>b;};C<int>c;
(c) Marc Aldorasi for (std::vector<std::pair<int, double>>::const_iterator it = v.begin(); it != v.end(); ++it) {
foo(*it);
}
The above used to be the recommended way to write a loop until C++11, and was supposedly superior to for (int i = 0; i < v.size(); ++i) {
foo(v[i]);
}
which was considered to be error prone and unsafe. But now things are much nicer and you can write for (auto x : v) {
foo(x);
}
Looks so much better. Clearly "auto" is a win for C++ programmers. But wait, it turns out that there's actually a bunch of different ways of writing the auto keyword that have subtly different semantics. You can write "auto", but you can also use every possible combination of "auto" with "const", "&" and "&&". So things like "auto&&" or "const auto&" are also perfectly valid and sometimes preferable to simple old "auto".Let's put aside the fact that you can't explain the difference between "*", "&" and "&&" to a C programmer without them bursting out laughing. Why does auto have six different variants? Isn't this emblematic of how C++ is evolving?
The fact that C11 made the security Annex optional, is a sign how much the C committee values writing safe code.
usually you'd use for(const auto& : v) though
I changed the initial use of loathe in this context to dislike, thanks for pointing that out.
What's less attractive is Dogmatic Bikeshedding C++ OO Fundamentalism.
Is that a thing? I always saw more of that in the Java world than C++ -- Java's standard library is rife with the singleton factory decorator monstrosities that have come to be associated with OO, C++ and the standard library have always felt more generic-programy. Now egregious use of obscure template meta programming tricks because of perceived (vs measured) performance gains, I've seen some horrors there...
I guess you missed the boat on CORBA, COM and DCOM, fun days...
J2EE architects were mostly former C++ architects that moved into Java land.
Also UWP APIs are actually the second coming of COM as .NET was originally supposed to be.
They had a golden opportunity to fix it, but even with C++17 and the expression "a(b(), c());", it is still left indeterminate whether b() or c() will be invoked first. Even between calls in the same program! So what if they have side effects? It might be a picosecond faster if the compiler can run c() first to push its argument onto the stack first!
I'm all for the power for C and C++ to optimize to such efficient code, and not consume many resources. But we're a long ways from the PDP-11 days and processor frequencies measured in KHz, and compiler developers are living in some alternate reality where the only thing that matters are benchmarks, and they'll happily undermine the stability and security of our software by doing things like erasing that call to memset() that cleared your private key from memory; or remove that conditional expression entirely because it detected the possibility of an integer overflow, so that means it can do whatever it wants! ... even though the world has been twos-complement for decades now.
Given the very real security concerns and exploits we keep seeing in code ... I don't believe that using languages full of undefined/unspecified behavior is the way to build stable and reliable systems.
Nobody can keep track of the hundreds of UB cases in C/C++. We all do it, and then suddenly a new GCC comes out with a new benchmark optimization, and now our programs are misbehaving.
I'm willing to pay a 5-10% penalty, and give up compatibility with the Motorola 68000 platform, to get well-defined and predictable behavior. Maybe you keep C/C++ for that gaming OS, or that Wall Street trading system. But on my server? I can spare the CPU cycles.
And yet, everything is built on C/C++. Your OS? That Python interpreter? Your web browser's Javascript engine? The .NET runtime? All C/C++ under the hood. We're building on quicksand; when we need a solid foundation.
You have it in specific compilers, not in the standard.
Not everyone is able to go installing clang and gcc on their work platform, when there are so many compilers to choose from.
https://en.wikipedia.org/wiki/List_of_compilers#C.2B.2B_comp...
But even moreso, yes!! I would love if we had a -fsafe directive that turned undefined behavior from "do whatever is fastest" into "do whatever is most expected." I would seriously start paying $100 a year to use such a compiler -- not even joking a little.
Unfortunately, as I've said, the compiler devs seem to have lost touch completely with the developers using the language and love playing these nasty games with UB. So I'm not sure how we can go about getting them to implement such a flag. And I just don't have the bandwidth to fork and maintain GCC or Clang to do it myself =(
Come over to the land of Rust, where you can keep your high performance memory semantics and safety :).
Seriously, life-long C++ dev here. Been writing C++ for almost 20 years now and I don't feel like there's anything I could do in C++ that I can't in Rust(both performance and functionality-wise).
Imagine you have a function like f(int, double). If argument evaluation order is fixed, you can no longer exchange the order of arguments: for all you know, someone might be depending on the first argument being evaluated first!
* constants
* regular variables that aren't using the cast constructor for implicit conversion
* member functions marked const
* constexpr functions
You would only need to actually evaluate function calls that may change global state, or that perform in-place assignment (eg f(x += 1, x += 2)).
I will take the performance impact of doing something 'stupid' like calling f(a(), b()) instead of splitting the expression up into three lines any day over potentially introducing a security vulnerability that I don't even know about and that the compiler doesn't warn me about any day of the week.
Performance is not the be-all end-all of the world. We should not make critical applications insecure in order to get our apps to be 0.0001% faster.
> If argument evaluation order is fixed, you can no longer exchange the order of arguments: for all you know, someone might be depending on the first argument being evaluated first!
... I don't understand how moving from arbitrary argument evaluation ordering to fixed argument evaluation ordering can possibly turn any valid code existing today into bad code. Quite the opposite, it has the potential to fix a lot of code. Anything that relied on the arguments being evaluated backward (I don't even know of a compiler that does that ... yet) would have been technically broken per the previous language specs anyway. So I don't know what point you are trying to make here, sorry.
* There's a function f(int, double), called in ~100 places, written by many different people.
* For some reason I decide to change it to f(double, int). Consistency, or preparing for some other refactoring, whatever.
* I have to track down ~100 occurrences of f and exchange arguments. Time-consuming but no big deal.
* If any code was dependent upon compiler silently evaluating the arguments in a particular way, then the code was broken, and reasonably competent coders don't write too much broken code. (Moreover, such a dependency is 99% likely to be broken by random changes in codes or compiler options, so chances are that I wouldn't encounter too many such bugs.)
In your world, it is just about impossible. If I want to go ahead, I could either change every occurrence into:
int arg1 = ...;
double arg2 = ...;
f(arg2, arg1);
...or pore through every line calling f to see if it's safe.C++ already has a reputation of being a difficult language to refactor, and your proposal will make it about impossible.
I am much more interested in predictability in my code than I am about reordering arguments in mature codebases.
Lots of languages guarantee expression ordering to follow precedence and associativity rules, and I've never heard anyone say that it was a problem to refactor their code as a result.
Whereas I am 100% certain that there are lots of codebases where this is a ticking timebomb with developers unintentionally relying on the order their compiler decides to evaluate expressions in. And it will blow up when, not if, the GCC devs find some micro-optimization and decide to reverse things on them. And that's not just a problem for them, it's a problem for everyone who relies on their code.
If the order of b() and c() matter, then they shouldn't be in the same statement. That is an abomination. What you little you gain in terseness, you more than make up for in future headaches.
int val = a(b(c(), d()), e(f(), g()));
Into this: auto _1 = c();
auto _2 = d();
auto _3 = b(_1, _2);
auto _4 = f();
auto _5 = g();
auto _6 = e(_4, _5);
int val = a(_6);
Is the abomination.Expecting every programmer to know that a(b(), c()); may call b() first or may call c() first is an abomination. Having a programmer's app work fine on most PCs, and then suddenly having a security vulnerability that takes down my server because a compiler dev decided to exploit this on my system in order to save two clock ticks on a 4GHz CPU is an abomination.
Here's a more tangible use case:
template<typename... P> void print(P&&...);
print("The value of: ", binaryObject.getNextToken(), " is ", binaryObject.getNextToken(), "\n");
Looks sensible, until you find out that the compiler decides to switch the parameter ordering and you get the value before the name.And it's not just me: the point of that PR was because people were doing exactly that with cout << next() << next(); and they clarified the rules so that was permissible. They just ignored function calls, so we are stuck using uglier than sin operator overloading of left-shift for our print statements, if we want predictable argument evaluation. Apparently the 'performance penalty' there is reasonable, so why not for function calls? So now we have to remember that in C++17, cout's ordering is left-to-right, yet print()'s ordering is effectively completely random.
auto key = binaryObject.getNextToken();
auto value = binaryObject.getNextToken();
print("The value of: ", key, " is ", value, "\n");
Of course if you are calling pure functions you can use nested expressions. And then the order of evaluation does not matter.No, I really don't. I don't want three lines instead of one line of code. And I don't want two named variables leaking into my scope. (I could encapsulate the block with {} here, but if print returned a value that I wanted to capture, then I couldn't do that.)
I'll stick by my original assertion. If the order of those invocations matter, then they shouldn't be on the same line, regardless of whether the actual behavior is well-defined or not. Just one example of what can go wrong: parameter reordering is often done automatically by tools, or manually by someone who is not intimately familiar with the code, such as six-months-in-the-future you.
Six operations with side effects--especially conflicting side-effects--belong on six lines. Six lines on your screen is not worth the days potentially spent searching for a future bug.