I have nothing against C++ per se. Used it on and off for 10 years (but most actively 3). It's a fine language.
To me using C++ means you are ready to give up a lot of time and energy in order to gain a complete control over certain aspects of your program. I was very much into that at the start of my career and gradually started drifting away to more immediate productivity while reserving the right to poke under the hood when necessary. This has led me to Rust and I find it to be the better natively compiled strongly & statically typed language, but YMMV of course.
These days I reach for dynamic languages for personal projects and experiments. I want to sketch an idea and see if it works in the interval of 15 minutes to a day. Languages like C, C++, Rust, D, Nim, Zig, Fortran, Pascal, Haskell, I found to be generally terrible for when I am in a flow state. Using Elixir / JS / OCaml I found that I could iterate much faster.
I never will participate in a language war (although all of us have slipped on that ice more times than we'd like to admit, let's be honest). But to me C++ is simply not productive. It feels like having to learn a 10x5 meters of control panel with dials, blinky lights, keys and levers. I know there are many who enjoy that -- more power to them. But I am not in that group.
I used to work with C++ almost exclusively, but accepted to use more interactive languages for most of my work mostly for faster iteration. C++ is definitely not my main choice anymore, but still use it daily.
C++ has pretty much unmatched tooling due to the massive ecosystem. I have my own long list of gripes against it's syntax and historical baggage, however it's a language that doesn't really impose a style/idiom like several other modern/newer languages do, making it vastly more applicable from anything from embedded to large-scale programs without restrictions. Modern C++ has smoothed a lot of the rough edges in terms of productivity, but it's still an overly complex language that cannot shed any weight due to backward compatibility, and it's a shame.
Contrarily to many other guys tough, I'd take a dash of C++ compared to "simple" C/C99 any day, especially in embedded. C++ can be vastly simpler and more readable, while retaining 100% control over memory and layout. Most of the memory/threading issues essentially disappear with very little fanfare, but few people talk about that.
I don't want to detract against static checking though! But I feel like newer languages such as Rust are still too young and idealistic, and haven't been beaten by implementation requirements or a system's programming language yet where your silly requirement is somebody else's essential feature. This is how it gets ugly, and this is where C++ has probably something to say.
I still like modern languages though. I like Rust, but won't be able to use it effectively until they get rid of static compilation. I wish I had D's metaprogramming over C++ templates any day, but the GC is a again non-starter. Nim has no static checking, but it should deserve much more consideration than it currently has: it's much more pragmatic than Rust IMHO, and it's also much easier to integrate into existing programs will less hassle.
I worked mostly in C++ for over 15 years. I was totally proficient in it, and very productive, especially after c++11. Often I would reach for C++ even in favor of Python for small throw-away tools, in the knowledge that it wouldn't bail out on a typo in the final print statement after 10 minutes of analysis.
I have now worked with Rust for a year, and I don't see myself ever going back to C++. The statement in the article that "only the final iteration would be unsafe if one would forget to check the length" made me cringe uncomfortably. I might have written similar code as the author, but it now looks clumsy and overly complicated.
Fair and accurate points. I admit Rust needs to sort some kinks out still but it builds up on the experience of C++ and I believe it's already heading in a very productive direction. I love what they're doing with their async stack, being just one example.
I can't deny compiler and linker speed in general are better in C++ land but Rust is gradually catching up (too slow for my taste still but sigh). That's a very strong point against Rust to this day.
You might be right that compromises for running in embedded environments might load Rust with the same ugly historical baggage as C++. That's very possible and I can only hope it won't happen, but you do still have an excellent point about the genesis of these problems.
D and Nim I admittedly didn't evaluate very fairly -- gave them half an afternoon each. They were quite nice in fact, but I found the small ecosystem off-putting, especially in a situation when I want to play with an idea that might require libraries for 5+ well-established technologies. So yeah, I wasn't very fair to them but didn't want to dedicate much time for prototyping ideas regardless.
> C++ can be vastly simpler and more readable, while retaining 100% control over memory and layout.
But unlike assembly, you don't have control over CPU flag registers and special instructions. Can you explain why and when, exactly, 100% control over memory is needed? Outside of OS kernels, high-frequency trading engines, and real-time control systems? If using assembly is too costly, why is using C++ economical?
And, do you really have control what happens at the machine level? Second and third level caches, hyperthreading, multicore, out-of-order execution, CPU affinity, NUMA, TLB, virtual memory, all that stuff, there is nothing in C++ which controls that.
And I would even argue that this in most cases is a good thing, because unless you are writing an OS kernel, it is none of your programs business - what it should focus on is the logical flow of the computation.
And this is what modern optimizing compilers do: They transform expressions into machine code which has an equivalent observable effect, and executes that what the programs specifies as fast and efficient as possible. Fi you write:
int a = 0;
for (int i=0; i < 100; i++)
a += i;
printf("a = %i\n", a);
you could be tempted to believe that the CPU executes increments on a 32-bit or 64-bit register. This is not what happens on a modern optimizing compiler. Look at that link (which uses -O3):
- the compiler reduces it to a single move and return instruction. This is not specific to C++ compilers - a good Lisp or D or Ocaml compiler will to the same, while managing the memory for you.
Now, control is, as in psychology, a double-edged sword. It allows you to take influence, but too much control is not a good thing, as the example of micro-management shows. In the case of the compiler, if you control too much, it interferes with the compiler's job to transform your expression into efficient equivalent machine code. The fine-tuned code you write today might be optimized for some of the modern hardware, but while it will probably still compile, it might be much less than optimal 15 years from now, and in the case of C++ without the compiler having any leeway to optimize it because you told it way to explicitly what to do.
It also interferes with your job to produce clear, valid, and readable algorithms and code. And the numerous controls which C++ gives make for a very broad and very fuzzy interface, which in turn makes it more difficult to produce good code and hard to do that in a way which is both reliable, strictly valid, and easy to understand. The issue with non-ASCII characters in words in the original article is a good example. Here is another one:
Take
include <vector>;
include <algorithm>;
std::vector<bool> vb(100);
std::fill(vb.begin(), vb.end(), false);
"Since its representation may be optimized, std::vector<bool> does not necessarily meet all Container or SequenceContainer requirements. For example, because std::vector<bool>::iterator is implementation-defined, it may not satisfy the LegacyForwardIterator requirement. Use of algorithms such as std::search that require LegacyForwardIterators may result in either compile-time or run-time errors. "
You don't have direct control over caches, however if you have control over memory layout you can indirectly influence how the code performs by leveraging the proper cache sizes/lanes. If that's your goal, you cannot focus on program flow alone, and that's the main point.
Low-level and high-performance code is not "pretty" by modern standards, as it often requires DMA and tight control over data placement in order to fully leverage instruction-level and hardware-level parallelism. The hardware influences how the data structure layout should be first, and we work on top of it. We abstract structs and containers that capture this layout, so that we can still have a readable program flow at the end.
In this sense, C++ can still be used as a glorified assembler, without the need to drop down to ASM for the simple stuff as your for loop: nobody wants to do that (me included). But contrarily to C, you can avoid a ton of preprocessor macros and get improved type checking, while still using ASM in selected spots if needed.
Your example about vector<bool> is not really surprising. Do you want the iterator to be efficient, or consistent? Tough choice, depending on the scenario. It's annoying that it's not standardized in one form or the other. I remember I was fretting over this detail over 10 years ago, but in actuality I used vector<bool> exactly 0 times in my career so far.
Again, please don't consider this as if I was praising C++. There are _many_ areas, including lack of standardization in the stdlib area (vector<bool> being one of many), that I don't like. I just want to say that it's an incredibly versatile tool which is very hard to replace given the same constraints.
> Low-level and high-performance code is not "pretty" by modern standards, as it often requires DMA and tight control over data placement in order to fully leverage instruction-level and hardware-level parallelism.
This is true if you look at implementations like these:
I'd say the C++ code in this particular case is not only much longer but also uglier.
However, how much of a fraction of C++ code in use does really require direct control over DMA calls? If you could get an at least equally fast result as when writing "normal C++" in Rust, with more safety and less effort for debugging, would this not be tempting for many people using C++? Because this is what I observed in my case. And this given that I've written no more than a few hundred lines of performance-critical code in Rust, and have worked for > 10 years on performance-critical code in C++ and C.
> Your example about vector<bool> is not really surprising. Do you want the iterator to be efficient, or consistent?
The thing is, what cppreference says here is that this innocent-looking code might either not compile or cause undefined behavior in whatever part of the program. In my case on GCC 8, it caused memory leak warnings with address sanitizer. For larger programs and especially for programs which have hard requirements on real-time latencies, safety or robustness, this is not acceptable.
And yes, an experienced C++ developer will initially need a little more time to write the same code in Rust, compared to C++. (I do not think this is true for developers new to C++ because C++ is a very large language). However, in any larger program, the time spent for testing and debugging is very likely larger than the time spent for typing in the first code. And in this metric, Rust's strict approach to correctness is far better.
And if you get to debug undefined behavior in large multi-threaded programs, the time you can spend debugging code is basically unbound if you were were not both experienced and careful with writing first.
I find it very interesting that you put Haskell in one camp, and OCaml into the other. I am more of a Standard ML guy instead of OCaml, but can you elaborate a bit why you feel you can iterate faster in OCaml than in Haskell?
OCaml is quite a lot more pragmatic and 'loose' than Haskell. But that's just my impression. They even have for-loops in OCaml..
(I would be really slow writing code in vanilla JavaScript. There's no static checking, and not much dynamic checking. So you need to watch your step very carefully---in some sense, very much like in C++.)
> OCaml is quite a lot more pragmatic and 'loose' than Haskell. But that's just my impression.
Mine too. I still dislike the lack of Unicode strings by default, and the somewhat confusing tooling (initially) but outside of that I quite love the language. It has a huge potential and I can only hope that the arrival of Multicore to the upstream language will kick it forward and put it in the same league as Rust (although due to GC it can never be that fast I'd think; but who knows).
> (although due to GC it can never be that fast I'd think; but who knows).
Well, in theory GC can be made very fast. Though you sort-of have to decide whether you want maximal throughput or real-time latency guarantees.
In practice, we aren't quite there yet, alas.
The way to get really fast code is just to avoid allocation, I'd guess? OCaml allows you to use mutation, and it's relatively easy to peak under the hood to see what your code gets translated into.
The main technical innovation of Rust is the borrow-checker.
But that piece is very related to linear types / uniqueness types. There's some interesting work going on with linear types in Haskell.
And just like Generalized Algebraic Data Types also eventually made their way to OCaml, there's no fundamental reason they couldn't add linear types to OCaml, I guess?
(Linear types can basically be used to encode the FP equivalent of manual memory management.)
One of the issues with this comment is that, while it aims to elaborate on certain aspects of C++ that make it unattractive, it tacitly promotes the idea using C++ gives a person "complete control over certain aspects of your program". Given the intent of the message, this is actually kind of harmful. There's a certain class of chest-thumping/-puffing programmer who sees this as vindication for the language. So, perpetuating that idea is bad news, especially since it's only true if applying very, very loose criteria of what it means to give a programmer "complete control".
This is what I heard several seasoned C++ veterans claim and so far I didn't have a reason to doubt them.
As already stated, I'm not a fan, but apparently a lot of people out there manage just fine with C++ and claim that it's actually easy and possible to attain complete control.
I'd imagine that could be somewhat true if you invest decades in that language.
It isn't true unless that investment includes tons of bespoke and unrealistic compiler work and an open side channel (e.g. in the form of command-line flags to the compiler) to get it to lay down exactly what you want it to.
When people write those kinds of things, they're being hyperbolic. It's best not to assent, in light of the chest thumpers. Complete control just isn't possible in C++. More control than many of its contemporaries, yes.
I general I agree with your broader point about when to select dynamic language, flow state etc., and specifically with C, C++, Rust (although of those I haven’t done C++ in years except to tweak others’ code). However I find D can be written in a very script like manner, using the GC. Only when chasing performance do I need to go GC-free. The fast compilation means I can iterate as fast as python, and it has replaced a lot of python in our group.
Side note,I would like to learn OCaml and am interested you drew a distinction between Haskell (former group) and OCaml (latter group).
OCaml and Haskell share a common ancestry, but I would also draw a distinction between them. (Though not the same that the other commenter did.)
OCaml is a bit more of a drop-in replacement for something like C++ or D. Mostly because it's strict by default, so it's easier to reason about performance even for a non-expert user.
OCaml's approach to side-effects has now entered the mainstream: it's considered good style to avoid them, but they are still used pervasively. That's very similar to what good modern C++, Java, Python etc style advocates.
Of course, Haskell also allows side-effects. But generally, you track that they are occurring with the type system, you generally stick them in some kind of Monad or Applicative Functor.
(EDIT: I misread your question, apologies. OCaml vs. C++ is mostly because of much less surprises to start working with it. OCaml vs. Rust is mostly because Rust mandates you to pay attention to a lot more initially (`Result`-s being a prominent example, although using `.expect()` or `.unwrap()` actually helps a lot) and even if I absolutely love that you have to iron-proof your code, it does get in your way when you just want to try something quickly. It kills the motivation intrinsic to the flow state.)
---
(Original reply:)
I'll admit it's not a factual and clear-and-cut distinction.
OCaml has warts (no multicore, although that's claimed to be very close to complete now; also no UTF-8 strings by default, and a somewhat surprising tooling, the first time at least) but I found Haskell to ask too much of me to even start -- I almost immediately grokked monads and lazy evaluation but it seemed to be too much effort for something you'd like to quickly play with. And when I saw the list of all compiler variants I almost gave up right there and then.
OCaml, as mentioned in the parentheses above, isn't without problems too but I found that I could get to a working dev stack and the ability to make a quick program and run it, much quicker compared to Haskell. Also, it's very terse.
Not the most compelling or factual of reasons but this is how I arrived at that place where I prefer OCaml for sketching ideas and not Haskell.
Similarly, Erlang (and thus Elixir, I guess) is also comparatively small and simple.
Surprisingly, C would be in the same category, if it wasn't so primitive and likely to make you shoot yourself in the foot.
I've programmed professionally in both OCaml and Haskell (and Erlang and C etc). I find that Haskell is generally more terse than OCaml.
They are both fine languages. Subjectively, Haskell feels a bit more fun to me. But if you want to really get into it, it helps to have some people around who know what they are doing.
Interesting how different experiences with the same thing can be! :)
> it helps to have some people around who know what they are doing
I think that's the crux of my issue. OCaml wasn't the easiest to start with either but it took me 2-3 casual evening sessions to just be able to sketch code and run it immediately with almost no hassle.
But yeah, can't deny Haskell kind of rubbed me the wrong way so I am likely biased now.
It does feel fun to bust out a few Project Euler challenges with OCaml; and as a reasonable experience programmer you should be able to do that after those 2-3 casual evening sessions.
It's been a few years since I last did OCaml. At that point, the ecosystem was a bit more mature for Haskell. More libraries, better editor support, etc.
In some platonic sense I like small languages, but I had come to appreciated all the extra comfort Haskell provides. OCaml was just too much in the uncanny valley of being almost Haskell, but then missing some nice-to-haves. I might grumble, but OCaml is a pure pleasure compared to C++.
Btw, they finally added (optional) stacktraces to Haskell a few years ago.
About all the language extensions in Haskell: I like them, but they can be overwhelming. Luckily, the language extensions your libraries use has almost never any bearing on what language extensions your client code uses.
So you can ignore the extensions, and still use your favourite libraries.
To me using C++ means you are ready to give up a lot of time and energy in order to gain a complete control over certain aspects of your program. I was very much into that at the start of my career and gradually started drifting away to more immediate productivity while reserving the right to poke under the hood when necessary. This has led me to Rust and I find it to be the better natively compiled strongly & statically typed language, but YMMV of course.
These days I reach for dynamic languages for personal projects and experiments. I want to sketch an idea and see if it works in the interval of 15 minutes to a day. Languages like C, C++, Rust, D, Nim, Zig, Fortran, Pascal, Haskell, I found to be generally terrible for when I am in a flow state. Using Elixir / JS / OCaml I found that I could iterate much faster.
I never will participate in a language war (although all of us have slipped on that ice more times than we'd like to admit, let's be honest). But to me C++ is simply not productive. It feels like having to learn a 10x5 meters of control panel with dials, blinky lights, keys and levers. I know there are many who enjoy that -- more power to them. But I am not in that group.