There is one real problem with "modern" C++ that I would have hoped they would focus on: Compile times.
All the new stuff is heavily template based. As more libraries are written in that style, compile times go through the roof.
I wrote some code recently that required high precision monetary calculations. So I used boost multiprecision. It turned out to be completely impossible as most compilation units ended up depending on that library directly or indirectly.
Already slow compiles became unbearably slow. Facing a tight deadline I quickly wrote an extremely ugly, slow, error prone, pimpl based wrapper around boost multiprecision. Many calculations now require dynamic memory allocation for no good reason.
This is just terrible, because I know I can't leave it like this. I'm going to have redesign the whole thing or look for other libraries or look a lot more deeply into the calculations to see if they really cannot be done with large integers.
It's a productivity disaster. You could say I made wrong choices, which is true, but I think the C++ standards committee has been making wrong choices in not prioritizing a fix for compile times (modules?).
What's the point of having nice new features if you end up spending half your time on carefully cordoning off any code that uses them?
I'm fighting with this daily. Working on a medium size desktop software application, seeing compile times increasing in the past months as developers use modernist C++.
Past a certain point, long compile times make it harder to improve a large code base. People work around it by adding more stuff at the leaves and never fixing fundamental parts of the code base, avoiding to touch heavily shared code. This is almost a guarantee for another increase in compile time (because you end up accreting more stuff) which makes it even harder to work on the code base at large scale.
And that has I believe a concrete effect on the quality of a piece of software.
The problem is, there are some low hanging fruits (like caching, parallelizing and distributing builds) so people think they can delay the inevitable. The problem is, those counter measures only deal with the average case, while doing nothing about the worst case.
Modules are the answer, but it seems nobody cares enough to get them in the standard or, more likely, it's just too hard a problem.
Having looked at the clang modules implementation you basically have to clean up all the system includes, the standard library and your own headers before you can use them. And you will have to rewrite many things in these headers due to changed preprocessor semantics.
I think neither the clang nor MSVC implementations can be used for anything but the most trivial code due to these issues.
There are talks from Google who invested heavily into modules using clang and got gains, but as far as I understand they fixed those issues only for themselves.
More info welcome.
At this point the only cure is brute force (blades with many cores are not that expensive, or a distributed compile cluster) and always taking care with your includes.
> Modules are the answer, but it seems nobody cares enough to get them in the standard or, more likely, it's just too hard a problem.
Aren't modules especially limited with the mentioned problem of templates?
If I have a vector of a custom type, I still have to instantiate it in every file that is using this type, I don't think modules help much here, they reduce the amount of time parsing, not instantiating if I understand correctly.
We'll need to significantly change the way we handle templates in compilers to get significant improvement.
"There are talks from Google who invested heavily into modules using clang and got gains, but as far as I understand they fixed those issues only for themselves"
I thought Google fixed the C++ compile time problem by inventing Go?
If we're talking about Google, I think the number is at least one order of magnitude larger. A year ago Google estimated it at 2 billion lines, though that appears to include all languages (not just C++): https://www.wired.com/2015/09/google-2-billion-lines-codeand...
No they invented their own build system and tools (Bazel). Although some of the tools are open source the true distributed compile scaling stuff I think is still not.
Libstdc++ is normally distributed with the compiler, so it will be updated as required by the standard.
Regarding the libc and other headers, modules will still fullly support include files, so that won't be an issue. You should be able to include headers and export them as modules.
Kernel headers are only an issue on free UNIX like OSes, on other OS what matters is what the SDK provides.
So at least in what concerns Apple, Google, Microsoft and every other commercial vendor OSes, don't expect a big issue having their SDKs updated when they start supporting modules.
The problem is getting the BSD, Linux and open source libraries without a specific agenda to adopt them.
Clang uses "module maps" to turn existing header-based libraries into modules. So, theoretically, your system compiler could include a module map for all of the system headers. I don't know if module maps are going to be part of the standard, though.
> There is one real problem with "modern" C++ that I would have hoped they would focus on: Compile times.
I found a really simple solution to this: disable all optimizations while you are developing and only use them for production build. I have 70k LOC project that compiles from scratch in 10-11 seconds (using 8 parallel builds).
Regular changes to 2-3 .cpp files and link are done in 1-2 seconds.
This is esp. important with MSVC which has a huge difference between non-optimized and optimized build.
Sure, but in a "modern" codebase where you're template-everything, then more changes will be in headers, resulting in a lot more translation units being affected by any given change.
I agree, the compile times are my mine problem with C++ right now, and they are not getting better.
If you listen to the C++ advocates, they all say use the STL as much as possible, use boost if there is solution for your problem there, etc.. It's true, you probably will quickly get a solution that works and looks pretty, but if you do this a hundred or a thousand times your code suddenly needs several hours to compile.
I am not against the STL, or boost, but there is a cost associated with using them that is often ignored, but IMHO is actually a productivity killer in codebases that are a bit bigger.
While Boost often implements the latest proposed standards and provides a vast extension to the STL, it is not the STL. Often proposed standards changes are best implemented by changing the compiler itself. Expecting Boost to provide the same functionality as a modified compiler without a cost is foolish.
Boost provides great implementations but at a great cost in complexity, compile time and binary size bloat. I don't even really believe modules will solve this. They push the boundaries of what is possible with today's equipment, in order to discover what the next equipment should be.
Also I no longer look to Boost libraries as a Seal of Quality. There be dragons. Choose wisely.
To speed up parallel compilation, use icecc. It's great if you half even two or three computers on your LAN.
For high precision currency, use int64_t (perhaps in a simple POD class). This, with an 8-digit implied decimal part, is enough to represent the finest price unit in any market today, with a max of 92 billion.
If you are dealing with more than 92 billion of any currency in a single place, don't complain!
After years of hyperinflation, Zimbabwe's currency was entirely discarded last year. The final exchange rate was 35 x 10^15 Zimbabwean dollars for one USD:
"Bank accounts with balances of up to 175 quadrillion Zimbabwean dollars will be paid $5. Those with balances above 175 quadrillion dollars will be paid at an exchange rate of $1 for 35 quadrillion Zimbabwean dollars.
"The highest – and last – banknote to be printed by the bank in 2008 was 100 trillion Zimbabwean dollars. It was not enough to ride a public bus to work for a week."
I guess you could move the decimal point a bit and just add zeroes on output for that particular currency. [Not serious, this is a strong argument for int128 for currency... wow]
Gas stations in the US had a similar problem in the 1970s; most pumps didn't go over 99 cents a gallon. Took a few years for things to be retrofitted.
A large swath of the finance industry (especially trading) is concerned with optimal speed, not outlier edge cases. They would rather ignore pathological cases like the Zimbabwean currency than sacrifice performance.
Of course overflow and rounding error must be accounted for in your data type. Exchanges have price boundaries, price quanta, or risk boundaries that would guide this decision. My point is that you would not rule out a data type for trading based on an obscure currency like the Zimbabwean dollar.
If you can uses gcc you can use _Decimal64 or _Decimal128, I have a sqlite3 fork that uses it for currency precision calculations.
https://github.com/mingodad/sqlite
True. Also, the smallest unit in which prices are quoted is not in all cases sufficient for calculations. A million + 1 rounding errors after the 8th decimal place adds up to an error measured in cents.
In my particular case, 64 bit integers might be sufficient. I thought about it but then decided I wanted to be on the safe side. Maybe not the best decision.
Not sure what sort of assets you are looking at but prices are rarely quoted with a very large number of decimals. Having chunky increments is sort of necessary to match bid and offers on markets.
Sure, but division and multiplication add more decimal places. If you do a lot of calculations and you truncate or round every single intermediate result, rounding errors may add up to unacceptable levels in some cases.
So I wanted perform the caculations with high precision, then round the final result according to what the law requires.
> Having chunky increments is sort of necessary to match bid and offers on markets.
How so? If there's a resting offer at 11.58726 and I put in a buy order, limit 11.63492, there's no reason an exchange can't match that and execute either at the midpoint or at 11.58726 +/- maker/taker (whichever the exchange policy is). What's the problem?
I'm not an expert on exchanges but my understanding is that the transaction is executed if the two sides of the order book match or the bid exceeds the ask. If you allow for a very large number of digits in the price, you reduce the probability there will be a match because of a tiny difference between the bid and ask. Won't be a problem on the most liquid securities but more of a problem on less liquid ones.
My understanding is that all prices on exchanges have a fixed number of decimal (and sometimes are even based on less granular base, like 1/8th of a decimal).
If the bid exceeds the ask there will be a trade; if not, the order will rest on the book. That's entirely normal, and a smoothly-functioning exchange should allow granular enough pricing that there would be a clear spread - when the spread is the minimum increment possible (as it usually is in equities markets - the offer is almost always just $.01 above the spread) that is a sign that there are trades that could be executing but aren't (e.g. if the bid is at 5.23 and the offer at 5.24, maybe both parties would be willing to trade at 5.233).
There's an argument that more granular prices make it harder for humans to read the order book but I don't put much faith in it.
In Microsoft-Land, I find that every time we force ourselves to upgrade to the latest compiler (really entire IDE, Visual Studio) then everyone also needs to upgrade their machines. Both the IDE and the C++ compiler run dog slow on "older hardware". We're talking new-ish microarchitecture, faster RAM, and SSDs, to keep things sane. This is just one anecdotal use case of course.
Though I'm curious as to what you mean by "unbearably slow", and what your toolchain is.
I have found the latest update (VS 2015), were Microsoft significantly invested in the compiler after years of stagnation, to bring better compile speed in addition to standards support. There were also quite some bugs in the codegen, but it seems those are never avoided when working on a C++ compiler.
I find the IDE to still be sufficiently fast after disabling some things like Git integration and codelens.
There are various solutions to compile times in C++ that have been talked about at length over the years. Lakos' book from 1996 talks about this.
In fact, a lot of modern software engineering guidelines come from working around C++'s built in shortcomings.
Use things such as the Pimpl idiom to help linking. Modular libraries rather than big-ball-of-mud, do you really need to recompile world+dog each time? Top-down includes, and no relative includes, isolating your code. In fact go-lang does exactly this.
The trouble is that in C++, fast compile times need to be designed into the code base and often enforced by scripts. I don't think that there's any other language that requires so much 'non-language' engineering as C++.
what is unbearably slow ? an hour ? a day ? how many times did you actually have to rebuild the entire code base ? Did you do parallel builds, or see what gains you could get from faster hardware ?
I'm genuinely curious as I've never worked on a codebase that large, and the only time you have to rebuild everything is when someone changes a common library header.
holy cow, I never would have thought in 2016 compiling a C++ program could be so slow that it makes it impossible for someone.
Can you put numbers on these figures? By "Unbearably slow" how many minutes (or hours) did you mean? Do you use a heavy desktop with a fast SSD and a lot of RAM?
Finally what is the total output program size (roughly). Like are we talking about an application that is in the gigabytes, like the complete Adobe suite or something?
I would like to know more information about how it's possible for this to happen. tell us as much as possible :)
As others have mentioned, I've resorted to brute force (parallel compile using 16 physical cores, good SSD, 32GB RAM). I have a 20KLOC library that I wrote myself (very few templates), that takes 20 sec to compile in parallel, meaning in serial it would take about 5 min. Then there is a giant 3MLOC system I work with (much templating), that takes 20 min to compile in parallel (consuming 15GB RAM), meaning in serial it would take 6 hours. One can also see in this 3MLOC system that it is the last few small files that take the longest to compile, because they include much of the rest of the system as template code in headers.
Preprocess your source 'cat' the results together; then, hand off to the compiler. On a 2015 MacBookPro I've seen upwards of 100kloc/s. Probably average 30--60kloc/s. With ccache my 5mloc code base takes as little as 300ms to do incremental compile & relink.
Another thing is to rely on the preprocessor for code generation; then you're dealing with memory-speed elaboration of the AST, rather than reading off of disk.
I did the single file trick for the small library that I control, as a configure option. It definitely does help, 2X speedup in serial compile time typically. however, you lose the ability to compile in parallel, which with my hardware gives a 16X speedup, so for now parallel compiles are the winner.
Something smells with the structure of the project if a small change in a single file triggers a long rebuild. Including headers that define template should be "free", it is costly when you start instantiating all of them. There might be opportunity to restructure some part. Also precompiled-header or better: modules can help (not much with template instantiation though).
In my parent comment are listed compile times from scratch, although there probably exist one or two files that would trigger similarly long rebuilds (included indirectly by most other files). If you change a random line of code, chances are the rebuild is quite limited. As others commented above, this actually scares people away from improving the core files.
So the major component of a modern C++ application that contributes to long compile times is templates.
In C and "older" C++, you're probably used to doing a particular style of separate compilation. You put your prototypes in header files, your implementations in cpp files, do the equivalent of `gcc -o somefile.o somefile.cpp` for every cpp file in your project. Each of those separate compilation stages has to include any referenced headers, but then it just compiles the code in that cpp file and writes an object file. At the end, you do one more step to link all the object files into a binary executable. This is fast and as parallelizable as the number of files you have.
In C++ today, you're likely doing a lot with templates, and templates don't work this way. With templates, every compilation unit that references a template needs not just declarations from a header file -- it needs the complete source code for that template. You essentially have to #include the cpp files (through one mechanism or another).
Simplifying a bit, that means that if you have a template defined in one file that is used by code in 25 other files, that template gets compiled 25 different times, and worse, you lose out on a lot of parallelism because by the time the compiler sees the code, the preprocessor has expanded the templates into one giant compilation unit that's going to be (slowly) compiled on one core.
I just grabbed the code I wrote during my PhD thesis that was heavily template-based. It's tiny (about 15,000 lines of C++) and building it on this $3000 2015 Macbook Pro took about a minute, and clang++ used a bit over 800 MB of memory in the process. (https://github.com/deong/sls if anyone cares to see the example).
You certainly don't have to be building the Adobe suite to see compile times in the tens of minutes, and even moderate sized applications can put you into the hours. So much just depends on how the application is structured and written as opposed to only looking at size.
I can't put numbers on it as the codebase no longer exists in that form, but I can tell you what I consider "unbearably slow".
"Unbearably slow" is when the compiler takes too long for me to stay focused on a single task. So compile times in the minutes are a blow to my productivity.
The machine is i7, 4 cores, 8G RAM, SSD. Definitely not as beefy as it should be.
But my point isn't about how to optimize C++ compile times by adding more hardware. It's about the relative impact of "modern" C++ compared to traditional C++. There shouldn't be a disincentive to using modern libraries.
I dug out some 5-year-old C++ code recently, about 20kloc. I was expecting it would compile instantly on modern hardware like my Surface Book. It's taking about 12 minutes to build on travis with some of the worst offenders disabled (the SWIG-generated bindings were particularly slow IIRC), and a local build feels like it takes longer: https://travis-ci.org/PembrokeNS/sakusen
To piggy back on this comment: what build system are you using? I've found that bazel has greatly reduced compile times, even when using boost and opencv.
I have used qbs with great success. It's by far the fastest and most correct¹ build system for C++ that I've used so far, and it's configuration ain't so bad (tm).
¹ I used it in a couple projects and never had a single incorrect build. With most other build tools, be it CMake or Gradle, these happen regularly for me.
I just took a very quick look at Bazel and saw that Windows support is "Highly Experimental" which concerned me. I've been looking around in this space recently as I'm getting into C/C++ again (ARM uC stuff) and all three platforms are fairly important to me for what I'm doing. I'm a big Gradle fan and Bazel seems like a natural next step if it's not problematic.
More features, more libraries, but is there a working group that looks back at C++98 and tries to reduce complexity by removing mistakes from the language?
My pet peeve is Argument-Dependent Lookup. It's a hack that breaks the encapsulation of namespaces, increases compile times and still makes it harder to write code - try swap()ing two values of type T in a template function. But like any of C++'s warts, it will be with us forever.
>More features, more libraries, but is there a working group that looks back at C++98 and tries to reduce complexity by removing mistakes from the language?
Then you break backwards compatibility. What about static analysis to enforce a particular subset of features deemed idiomatic or otherwise best practice?
Granted, reducing complexity does make the job of future compiler authors far less miserable.
...Yet I am not fully convinced that the general idea of language evolution implemented as ruleset outside of the language itself is the way to go. Seems more like band-aid to me and made me start to think about concept of programming language's life cycle in context of Darwinian evolution - once either legacy compatibility burden is too big or that particular biotope of specific problems the language solves better than others vanishes/changes the language dies out.
My particular issue with C++ is that it's complexity, longevity and evolution made possible for widespread bad-practices as not every C++ developer evolved as much as the language itself and it's general public understanding. (Some current best-practices were simply not always widely known or accepted.) I would even go as far to say that most of existing C++ code is crap (at least from standpoint of today "modern" C++).
And that's why I am both eagerly looking to "next generation" being shaped up by people who has (hopefully) learnt from past mistakes and used by people who care (itching to give Rust a ride once it stabilizes just a bit more) and also occasionally digging in the history for possible gems that I might not able to see without my current experience (platonic weekends-only relationship with Haskell, Lisp being right there at top of my TODO list).
> I am not fully convinced that the general idea of language evolution implemented as ruleset outside of the language itself is the way to go
I think you are right - as far as 'the' language is concerned. The complexity (and the slowness) of a C++ compiler now is mostly due to the complexity - and the slowness - of the built-in interpreter of the template meta-language. There are other, saner languages, such as Common Lisp, where the meta-language is indistinguishable from the language itself, which, in addition to its greater simplicity, greatly contributes to the reasonable compile times. In the C/C++ land, it might have been possible to use a similar approach instead of introducing a separate macro/template syntax, a good example being the approach taken in the design of ASP (active server pages), PHP, and the like.
You may want to take a quick gander at the profile of whom you're replying to. :)
>... itching to give Rust a ride once it stabilizes just a bit more ...
Likewise, though from my limited research it seems Rust has legacy baggage of its own already, and there's difficulty in coming to a consensus on what qualifies as idiomatic Rust. It seems that the same principle of cherry-picking a subset of features would apply just the same to Rust as it does to C++. The latter being far messier, of course.
> it seems Rust has legacy baggage of its own already
What is considered legacy cruft in Rust? The only change I'm aware of was the introduction of the `?` operator to replace the `try!` macro. That's easy to find/replace.
The notion extends to official libraries, not just the language itself. That said, I'm not the best person to ask, but cursory research turns up a few examples.[0][1][2][3]
Some of these aren't examples of legacy cruft per se. However, as complexity is added to a language at a rapid rate, there becomes more confusion over what best practice actually is.
Just to point out: biological evolution uses changes in the base features utilized (epigenetics) to speed adaptability to changing environments (or new environments).
There's every reason to think that evolving human systems should use similar masks on features of a technology to make it more suitable for a particular ___domain and serve as a signal to drive the base evolution.
The restricted C subset used for safety critical systems I think is a good example of this -- the restriction allows for usage in a ___domain where the full set of features is a disadvantage.
> ...Yet I am not fully convinced that the general idea of language evolution implemented as ruleset outside of the language itself is the way to go.
What most people nowadays recommend programming in is actually not C++, and actually C++ + all compiler warnings, which is in itself an extension/evolution/restriction of the underlying language. So it's not so far out to go from there to externalizing more rules around valid programs.
The trouble with namespaces is they are not encapsulated (even without ADL). Any piece of code can open up any namespace and add symbols to it. Not being able to reliably determine what is the set of symbols being overloaded in a namespace, for example, makes it hard to reason about.
When writing a container, I'd actually prefer to open namespace std and overload std::begin and std::end for my_namespace::container. That way, I'd be free to use my_namespace::begin for something else, which IMHO is the point of namespaces. But having ADL and being able to re-open other people's namespaces is definitely unfortunate.
I love the Objective-C convention of just using three-letter prefixes for everything. I'll miss its pragmatic approach.
Nobody is forcing you to use the old stuff. You can stick to the new nice bits and mostly just ignore the fact that the old crud exists in the language.
Unless you use libraries which are written in different styles, and have to read / fix them. Then you still have to remember / understand all the complexity.
What a wonderful world would it be… Not being forced to used legacy C++ style. Not being forced to mind the undefined minefield. Not being forced to even use C++ in the many many cases where garbage collection is obviously fine…
Sadly these days, I'm not paid to "solve problems". I'm paid to solve them in C++ —sometimes for good reasons.
std::begin suffers from the same problem and will stay relevant for longer. The cppreference.com docs specifically mention the "using trick"[1], so it seems to be an established pattern now.
It's still ridiculous - calling a function should be easy :(
Back in the 70s IBM used a language called PL/I (aka "Pig Language for Idiots). It had EVERYTHING. Every idea from every language showed up in PL/I. Compiling on a 6Mhz IBM mainframe took a LONG time. The language was, for its time, very complex.
We had one person in the building who was the PL/I guru. If you really needed to understand something he could work out the answer. Everyone else chose a simple subset of the language and never used the fancy features. Unfortunately everyone chose different subsets so maintaining other people's code was a pain.
C++ has the same problem; big, slow, and a collection of ideas imported from other languages. For example, "concepts" have been proposed for the next "standard". They appear to be lifted from Axiom's Spad language.
The C++ committee seems to feel that "importing" ideas is a way to make C++ into a better, newer "standard". Unfortunately that is simply causing havoc. Forty year old code that no longer compiles because some bright spot decided to change the semantics of "inline" is a huge waste of time.
Forty year old code in Common Lisp, which also has a standard, a single standard, just works. The code is fast because there are years worth of optimizations by the compiler writers.
C++ isn't a language. It is a laboratory where some people work out ways to do poorly what other languages do well. C++ isn't a language. It is a parade of languages with "standards". Do you really want to have to "fix" legacy working code because it no longer works in C++21? Programming is hard enough. Fixing old code because it "no longer conforms to the standard" is a huge, unnecessary cost in both time and money. Use it at your own peril. Just be aware that the code you write today won't compile next year.
I don't know, every feature added to C++11 and 14 made my life easier. I do think it's a pig of a language, but all the current features and all the upcoming features are things I want. I view C++ a lot like the english language; complex and inconsistent: yep! Incredibly useful: yep!
"C++ isn't a language. It is a laboratory where some people work out ways to do poorly what other languages do well." Amen!
I studied PL/I, in the University of Maryland dialect called PL/UM, in the 70s as a student. I was very glad that it did not catch on. As C++ grew in size and complexity, I realized that it was this generation's C++ but that it had, sadly, succeeded wildly. If PL/I had been a more well known failure, perhaps the authors of C++ would have heeded it's warning.
I've been programming in C for a long time. I occasionally dig up decades old code and compile it, usually just for fun. Much of the time, I just need to add a few include files, as old C was more tolerant of calling undeclared functions and such. With all of the changes to C, from K&R, to C89, to C99, to C11, it's nice that the core features have been pleasingly stable. The instability of C++ that the OP comments on is one reason I don't like C++ and I'm happy that I don't have to use it often.
> Forty year old code in Common Lisp, which also has a standard, a single standard, just works.
Mostly. If not, just
(pushnew :symbolics *features*)
or whatever the code was created on. Especially the very old lisp code regularly needs some coercion to be made run on a standard compliant cl.
The "just works" part is proven wrong by the regularly reoccurring attempts to agree on extensions to the standard and the myriads of libraries out there that provide generic wrappers around implementation specifics.
> C++ isn't a language. It is a laboratory where some people work out ways to do poorly what other languages do well.
"Lisp isn't a language, it's a building material." -- Alan Kay
and
"A poor coder can write poor code in any language." -- me
Almost. I recently revived the original Boyer-Moore theorem prover from the 1992 sources. I had to make only one change to get it to run with Gnu Common LISP. The name "EXECUTE" is now being used by a standard library, and I had to rename a function.[1]
(It's fun running that on modern hardware. It was painfully slow when I ran the original around 1980. Now it can prove the basic theorems of number theory in a few seconds.)
It is true that my lisp 1.5, maclisp, lisp360, VMLisp, Interlisp, and Symbolics code need rewrites to run but that code predates the Common Lisp standard. There are things I'd like to see in Common Lisp but not enough to require a new standard. If there is ever a new standards effort it should start by absolutely, positively REQUIRING that ALL code compliant with the prior standard MUST work unchanged under the new standard.
The new C++ features like 'auto' simplify life in new code, which should not have been complicated in the first place. Where life gets ugly is with existing code written years ago. My C++ legacy code regularly breaks. gcc seems to default to C++11 but g++ uses C++14 by default. Breaking 'inline' behavior is just wrong. It seems that the Boost library is partially broken, apparently due to compiler implementation issues. Stop-gap measures, like compiler flags, just make the pain all that more intense.
I'd be quite happy if the standards committee decided to create new languages (e.g. C11, C14, C17, etc) so it would be possible to say "I use C11". As it now stands I don't believe ANYONE who says they "know" C++. Are you up to speed on "concepts"? Even Stroustrop described the last "standard" as "a totally new language".
So-called "modern C++" (basically C++11 and beyond) is really immensely more pleasurable to work with than C++03/C++98.
The confluence of automatic type deduction, closures, and move semantics turned C++ into the language that it really ought to have always been, given the vision of Alexander Stepanov's STL. I mean really, before lambdas, using the STL was often really clunky.
But even before C++11, the main "killer features" of C++ has always been RAII (deterministic destruction of resources) and compile time duck typing (templates.) These features together have always made C++ an incredibly powerful language that lets you code at a high level of abstraction with zero runtime penalty (at the cost of longer compile times).
Nowadays, C++ can be almost Pythonic in its expressiveness. But I agree that the most urgent problem facing C++ is huge compile-times, which often result from a combinatorial explosion of new types created by templates that instantiate other templates, etc.
Move semantics are so useful and powerful, but so complex. I've read multiple tutorials on them (all of them very long) and I still keep forgetting minor but critical details.
(You could probably replace "move semantics" in that sentence with multiple other C++ features and still have a valid statement.)
I never, ever hear this as a priority from fellow C++ programmers. It's always just on HN.
If you're actually working with C++, old "misfeatures" simply aren't something which gets in your way very often. You can just code in the modern way and be happy.
That's great for you, but unfortunately doesn't match my experience. Large, old codebases tend to become a mix of all "eras" of C++. That's either because old code was never completely reworked, or because newer programmers pick stuff up in the wrong place, for example from an old blog post.
The problem then is that some of the advantages of modern C++ are a bit "all or nothing". Just to pick an example, for exception safety in the current sense to really work, basically all of your code has to be written in a certain style, which is something that's really hard to achieve.
Maybe that's less of a problem for codebases started after 2011 that have been written in the modern style from the beginning. But given the way the C++ language continues to evolve, my bet is that even those will eventually run into the same problem with whatever new feature is introduced in 2026. There really needs to be a way to deprecate old language features.
Maybe that's less of a problem for codebases started after 2011 that have been written in the modern style from the beginning. But given the way the C++ language continues to evolve, my bet is that even those will eventually run into the same problem with whatever new feature is introduced in 2026. There really needs to be a way to deprecate old language features.
That's one of the things I think the golang project did right. The gofix tool can be used to update old codebases.
The problem is you must keep all those "misfeatures" in the back of your head when trying to code "in the modern way". If you'd really try hard to ignore them, they'll just show up now and then and wreak havoc, sprinkled with some bitter undefined behavior topping.
As a related question: I seem to vaguely recall some mention by someone on HN, that somebody is trying to get some kind of "safe/strict blocks" markers into C++ (or some compiler? I have the feeling it was to be MSVCpp?), where "misfeatures"* would be disabled. Is anybody aware of something like this? Or was that just a beautiful dream I had?
Sounds like someone could make a decent language by just removing all the old misfeatures from C++ and calling it something new.
You could argue that anyone who wants that is already on Rust or whatever, but I bet there are plenty of people who like modern C++ and don't necessarily like Rust.
> I never, ever hear this as a priority from fellow C++ programmers. It's always just on HN.
These aren't mutually exclusive groups. I'm a professional C++ programmer posting on HN to say that C++ is terrible, even when you can somehow confine yourself to "modern C++" -- presumably because you're free from legacy code originally written in the 00s, or earlier!
C++11 (and to some extent C++14) made C++ less awful, but it's still a turd. And even many of the best features of C++11, like move semantics, are blown away by their equivalents in Rust. By the way, even whenever concepts and concepts finally land (I've given up now), C++ templates are still going to miles behind Rust's trait-based generics in ease of use and readability.
Not everything is a new cleanly constructed codebase. I regularly have to jump into and fix/debug existing C++ projects. Often issues are due to accidental interaction between poorly understood features, or a misunderstanding of what a given syntax/idiom does.
Every feature/syntax added to C++ is one more place mistakes can happen, and one more thing to watch out for when debugging. I wish there was someone in a position of C++ power pushing to remove a feature for each one added.
And for those existing multi-milion line projects, which are often decades old, you certanly do noy want to break backward compatibility. Ever.
It is fine to deprecate small warts if there is an obvious semiautomated path forward or if are hardly used (auto_ptr, trigraphs), but things like ADL are very unlikely to be ever fixed completely.
> And for those existing multi-milion line projects, which are often decades old, you certanly do noy want to break backward compatibility. Ever.
I don't see what's inherently wrong with deprecating and then removing features or standard library templates in newer standards. As long as compilers and library implementations continue to support the old features when you ask for an old standard (by passing `-std=c++11` or whatever), what's the problem if C++26 removes $BAD_FEATURE?
The standards committee doesn't seem to think there's an absolute problem with removing features or standard library templates, because they've already removed trigraphs, auto_ptr, and more in C++17.
Those multimilion lines of code are not just legacy codebases, they are often continuously updated and slowly dragged in the 21st century by using more modern idioms and embracing newers standards. If c++11 and 14 had significant compatibility breaks, they wouldn't have had the uptake they did and would have split the community.
It is not just a theoretical issue btw, unfortunately compatibility breaks do happen outside the standard, especially during the early to mid 2000s as compilers started enforcing the standard more closely; enforcing two phase lookup has been particularly painful and I don't think anybody is willing to repeat that experience very soon; there are still codebases stuck on Visual Studio 6 which was infamous for its nonconformance [1] and was widely popular in the late 90s till the early 2000s.
Theoretically you could remove features by deprecating them (with warning) for a specific c++xx version, and removing them in the next. So if c++14 warned on some $BAD_FEATURE, and then $BAD_FEATURE was removed in c++17, there is still a migration path from c++11 to c++14, and eventually c++17 once all deprecation warnings are removed. (or even before, if someone wants to take the time to remove any remaining warnings in one shot) If the compilers were then able to support the exact set of warnings/removals per version by a flag, old codebased would continue to compile and theoretically would continue to move into more modern features as the next version would never actually break the code.
Probably would be nasty to maintain for the compiler developers though. Imagine having to do a bugfix for c++11 mode when you just released c++26 mode.
> Probably would be nasty to maintain for the compiler developers though. Imagine having to do a bugfix for c++11 mode when you just released c++26 mode.
They already have to.
What you're proposing makes much more sense: it means if you write a C++26 only compiler, you don't have to worry about implementing all the old crap that no sane person uses.
> Not everything is a new cleanly constructed codebase.
This is true, in lots of ways. Lots of code is rushed, corners cut, so was bad the moment it was written. Other code is written in a particular style, and would need to be cleaned up to match a more modern style.
One benefit of backwards compatibility within one language is that these pieces of code can be improved incrementally, without having to switch languages. So, a road to improvement, rather than a source of mistakes.
The key is to make the new code better than the old code!
That's because the people who can't stand to deal with those things have found a way to stop writing C++. The rump of programmers still working in C++ are those who didn't care as much.
I did a limited amount of C++ some years ago - it was never my primary language, but there was a time when my primary stack was Python with C++ extensions. I'm a full-time Scala developer these days and love the language - it's concise and expressive, with a powerful type system (I guess C++ template metaprogramming is probably as powerful, but Scala makes that power practical to use) and reasonable runtime performance.
Well, yes. Unless you happen to work with mammoth size legacy C++ code-base which due to C++ being 30+ years old and prevalent mindset about technical debt I assume is the norm.
To be truthful: the older and more experienced I get I can't help but wonder why on earth people get so stuck in C++. To me the sheer weight and complexity of C++ appears meaningless. We know better now than we did in the 80s. Yet people spend their lives trying to fix C++.
> To be truthful: the older and more experienced I get I can't help but wonder why on earth people get so stuck in C++.
For a number of reasons.
First, it actually works.
Second, a lot of software is written in C++.
Third, availability of third-party libraries and tookits.
Fourth, people do know how to write software in C++, and some even have decades of experience under their belt.
Fifth, it's an international standard, therefore better ensured against bit rot.
Sixth, why should anyone jump onto the latest fad just because?
I seriously don't understand why this myth about "newer automatically means better" is so entrenched in the minds of those just starting out in software development.
To clarify, by "people" I was mostly thinking about language designers and the kinds of engineers whose job it is to think a bit further ahead. Not necessarily regular software engineers (hence the reference to "trying to fix C++".
> First, it actually works.
It can me made to work, which isn't the same as being a good programming language.
> Second, a lot of software is written in C++.
A lot of software is written in PHP, Cobol and Fortran. That doesn't mean they represent good directions.
> Third, availability of third-party libraries and tookits.
Valid point from a practical perspective, but not from a language design perspective.
> Fourth, people do know how to write software in C++, and some even have decades
> of experience under their belt.
Based on a few decades of software development: some people can write good C++, most can't and the majority of those who can't are delusional about which camp they are actually in.
> Fifth, it's an international standard, therefore better ensured against bit rot.
That statement makes no sense to me.
> Sixth, why should anyone jump onto the latest fad just because?
> I seriously don't understand why this myth about "newer automatically means better" > is so entrenched in the minds of those just starting out in software development.
First, second, and third, suggest a very strong path dependence. We keep using C++ because we used it in the past —just like Qwerty.
Fourth is also a kind of path dependence, but… come on, reasonable programming languages are simple (Lua and 1ML come to mind). Libraries and frameworks take time to learn, but a core language needs a couple days to be picked up, maybe a couple weeks for full productivity. Assuming you knew the underlying paradigms in the first place of course —no Haskell programmer learns Forth in a day.
Of course newer is not automatically better. See the mess we made of the web. Still, it is safe to say that nobody in their right mind would invent C++ today. As Rust is showing, we can do better.
Even so, I'm extremely wary of this "one language to rule them all". Why does the "right tool for the job" always end up being the vanilla version of some popular language? Where are the languages specialists that can tweak your language into something uniquely suited for your particular needs?
Seriously. We call in database specialists, network specialists, security specialists, OS specialists whenever the project has significant demands in those domains. Why are programming languages left out? We breathe with that stuff!
Trivially, yes; for instance, the machine language of your device's CPU actually runs all the code.
It's not so far fetched to conjecture having one decent high level language to generate that machine code.
I think a good many language designers actually have that as a goal.
Make that feasible goal "two languages to rule them": the high level language, plus uses of the assembly language for some of the inevitable escape hatching. Feasible at least with respect to a given system, and others like it.
However, nothing but one language to program a microcontroller with 512 words of memory, or a parallel cluster with terabytes of storage? Ha ... :)
Well, in theory what you say seems to make sense. In reality things do not look like this. And it isn't like I can articulate perfectly why it is so.
As for programming tiny devices, no, C++ (or even C) doesn't seem to be the right language to do that because people who should know better routinely do very stupid things in those languages for embedded applications. And it isn't just because they are some cash-strapped amateur: car-manufacturers and name-brand manufacturers of appliances have been shown to not know how to do this. Which tells me that they need languages that can help them do the right thing. Or rather: force them to address typical classes of problems or help them address these.
I even saw a discussion about heap-fragmentation on an embedded system where you shouldn't be allocating memory at all -- which is not only doing the wrong thing, but doing the wrong thing badly. Sure, people should know better, but they don't.
I've spent much of the past two years with my nose in embedded code and if you think the average fullstack programmer produces terrible code, much embedded code is often the stuff of nightmares.
If we are to limit the discussion to larger systems, the number of things where you'd need to use C++ for performance or efficiency has shrunk down a lot. In my experience, the vast majority of performance issues come from bad design, lack of measurement, sloppiness and ignorance about algorithmic complexity rather than the ability to squeeze performance out of hardware. Sure, you can, and should, write extremely performance sensitive libraries that cling to the metal where appropriate, but this still leaves a lot you really should do in languages that provide higher productivity, security and clarity.
Until Rust there was no real alternative. All other 'better' languages use a garbage collector which is a no-go in most of the situations where C++ is used.
C++ is often used in situations where a GC would have been perfectly fine. I'll go as far as to guess that most C++ projects are like this. In my little corner of the world, C++ is one of the most overused languages.
Until relatively recently GC pauses have been a real problem. Java used to have no upper bound on stop-the-world GC pauses - your program could literally stop doing anything for an entire minute.
But these days GCs are much better, and especially with the Go GC having sub-millisecond pauses I think you are right that most projects written in C++ today could be written in Go. (And it seems like that is starting to happen.)
Not all - Ada, Modula-2, Object Pascal tried to compete in that space, but developers were more interested in C++ because it allowed them to just #include their existing C headers. Also, before GNAT became available around 1995, Ada vendors invoiced several limbs for a license.
Modules seem much more important than either of those. You know where you include a platform header and suddenly you can no longer call a variable "min" because said platform header decided that should be a macro and it's giving you cryptic errors because what you see isn't what the compiler sees.
I've written too much C++ to be dumbfounded by that stuff but that doesn't mean I'm not still bitter about it.
> Modules seem much more important than either of those.
Modules solve an immediate and obvious pain: unacceptably long compile times. Sum types are more profound. Anyone having tasted an ML derivative (as I have) would dearly miss them. Seriously, once you've tasted sum types, you'll want to use them everywhere: option types, error returns, abstract syntax trees (JSON, XML, compilers…), you name it, sum types probably solve it.
Sum types are no panacea, but if you squint your eyes just right they're pretty damn close.
I've tasted sum types and if I was trying to be snarky I'd say so have millions of Java enterprise developers who fill their code with a healthy dose of instanceof and null :)
It is somewhat anathema to the OO idea and brings lots of (runtime) implementation concerns so I can understand the delay.
They would be deprecated only if the features you are missing are actually on their way into the language. Are they? I agree those features would be nice, but I've never seen any mention of them actually planned.
The problem with breaking some backwards compatibility is that you just broke the ecosystem. And Python3 showed how breaking some is effectively the same as breaking all, and if you are going to do something small, you might as well do it all because its going to be a pain point for years or decades no matter how much automation tooling you build around the transition.
That is made even worse for a compiled language like C++, where if you break headers for some unsupported library it becomes effectively unusable in newer versions.
Maybe we need a standard C++17-depricated/old ? In which the standard defines which features should no longer be used ? This should be optional for compiler to implement though.
I think the problem with Py2 vs 3 is mostly down to the horrible mistake of some operating systems to bundle a "system Python" which makes it very hard to break things because systems (rather than just developers) are depending on it for god knows what.
Someone downvoter may not have understood that several compilers can still produce object code that can be linked together.
Reminds me of C++ itself. Stroustrup posited that it needed syntactic compatibility with C. People had to be able to compile their own code with the new compiler, else his new language would never have been used.
I'm sad to admit he was probably right. He could have settled for linking compatibility, where old modules can be used from new code (and conversely). But that probably wouldn't have worked, if only because nobody wants to deal with 2 compilers and 2 slightly different languages. Also, macros and header files. Seriously K&R, what were you thinking?
Still, this is a possible path forward. To hell with syntactic compatibility, the ability to link the object files should be enough. That would at least let one use legacy libraries with the old compiler, and write the current project with the new stuff. There's just one hard part: the FFI must be seamless.
It's still C++ with all the baggage that comes with. Every company/project seems to have their own subset they use. C++ isn't one language, it's dozens.
There is a nice way to realize how deeply wrong everything is with C++..
Suppose, I have a silly idea - I could recompile my mplayer+ffmpeg and, perhaps, emacs with clang4 with all the cool optimizations for my cheap crappy i3-6xxx CPU of my crappy Lenovo laptop. Seems like nobrainer. Now movies are less laggy.
Why then wouldn't I recompile my mysql server with -stdlib=libc++ and -std=c++1x, -rtlib=compile-rt, using lld and -lomp? Oh fuck...
I wonder why it feels like I can't move at the same pace with C++ compared to C#; want to have a file copied to your output dir? Create a post-compile task; with C# you can just set a property to 'copy always'. Want to add a reference? Set up which dir's to look for, type names, etc. with C#: add reference, choose your dll/whatever and you're done! And those are just a few. One of the biggest hurdles with C++ is type conversion; I recently wrote a small app; most time was spent writing functions to cast/convert all the 9000 ways to define numbers and strings... It's when you start to appreciate the System.Object model that makes sure everything can be represented as a Unicode string that everything knows how to work with.
I also agree that type system in C++ is not perfect and is rarely used in a sensible manner. Yet I would rather look for inspiration in functional statically-typed languages rather than in specialized "multi-paradigm" dynamically-typed ones.
I know it's not fair, but because I write identical apps in both languages, it's one that comes to mind frequently and it becomes very apparent how powerful C# is when it comes to Rapid Application Development.
Every machine in the world is Little Endian, and I'm going to specify a file format -- https://en.wikipedia.org/wiki/WAV -- with C structs and type punning. Seriously, Microsoft?
This is why in TLDR fashion:
"[...] if you take them (promised big language features) all together - or even just two or three of them - they have the potential to change the language[...]"
ccache and forward declarations / isolating classes that require use of large header files to implementation files. Ie, spend 80% of your time working on classes without deep header dependencies.
C++'s standardization committee is known for being enthusiastically pro-Microsoft. IIRC, C++'s thread support is essentially the option that Microsoft forced upon the standard, in detriment of ISO's very own pthreads library which was already an ISO standard.
IIRC, the same problem also plagues C's committee.
You're asking somebody who makes money selling thick expensive self-obsolescing books explaining an obscenely complicated programming language to simplify the language?
I doubt you could say much the same about C, which is not only older than C++, but with similar industry adoption (albeit at a different place in the stack nowadays).
I have - although POSIX covered a fair number of them and POSIX-ish or pretend-to-be-POSIX made up some more. That could all usually be covered with a pretty thin utility layer, and the compiler-of-the-day for whatever the platform was. (and, sometimes, a whole lotta #IFDEFs!)
You must be talking about something more extreme, maybe in embedded land? Do you happen to have a small number of f'rinstances?
C'89 is an awesome language (and language standard). The document is readable and one can reasonably expect to understand the whole language, dark corners and all.
(Features in a language that one doesn't understand are dangerous. Nobody understands all of C++17. I'll let you reach the conclusion.)
Very nice expression!
One of the best expressions i have heard about C++ is "C++ is feature adding contest, they add ao much feature in every cycle it seems they are in hurry"
All the new stuff is heavily template based. As more libraries are written in that style, compile times go through the roof.
I wrote some code recently that required high precision monetary calculations. So I used boost multiprecision. It turned out to be completely impossible as most compilation units ended up depending on that library directly or indirectly.
Already slow compiles became unbearably slow. Facing a tight deadline I quickly wrote an extremely ugly, slow, error prone, pimpl based wrapper around boost multiprecision. Many calculations now require dynamic memory allocation for no good reason.
This is just terrible, because I know I can't leave it like this. I'm going to have redesign the whole thing or look for other libraries or look a lot more deeply into the calculations to see if they really cannot be done with large integers.
It's a productivity disaster. You could say I made wrong choices, which is true, but I think the C++ standards committee has been making wrong choices in not prioritizing a fix for compile times (modules?).
What's the point of having nice new features if you end up spending half your time on carefully cordoning off any code that uses them?