logo

Modern C++

Last Updated: 2023-12-03

Modern C++ = at least C++11 (i.e. C++11, C++14, C++17, C++20 and C++23).

  • C++20 big new features: modules, ranges, coroutines, concepts
  • C++17: added and removed many features, but not as big as C++11 or C++20.
  • C++14: a small extension over C++11, with bug fixes and small improvements.
  • C++11 big new features: smart pointers, lambda expressions, rvalue references

Check if a feature is supported in clang: https://clang.llvm.org/cxx_status.html

Smart Pointers

noexcept

Compile-time check, specifies that the function will NOT throw any exceptions.

Type inference: auto

Range-based for loop

Similar to Java and Python:

for (int& x : my_array)
  x *= 2;

Lambda Function

[](int x, int y) -> int { return x + y; }

Polymorphic lambda: in the following example, type of x depends on type of n

double n = 1.0; // or int n = 1;
auto fp = [] (const auto &n) -> auto {return n*2;};
auto x = fp(n);

Function Object (Functors)

An object that has an operator() defined; it can be called with function call syntax.

  • std::function (since C++11) requires that the callable it wraps be CopyConstructible
  • std::move_only_function (since C++23) can store and invoke any constructible (not required to be move constructible)

At its core, std::function is a type-erasing container, which may be used to hold any functor.

Functions are value types; no heap allocation is required.

RAII: Resource Acquisition Is Initialization

A common C++ pattern. It couples the use of a resource to the lifetime of an object:

  • construction of the object <=> acquire the resource
  • destruction of the object <=> release the resource.

This is similar to try-with-resources in Java

C++14 type traits

std::is_signed<T>::value;     /* ---> */   std::is_signed_v<T>;
std::remove_const<T>::type;   /* ---> */   std::remove_const_t<T>;

Modules

Since C++20.

A language feature allowing for a principled specification of component boundaries, rather than using textual inclusion.

Traditionally, when a C++ library X wants to expose its interface to another library Y, X provides a set of declarations in the form of a .h file, which is textually copied into Y’s source files by the preprocessor.

problems:

  • The compiler cannot restrict visibility of symbols.
  • Textual inclusion is brittle. the point and order of inclusion affects the meaning of the program.
  • Textual inclusion does not scale. without ongoing cleanup efforts, textual inclusion leads to a superlinear growth in average C++ parse times.

Modules:

  • Will replace header files in the long run;
  • explicit about what is exported
  • the same module is only processed once, so it will improve build throughput (versus header file will be copied into each source file)

The C++20 standard includes a specification for modules (standard modules). Clang has a different realization of modules (Clang modules, or header modules).

Coroutine

Since C++20.

A new kind of control flow that extends the notion of a function.

A function with one of the following: co_await, co_yield, co_return

  • co_return: "return and don’t call me again"
  • co_yield: "return and resume here on the next call."

A coroutine is simply a function that behaves differently when called multiple times: Instead of restarting from the start on each call, it will continue running after the return statement that last returned. To do this, it needs some state to store the information of where it last returned, and what the state of the local variables was at that point. In existing usage that meant that you need a program stack to store this state, but in C++ this is just stored in a struct.

Designated initializers

Since C++20.

struct Point {
  double x;
  double y;
  double z;
};

Point point = {
    .x = 3.0,
    .y = 4.0,
    .z = 5.0,
};

std::vector<Point> points;
points.push_back(Point{.x = 3.0, .y = 3.0});

Concept

Since C++20.

Facilities primarily intended to specify requirements ("constraints") on templates, often leading to clearer code and more straightforward error messages.

Simpler Comparisons

Defining equality (via operator==) and ordering (via the new “spaceship” operator, operator<=>) for user-defined types becomes much simpler. The compiler will also rewrite a != b into !(a == b) as needed, so there is no need to define operator!= for your types anymore.

Ranges

Since C++20.

Similar to begin()/end() iterator pairs; can directly sort(data);

More Library Changes in C++20

Compile-time Evaluation

The standard library has got more constexpr: vector, string, pair, tuple, algorithms and many other bits can now be evaluated at compile time.

String Utilities

std::string and std::string_view now have handy starts_with and ends_with members (but not yet contains!).

String Formatting

std::format provides a modern replacement for printf.

Deprecation

Some C++17 features have been deprecated to allow for future changes: for example, the this pointer should no longer be captured by a lambda with [=], and use of the comma operator in subscripts, e.g. a[x, y, z], is deprecated (to allow extensions to subscript operators in future versions of C++).

Some minor C++17 features have been removed in C++20, either because they were not sufficiently useful in practice, or because better replacements have been available for some time. For example, a number of useless header files such as <cstdbool> are no longer available, and std::uncaught_exception is superseded by std::uncaught_exceptions.