What you need to do to move on to C++ 20: The complete list

Dori Exterman
Dori Exterman reading time: 8 minutes
August 2, 2020

Are You Ready for C++20?

C++ aims to start out this decade as it did the last—with breathtaking changes. The latest version of C++ – C++20 – introduces numerous new features that have successfully completed the final stages of committee approval. Official publication is expected within the next few months.

With this upgrade on the horizon, every company has the perfect opportunity to upgrade its codebase and take advantage of these significant enhancements to the language. However, embarking upon this process introduces a number of important questions. Will the changes to the standard really help your business? What is the best way to go about upgrading? Are there pitfalls to look out for during the migration process? This article will tackle all of these questions. But first, let’s take a look at what’s new in C++20.

Moving to C++20

Almost ten years ago, C++11 represented the language’s first major leap forward after a long period of minor revisions. It offered a number of substantial new features such as move semantics and lambda expressions. C++14 and C++17 both made important incremental changes to the language, although not everyone upgraded because the changes were not as critical. C++20 refines some of the features introduced in the standard’s previous versions with the benefit of hindsight, and a host of new features have been added as well. The upgrades described below may be enough to convince you that now is the time to migrate.

Modules

C++20 now provides a standardized mechanism for code reuse that mirrors what is available in other languages such as Java and C#. This mechanism is referred to as a module, and it explicitly exports the classes and functions that code outside of the module will be allowed to access. All other code stays private to the module itself. Other modules or code files can then import the module and import the functionality that it provides.

Modules massively reduce dependency on the preprocessor and header files. This makes your code safer since it’s not susceptible to issues such as #include file order. It also results in much faster build times because modules are compiled once rather than every time they are included. This same behavior has also been extended to regular header files. When you import a header file, instead of using #include it will behave in the same way as a pre-compiled header.

The following is a simple example of how to import and export modules:

actions.cppm main.cpp
export module actions;
export class Action {
public:
  const char* perform() {              
    return "done!";
  }
};
import actions;
import <iostream>;

int main() {
  std::cout << Action().perform() << '\n';
}

 

speed up c++

Concepts

The C++ committee has been laboring over the facilitation of constraints on template classes and methods for quite some time, and a solution has finally been ratified for C++20. Concepts are used to ensure that expressions involving a given type are guaranteed to compile rather than exhibiting SFINAE behavior.

The violation of concepts leads to error messages that are much easier to understand and troubleshoot than the usual template-related errors. Catching these problems early on helps ensure that your application will work just as you intended. Concepts can also be used to constrain the types accepted by auto, allowing you to exert greater control over the types you want a given function to accept.

Below is an example of how to constrain the type accepted by a function using concepts in g++ 9.3.0:

template<typename T>
concept Container = requires(T value) { { value.size() } -> std::size_t;};

size_t size(Container& value) {
  return value.size();
}

Coroutines

Asynchronous programming and lazy evaluation have become much easier to implement, thanks to the coroutines being introduced in C++20. Coroutines can be used to essentially suspend the execution of a function and return control back to the caller without the called function losing its current state. This enables the function to be resumed in the future by starting at the same place at which it left off. With coroutines, callbacks are not required. Instead, you can simply yield control to other functions as and when necessary.

C++20 coroutines differ from library-only implementations such as Boost. Coroutines and Boost.Fibers in that they are stackless as opposed to stackful. Instead of requiring an independent stack for each coroutine invocation, stackless coroutines reuse the caller’s stack and allocate the data required to reactivate the coroutine on the heap. This ends up being much more efficient, in terms of memory usage and context switching.

Ranges

Ranges make a huge difference in how we work with collections of data since they give users the power to lazily filter and transform data through a pipeline. Ranges also make iterator pairs largely unnecessary, reducing the need to write error-prone code. The familiar “|” operator is used to pass data from one part of the pipeline to the next, making it easy to compose different pipelines from a common set of primitive functions.

The following is an example of using ranges to lazily evaluate a data pipeline over a collection in g++ 10:

#include <iostream>
#include <ranges>
#include <vector>

int main() {
  using std::views::filter,
  std::views::transform,
  std::views::reverse; // Some data for us to work on
  std::vector<int> numbers = { 6, 5, 4, 3, 2, 1 }; // Lambda function that will provide filtering
  auto is_even = [](int n) { return n % 2 == 0; }; // Process our dataset
  auto results = numbers | filter(is_even)
    | transform([](int n) { return n++; })
    | reverse; // Use lazy evaluation to print out the results
  for (auto v: results) {
    std::cout << v << " ";   // Output: 3 5 7
  }
}

Smaller Features

There are a plethora of useful feature additions in C++20. For example, the spaceship operator (i.e., <=>), which can also be defaulted, offers you a single function for implementing three-way comparisons. The compiler can then use this function to automatically generate every other flavor of comparison operator for you.

C++20 also adds a number of refinements to what can be achieved at compile time. Whereas constexpr functions silently change to execute at runtime when passed non-constant arguments, the new consteval keyword treats this as an error. Both string and vector have also both been adapted for use in constexpr functions. Contexpr functions can now be virtual, and they can utilize try/catch blocks.

 

How Do You Prepare for Migration?

Transitioning to C++20 can be a major undertaking, depending on which version of C++ you’re currently using and the size of your codebase. Your reliance on third-party dependencies can also affect the migration, because they may need to be upgraded or changed in order to compile correctly in C++20. The level of work required to tackle these issues will ultimately determine whether or not the benefits provided by a short-term migration are worth the effort.

It’s important to perform an initial evaluation of the new features in C++20 to determine how well they meet your use cases. For example, if you’re currently using a library solution for coroutines and are tempted by the native solution provided by C++20, write some test code to find out whether or not the new implementation actually gives you an advantage, rather than leaving this evaluation until after you’ve put in all the effort required to change your code. You may find that the feature doesn’t work how you need it to.

Before creating a new build of your codebase it’s important to verify that all of your third party dependencies will continue to function in the upgraded compiler. Changes to the standard library and ABI can cause issues if you try to use multiple versions of the same compiler, and that’s before mentioning using different compilers. Tackling this issue first will bring you much closer to a successful migration and allow you to uncover discrepancies in advance.

Below are five tips to help you prepare for a successful transition to C++ 20:

  • Try out new features. Branch your codebase and experiment with how the new features in C++20 might add value. This will give you a chance to figure out how these features work and what their limitations are.
  • Ensure third-party library support. In addition to compiling your own code, you’ll need to guarantee the continued use of the third-party libraries you depend on. If you’ve previously built these libraries from source, make sure that they still build correctly in C++20.
  • Build your codebase. GCC, Clang, and Microsoft Visual C++ all provide varying degrees of support for C++20. Using the right compiler flags for your environment, such as -std=c++2a, can help to ensure a successful build.
  • Troubleshoot compilation issues. Compilation problems can occur as a result of the introduction of new keywords into the language, changes to standard C++ libraries and deprecated/removed features. Keep in mind that compilers usually vary in how thoroughly they warn users about deprecated feature usage.
  • Verify correctness and performance. It’s important to actually exercise your code in order to be sure that everything is working correctly. Having automated testing and CI/CD pipelines in place will definitely help, and so will running a soak test.

Conclusion

C++20’s advances to the C++ language standard make it easier and faster to deploy applications. In some cases, it may be difficult to adopt C++20 when working with legacy codebases. That’s the risk of post-migration issues which can result from a lack of automated testing. In most cases, however, migration to C++20 will expand your horizons.

Although this article has focused on the major changes in C++20, the sheer quantity and scope of smaller changes and improvements to the language mean there’s something useful for everyone in this upgrade. A number of these changes, such as the introduction of template lambdas, will make your code less verbose without sacrificing readability.

The best way to get started with C++20 is to try out some of the new features and evaluate their usefulness for your particular environment. Hands-on experience with the upgraded language will make it easier to justify the migration effort to your team when the time comes to do so. With C++ moving forward in leaps and bounds, staying current – at least with the major versions, such as this one – is critical.

free trial

 

 

Dori Exterman
Dori Exterman reading time: 8 minutes minutes August 2, 2020
August 2, 2020

Table of Contents

Related Posts

8 minutes 8 Reasons Why You Need Build Observability

Read More  

8 minutes These 4 advantages of caching are a game-changer for development projects

Read More  

8 minutes What Level of Build Observability Is Right for You?

Read More