Logo

Blog


Write modern code with features of C++17 and C++20

When you transition from older C++ standards like C++11 or C++14 to the latest C++17 and C++20 it can be a tough journey. It's essential for writing clean and easy-to-maintain code, but many developers find the process challenging.

Here are three common hurdles:

1. Variety of New Features

Adapting to C++17 and C++20 can be intimidating. As the latest standards offer a wide range of new features and syntax changes. One such feature is the introduction of structured bindings in C++17, which represents a shift from how variables were traditionally declared and accessed.

Old Way (C++14 and Earlier):

Before C++17, when you needed to unpack values from a pair or a tuple, you would typically do something like this:

1
2
3
4
5
6
const auto person{std::make_tuple(39, "Alexander"s)};

int         age  = std::get<0>(person);
std::string name = std::get<1>(person);

std::cout << "Name: " << name << ", Age: " << age << '\n';

Here, you need to use std::get<>() to access each element of the tuple, which is both verbose and less intuitive, especially when dealing with more complex data structures.

New Way with Structured Bindings (C++17):

C++17 introduced structured bindings, allowing you to unpack tuple-like structures more succinctly:

1
2
3
4
5
const std::tuple person{38, "Alexander"s};

auto [age, name] = person;

std::cout << "Name: " << name << ", Age: " << age << '\n';

With structured bindings, the code becomes cleaner and more readable. The variables age and name are directly declared and initialized from the tuple in a single, intuitive statement.

Despite the clear advantages of structured bindings, adopting this feature requires you to rethink how you handle and unpack data. This sometimes is a challenge when you transition from older standards.

If you've ever struggled to adopt to these modern features, you're not alone. The variety of new tools and syntax changes can feel overwhelming, but once you understand how to use them, you can easily integrate them into your daily coding practice.

2. Applying New Features Effectively

Another challenge is knowing how to apply new features in practical situations effectively. C++20 introduces powerful features like concepts and ranges. Integrating them into your existing codebase requires more than just theoretical understanding.

Let's explore the ranges feature introduced in C++20. This feature simplifies and enhances the way you work with collections of data. The ranges library allows you to write cleaner and more expressive code by providing a range-based view of your data.

Old Way (C++17 and earlier):

1
2
3
4
5
6
7
8
std::vector<int> Process(const std::vector<int>& data)
{
  std::vector<int> result{};
  std::copy_if(data.begin(), data.end(), std::back_inserter(result), IsEven);
  std::transform(result.begin(), result.end(), result.begin(), MultiplyBy(2));

  return result;
}

In the example above, filtering and transforming the data involves using std::copy_if and std::transform. While functional, this approach requires multiple steps and intermediary collections, which can clutter your code.

New Way with Ranges (C++20):

1
2
3
4
5
6
7
std::vector<int> Process(const std::vector<int>& data)
{
  auto view =
    data | std::views::filter(IsEven) | std::views::transform(MultiplyBy(2));

  return {view.begin(), view.end()};
}

With ranges, you can chain operations more naturally and readably. The views::filter and views::transform provide a concise and expressive way to manipulate data. The result is a cleaner, more maintainable codebase.

Despite these advantages, integrating ranges into your code effectively requires understanding its new paradigms and patterns.

3. Keeping Skills Up-to-Date

Adapting to new standards not only involves learning new syntax. You also need to understand how these updates can enhance your code's clarity and efficiency.

Improvement Example: std::optional and std::variant from C++17

One significant improvement from C++14 to C++17 involves the introduction of std::optional and std::variant. These features simplify error handling and type-safe programming, which were previously more cumbersome and error-prone.

Old Way (C++14):

Before C++17, handling optional values or variant types often required custom implementations or reliance on pointers and union types, leading to verbose and error-prone code.

Here's an example of how you might handle an optional return value with raw pointers in C++14:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
std::unique_ptr<int> findValue(bool condition)
{
  if(condition) {
    return std::make_unique<int>(42);  // Success
  }

  return nullptr;  // Failure
}

void Use()
{
  auto val = findValue(true);
  if(val) {
    std::cout << "Value: " << *val << '\n';
  } else {
    std::cout << "No value found." << '\n';
  }
}

Here, memory management adds complexity and potential for leaks or crashes, and checking for nullptr introduces extra boilerplate.

New Way with std::optional (C++17):

C++17 introduces std::optional, which provides a more elegant and safer way to handle optional values without manual memory management.

Here's how you can simplify the above example using std::optional:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
std::optional<int> findValue(bool condition)
{
  if(condition) {
    return 42;  // Success
  }

  return std::nullopt;  // Failure
}

void Use()
{
  if(auto val = findValue(true); val.has_value()) {
    std::cout << "Value: " << *val << '\n';
  } else {
    std::cout << "No value found." << '\n';
  }
}

Using std::optional not only eliminates the need for raw or smart pointers and memory management but also makes the code cleaner and easier to understand. The handling of optional values is now safer and more expressive, reducing the risk of errors and improving code readability.

Advancements with std::variant (C++17):

Similarly, std::variant simplifies handling multiple possible types in a type-safe way. Prior to C++17, handling such cases typically involved using union or extensive type checks, leading to cumbersome and less safe code.

Here's an example of using std::variant to handle different types:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using MyVariant = std::variant<int, std::string>;

void processValue(const MyVariant& value)
{
  std::visit([](auto&& arg) { std::cout << arg << '\n'; }, value);
}

void Use()
{
  MyVariant value{42};
  processValue(value);

  value = "Hello, C++20!"s;
  processValue(value);
}

std::variant allows you to safely manage and work with different types, reducing the complexity and improving the safety of your code. Please note, the std::variant takes care of destroying the active std::string member.

Keeping your skills up-to-date with these new features ensures that you write clean code and remain competitive in your field. By integrating them, you can improve your coding practices and enhance your overall programming expertise.

How my new course "Programming C++17 to C++20" can help

To help you tackle those features I've created the new course "Programming C++17 to C++20". It is a self-study course that helps you to understand and apply the new language features.

1. Learn wherever you like. Whenever you like.

The course consists of 86 lessons and 11 hours of detailed video content, breaking down C++17 and C++20 features into manageable segments. You can study them at your own pace and rewatch the videos whenever you need to dive into the features of the actual standards.

2. Practical Examples

The course provides practical examples to help you navigate the complexities of modern C++. With clear, actionable exercises, you'll discover how to write modern code. Your code will become easier to read.

3. You'll receive all the material I use in the course.

I've included real-life examples to show you how to apply C++17 and C++20 features in your programming routine. You'll get everything used in the course, so you can apply the shared knowledge on your own. The source code for the exercises and the sample solutions is included as well as the code you see on the slides. Of course, you'll also get a CMake file to build the exercises and solutions.

Are you ready to write clean code?

Write modern code now

With a click on the button you get straight to the course platform so you can start improving your code right away.

Andreas