Joseph Sibony
reading time:
C++ is alive and kicking. There are many new projects written in C++, and in the TIOBE index C++ is rated number 4 in popularity and usage, only after Python, Java and C.
But even though C++ has been rated highly by TIOBE index in the last two decades, going from #5 in popularity (February 2008) to #3 (May 2019), the forecasts for C++ two decades ago were not that optimistic.
Back in 2001, C++ seemed to be declining, to say the least. Other programming languages such as Java and C# were starting to obtain their leadership position, while for web development javascript was in the lead, and that’s even before the big rise of Python.
C++ seemed to be undergoing a midlife crisis:
“… with ever tightening Web developer cycles, and new easier-to-use programming languages, C++ is going through something like a midlife crisis, perhaps, examining what it’s good at and using what time it has left as a programming language to do what it does best. No more trying to conquer the world. C++ knows what it’s good at–and what it does, it still does quite well.”
Even though by now C++ most probably overcame its midlife crisis, the quote above is still mainly true – nobody sees C++ as a language for all usages, and it certainly is not trying to conquer the world. C++ users know its strengths – in domains where it still does not have much competition. But two decades ago the midlife crisis of C++ seemed to be a real challenge that might doom the language to a slow death, destined to be replaced by languages such as Java and VB.NET.
The questions “Is C++ Dying?” or “Is C++ still relevant?” were still around in 2015 and in 2018, and are still around even today. Yes, even today in 2021! Don’t get me wrong, the answers in all above articles are: “no, it is not dying” and “yes, it is still relevant”. But the mere fact that such questions arise means that there are some doubts around C++. Even when a blog post doesn’t list C++ in the list of languages that are marked for death, some readers would hurry to put C++ on that list, while others argue in favor of it.
The main reasons for having such doubts for years now, are the complexity of C++ and it’s position as a legacy language, as some view it. With new languages coming out, most of which are easier to write code with, or at least perceived as such, the competition is fierce. And C++ is complex. But in spite of the apocalyptic prophecies about it, C++ is still highly in use, and many predict it will keep its place for years to come.
So, What Made C++ Survive These Apocalyptic Prophecies?
There are two main reasons C++ is still so strong, and will continue to be.
First, there is an actual need for a language that provides low level control, access to hardware memory and high runtime performance. All these features are required in many industries and domains, from embedded and real time systems (which include aviation, automotive, defense and military systems, medical devices, etc.), to gaming, finance, systems with high traffic volume, deep learning engines, and many more.
Second, the language is very much alive. It is evolving and improving by the minute (well, maybe not by the minute, but you get what I’m saying). This evolution is the main topic of this post. If C++ was not evolving, there was a good chance that it would have been in decline. Programmers tend to like using cutting-edge technologies and modern, updated techniques. When it comes to software programming languages, they prefer languages that come with modern features, when compared to other programming languages around.
The Evolution of C++
It all dates back to Bjarne Stroustrup’s bad experience with coding complicated code in C. Something was missing: the Object Oriented part, the ability to think in terms of “Objects”. In 1979, Stroustrup implemented C with Classes, which continued to evolve, then renamed C++ in 1982. In 1985, the first edition of “The C++ Programming Language” book came out. It already had a few compiler implementations by then, but it was not yet standardized. Bjarne wanted the language to have an official standard that would be influenced by the community. This ignited an effort to create an ISO-approved standard for C++, which was achieved on November 14, 1997, with the final vote on the C++ standard that later became known as C++98. A few years later, after gathering some minor defects in the language specifications, the ISO committee released C++03, which was not a major change from C++98.
Then came a long period of silence.
C++ was being heavily used by many and taught as a leading language in almost any computer science curriculum. Many C++ libraries came out, including the known Boost library (which actually started in 1999). But the language itself was stagnant.
During these years other languages that were influenced by C++, like Java and C#, gained popularity. These languages introduced nice features that could have been present in C++, for example, the ability to initialize a data member in class:
class Foo {
int widget = 42; // not allowed in C++98/03
public:
// ...
};
The reason given, back then, when teaching that you cannot initialize a data member like that, was that it doesn’t have yet a place in memory, which would be given to it only when the object will be created. Thus the initialization has to happen in the constructor body or in the constructor initialization list. But this is, of course, quite a bogus explanation, as Java and C# that were influenced by C++ allowed that. And the explanation that there isn’t yet a place in memory for the assigned field, is just an excuse – the compiler should be capable of turning that into an initialization that would happen only when an object is created.
And guess what? About a decade later, you can do it!
Such a syntax issue may seem very minor, but it has a bad effect on developers, who see how a similar thing can work in other languages. And developers tend to compare software languages and murmur in discontent when they miss something that seems to be useful.
In addition to syntax issues, it was clear that there is a need to expand the C++ library that comes as part of the language.
It was clear that the language has to evolve. But the process was somehow hampered by not having a clear path.
Back in the early 2000s, Bjarne aimed the newly discussed standard, C++0x, to be ready sometime between 2003 and 2005, with:
- No major changes to the language itself
- Major extensions to the standard library
(See in an archive of Bjarne FAQ, here).
The reality was a bit different: the new C++0x standard came out in 2011 (that is 0xB in hexa), naturally getting the name C++11. And it did include many additions to the language itself, not only to the standard library.
Then Came C++11
C++11 was a major version and a great achievement for C++, with the following content categories:
- New “syntactic sugaring” features for nicer code which is easier to write, read and maintain.
- Changes and additional features added to the language, enabling new options for writing or simplifying code.
- Adding features that allow writing more efficient code.
- Adding resource management features that allow writing less bug prone code.
- Adding library extensions, thus reducing the need for external libraries.
Nicer Code – “Syntactic Sugaring”
This includes features such as the `auto` keyword for automatic deducing of variable type:
std::map<std::string, int> name_2_id;
// ...
auto itr = name_2_id.find("John Doe");
It is referred to as “syntactic sugaring”, as you can achieve the exact same thing with the more verbose syntax:
std::map<std::string, int> name_2_id;
// ...
std::map<std::string, int>::iterator itr = name_2_id.find("John Doe");
No one would argue that the second is easier to write. One may say that being more verbose, the code is easier to read and maintain, but the view today is that `auto` is a good tool that should be used in cases like above.
It is interesting to note that both Java and C# also have the `var` keyword, which is the equivalent to C++’s `auto`. However, in both languages this feature came about 6 or 7 years after `auto` was introduced in C++. In C#, `var` was introduced in version 3, in 2017. In Java, `var` was introduced in version 10, in 2018. And in both C# and Java, the abilities of `var` are not as strong as `auto` in C++.
Another “syntactic sugaring” is the range-based-for, added in C++11:
for(const auto& name_id_pair: name_2_id) {
// do something with pair of name and id
}
This is again “syntactic sugar”, as the specification itself defines the range-based-for in terms of the code to be replaced by the compiler.
It is to be noted that “syntactic sugar” may still assist the programmer in writing correct code, where if not using it, wrong or inefficient code might have been implemented. An example for that, specifically for deducing type with `auto`, can be found here. So in some cases it is more than just sugaring.
Additional Changes and Additions
C++11 added some important features, such as lambda expressions and variadic templates, plus changes like deprecating the `throw` signature and replacing it with `noexcept`.
This post does not aim to be an exhaustive review of the changes and additions in the different C++ versions, so we will not dive into each of those (links to additional reading can be found at the end of this post). But the important part is that the language became modern, with features like lambda expressions that were already there in other languages.
More Efficient Code
One of the smart additions introduced by C++11 was rvalue and move semantics. This addition fights, with great success, the redundant copies that were known to be part of C++ and were one of the reasons for C keeping its place in embedded and real time environments, where any extra redundant copy is too costly to allow. With C++11 rvalue and move semantics, most cases of redundant copies can be eliminated with significant performance boost, attracting C programmers to hop on the C++ wagon.
Another addition that affects efficiency is the emplace functions that allow the creation of objects inside the std containers instead of copying or moving them around. This was enabled by C++11 based on variadic templates and perfect forwarding, allowing forwarding of constructor parameters without redundant copying.
Less Bug-Prone Code
With smart pointers, C++11 paved the way for “no new and delete in user code”, the rule-of-zero and the notion that a language without garbage collection can still have a safe and simple resource management model. The addition of smart pointers in C++11 is partially based on rvalue and move semantics.
Additions to the Standard Library
A richer standard library reduces the need for external libraries and increases the commonality between different projects. C++11 paved the way for an enriched common C++ library with multi-threading and concurrency support and additions to the containers and algorithms selection provided as part of the standard library.
But Hey, We Are at C++20 by Now!
After C++11 came out, it was clear to everyone that there is a need for a regular track of new C++ versions. The three year cycle for C++ releases was not yet set at that time, but the momentum for improving the language was there. C++11 was followed by a minor version C++14. Then when C++17 came out the three years cycle was established, with C++20. All three C++ versions from C++14 till C++20 were meeting their target d
eadlines and were published on time, though the content was compromised a bit in some cases. The notion of publish on time, get in anything that is ready and voted in-favor, and postpone the rest, became the norm.
So what is there in C++14 till C++20?
C++14 – A Minor, Yet Important, Release
C++14 added a few important additions, such as:
- Adding std::make_unique that was somehow overlooked and was not added into C++11, while std::make_shared was already added in C++11. (There were good reasons for adding std::make_shared in C++11. The need for std::make_unique is not as strong as the need for std::make_shared, yet having std::make_unique as part of the standard library is of course useful).
- Function return type deduction: you can now just use `auto` as your return type and the compiler would deduce the returned type from your function:
auto foo() {
return moo(); // if moo changes its return type, so do I
}
- Generic lambdas: allowing lambdas to get `auto` parameter types and thus behave as a template function.
- Lambda capture expressions: allowing to declare a new variable in the lambda’s capture. This is necessary in order to allow moving into the lambda, and omits the need for using std::bind.
For additional items added in C++14, follow the links at the end of this post.
C++17 – Novel Ideas and Additions
C++17 brought in some novel ideas allowing writing simpler code for complicated usages:
- Class template argument deduction (CTAD), allowing the creation of objects of template classes without providing template arguments, based on constructor parameters, e.g.:
std::tuple tup("C++", 17, "magic"); // no need to use std::make_tuple
- if-constexpr, for evaluating a condition at compile time. This allows writing a template function that returns different types based on a compile time condition, something that would have been achieved before C++17 with specialization or heavy SFINAE machinery. Check out the C++17 way of achieving it in an example here or watch it here.
- Structured binding, making C++ closer to Python, with the following new syntax:
std::map<std::string, int> name_2_id;
for(const auto& [name, id]: name_2_id) {
// extracting name and id from a pair
}
- template<auto> – allowing auto for non-type template parameters, read more about it here or watch here.
- Adding to the standard library: std::optional, std::any, std::variant and std::string_view.
- Added a new filesystem library into the standard library.
And again, this is only a partial list. For additional items added in C++17, follow the links at the end of this post.
C++20 – A Leap Forward for C++
C++20 is a real leap forward with the big four already detailed in this blog post:
- Modules – the new way of modularizing code while letting away the old #include, with significant benefits, reducing dependencies and improving build time.
- Concepts – the feature that many have waited a long time for, allowing restricting template parameters, without using SFINAE, achieving beautiful duck-typing done right.
- Coroutines – the mechanism that is already there in many other languages, allowing natural asynchronous calls.
- Ranges – a very elegant substitute for using iterators in algorithms, allowing much simpler calls to standard algorithms together with advanced chaining abilities on containers and views.
In addition to the big four, there are some other significant additions, such as three-way-comparison (the spaceship operator), many new abilities for lambdas, new standard attributes, removing the need for typename in certain circumstances, and many more.
As in previous sections, above is only a partial list. For additional items added in C++20 follow the links at the end of this post.
Migrating to a Newer C++ Version
Usually, the best approach is keeping up with the most updated version of the language, thus being able to utilize its new features and use libraries that advanced to the new version. The C++ versions are 99% backward compatible (there are rare cases of fixing defects in the spec that might break compatibility, but those are very rare and restricted). You may want to wait a bit before jumping to a new version, making sure that the compliant compilers are well behaved, but eventually you would have to make the transition. For more insights into how to manage your migration project, you may follow this blog post: How to Modernize Legacy C++ Code?
And when you do learn any new version of C++, don’t feel obliged to grab it all. Take one bite, or one byte, at a time. And keep calm, Modern C++ Isn’t Scary.
The committee
The work of the committee, who actually makes the progress of C++, is described here. The progress is based on proposal papers submitted by the C++ community for discussion. These papers may include proposed new features (e.g., deducing this), new additions suggested into the standard library (e.g., executors library) and reported defects in the standard itself with ideas on how to amend it (e.g., counted_iterator issues).
Over the years the size of the committee has grown, as can be seen in the picture below, taken from the committee web page. The challenge of managing such a group and reaching consensus, or at least, agreed decisions, is not easy. And yet, being done successfully. One of the most important responsibilities of the committee is to make sure any new addition to the language would not break existing code.
Modern C++ – Plans for C++23
With the plans of having a new version every 3 years, C++23 is the next expected version and it is going to be a minor one. Covid19 is affecting the pace the ISO committee can progress in online meetings, instead of face-to-face-meetings, and though C++23 would have exciting new additions, its exact content is not yet clear.
As it seems, C++23 is planned to be more about fixes and small additions. The items that Bjarne would like to see in C++23, as listed by him at CoreCpp, include: (a) adding coroutines support into the standard libraries; (b) shifting the standard library to use modules; and (c) executors, the standard library solution for managing concurrent and parallel execution. However it is not clear if the above would be ready on time to get into C++23. Static reflection, which was a candidate for C++23, is already expected to be postponed to C++26.
So, What Is the Future of C++?
Jon Kalb and Gašper Ažman, in their book “C++ Today: The Beast is Back” (O’Reilly 2015, free download here), summarize the future of C++ (p. 69) with the disclaimer “Never Make Predictions, Especially About the Future” (a quote attributed to Casey Stengel, however it might be Niels Bohr, or others, who said that before), focusing on the following attributes:
Performance
Mobile and cloud computing has rekindled the interest in performance – performance will always be important, which is good for a language that has always been uncompromising in its approach to performance.
New Platforms
As the cost of hardware falls, more and more computing devices are created, some with very tight memory and footprint requirements. It looks good for a highly portable systems language with a “you don’t pay for what you don’t use” approach to features.
Scale
At the top end, the falling cost of hardware will lead to the design and implementation of systems of a size that are difficult for us to imagine now. To implement these systems, engineers are going to look for a language that scales with additional users and teams and supports the creation of very large systems. Such a language will need to have high performance, but also support the high-level abstractions necessary to design systems at that scale. It will also need as much compiler-aided bug-catching as possible, which is heavily aided by an expressive type system that C++ supports.
Software Ubiquity
Our world is constantly evolving into one in which we are surrounded by software. Will all of this software need to be highly portable, low-memory, high-performance code? Of course not. There will be great demand for applications that do not require software to be pushed to its limit. But these applications will always run on infrastructure where performance will be in demand. A lot of this infrastructure is currently written in C, but when infrastructure code requires high-level abstractions, that code is and will usually be written in C++.
[…] the fact is that high performance infrastructure makes it possible to create applications in a less demanding programming environment. More programmers working in high-level, non-systems languages just increases the demand for and value of the systems-programming projects that make their work possible.
Powerful Tools
The philosophy of C++ has been to rely more and more on a powerful compiler to do the heavy lifting of making high-performance applications. […] The world of computing technology can change quickly, dramatically, and sometimes unexpectedly, but from where we sit, it looks like C++ is going to continue to play an important role for the foreseeable future.
Although published in 2015, “The Beast is Back” remains as relevant as if it was written today. Adding to that the continuous evolution of C++ and the enthusiastic community around it and you get a midlife language that feels modern, young and desirable. And at this pace of C++ continuous popularity over the years, there are those who predict that C++ would stay around even when all C++ programmers are replaced by AI.
Links for Additional Read
Wikipedia lists of what was added in each C++ language version:
C++11, C++14, C++20 and the plans for C++23
ISO summary of latest C++ status
CppReference page on the History of C++
C++11 links:
C++11 Overview, C++ FAQ (originally created by Bjarne Stroustrp, here).
Stackoverflow post on What breaking changes are introduced in C++11?
Stackoverflow post on New features in C++11
Stackoverflow post on What is move semantics?
Stackoverflow post on What is a lambda expression in C++11?
Stackoverflow post on C++11 standardized memory model
C++17 and C++20 links:
Stackoverflow post covering the new features in C++17
Incredibuild blog post on the new features in C++20
A summary in Microsoft blog on the progress of Modern C++.
ISO papers on the changes in each version release:
Changes between C++11 and C++14
Changes between C++14 and C++17
Changes between C++17 and C++20
Incredibuild blog post on How to Modernize Legacy C++ Code?
Table of Contents
Shorten your builds
Incredibuild empowers your teams to be productive and focus on innovating.