In September last year, the ISO committee approved C++ 20 as the current version of the international standard. I suggest that you familiarize yourself with the most useful and long-awaited changes to the new standard.

C++ Concepts Library

The library defines fundamental concepts that can be used to dispatch functions and check template arguments at compile time, based on type properties. The concepts are needed in order to avoid logical contradictions between the properties of the data types within the template and those of the input parameters. The concept should be defined within the namespace and is as follows.

template 

concept concept-name = constraint-expression;

...

template 

concept Derived = std::is_base_of::value;

Each concept is a predicate that is evaluated at compile time and becomes part of the template interface, where it is used as a constraint:

#include 
#include 
#include 
template 
concept Sorter = requires(T a) {
{ std::hash{}(a) } -> std::convertible_to;
};
struct asdf {};
template 
void f(T) {}
int main() {
using std::operators;
f(«abc»s); // True, std::string satisfies the Sorter conditions
//f(asdf{}); // Error: asdf does not meet Sorter conditions
}

The #include directives are followed by the declaration of the Sorter concept, which any type T satisfies such that for values a of type T the expression std::hash{}(a) is compiled, and its result is converted to std::size_t. If we call f(asdf) in main, we get a completely meaningful compilation error.

main.cpp: In function 'int main()':
main.cpp:18:9: error: use of function 'void f(T) [with T = asdf]' with unsatisfied constraints
18 | f(asdf{}); // Error: asdf does not meet Sorter conditions
| ^
main.cpp:13:6: note: declared here
13 | void f(T) {}
| ^
main.cpp:13:6: note: constraints not satisfied
main.cpp: In instantiation of 'void f(T) [with T = asdf]':
main.cpp:18:9: required from here
main.cpp:6:9: required for the satisfaction of 'Sorter' [with T = asdf]
main.cpp:6:18: in requirements with 'T a' [with _Tp = asdf; T = asdf]
main.cpp:7:21: note: the required expression 'std::hash<_Tp>{}(a)' is invalid
7 | { std::hash{}(a) } -> std::convertible_to

| ~~~~~~~~~~~~~~^~~
cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail

The compiler also converts the concept, like requires-expression into a bool value, and then they can be used as a simple value, for example, in if constexpr.

template
concept Meshable = requires(T a, T b)
{
a + b;
};
template 
void f(T x)
{
if constexpr(Meshable){ /*...*/ }
else if constexpr(requires(T a, T b) { a + b; }){ /*...*/ }
}

Requires-expression

The new keyword in C++ 20 has two meanings: requires clause and requires-expression. Despite the significant payload, this duality of requires leads to confusion.
Requires-expression uses the bool type, and the code in curly braces is evaluated at compile time. If the expression is correct, requires-expression returns true, otherwise it returns false. The first oddity is that the code in curly braces must be written in a specially designed language, not C++.

template
constexpr bool Movable = requires(T i) { i>>1; };
bool b1 = Movable; // true
bool b2 = Movable; // false

The main use case for requires-expression is to create concepts, just check for the required fields and methods within the type.

template 
concept Vehicle =
requires(T v) {  // any variable m from the Vehicle concept
v.start();     // must have `v.start()`
v.stop();      // and `v.stop()`
};

However, requires-expression has other uses. It is often necessary to check whether a given set of template parameters provides the required interface: free functions, member functions, related types, etc.

template 
void smart_swap(T& a, T& b)
{
constexpr bool have_element_swap = requires(T a, T b){
a.swap(b);
};
if constexpr (have_element_swap) {
a.swap(b);
}
else {
using std::swap;
swap(a, b);
}
}

Requires clause

To really limit something, we need a requires clause. It can be applied to any templated declaration or non-templated function to see if it is visible in a particular context. The main benefit of requires clause is that it lets you forget about SFINAE and other weird C++ templating workarounds.

template
void f(T&&) requires Eq;
template requires Dividable
T divide(T a, T b) { return a/b; }

In the requires clause declaration, it is possible to use several predicates combined by the logical operators && or ||.

template 
requires is_standard_layout_v && is_trivial_v
void fun(T v);
int main()
{
  std::string s;
  fun(1);  // true
  fun(s);  // compilation error
}

Because of the dual nature of the requires keyword, situations with reference unreadable code can arise.

template
requires Sumable
auto f1(T a, T b) requires Subtractable; // Sumable && Subtractable
auto l = [] requires Sumable
    (T a, T b) requires Subtractable{};
template 
requires Sumable
class C;
template 
requires requires(T a, T b) {a + b;}
auto f4(T x);

The same requires requires, the first is clause, the second is expression.

Modules

There is a long-term trend in C++, which is expressed in the gradual elimination of the preprocessor. It is believed that this will eliminate a number of difficulties:

  • headers depending on the order of inclusion;
  • leakage of macros from header files;
  • re-compilation of the same code;
  • cyclical dependencies;
  • poor encapsulation of implementation details.

For example, source_location replaces one of the most commonly used macros, and consteval replaces macro functions. The new way to split source code uses modules and is intended to completely replace all #include directives.
This is what a modular Hello World!..

//module.cpp
export module speech;
export const char* get_phrase() {
    return «Hello, world!»;
}
//main.cpp
import speech;
import ;
int main() {
    std::cout << get_phrase() << '\n';
}

Coroutines

A coroutine is a function that can stop execution in order to be resumed later. Such a function does not have a stack, it pauses execution, returning to the calling instruction. C++ 20 provides almost the lowest-level API, leaving everything else to the user's discretion.
A function is a coroutine if one of the following is used in its definition.

  • co_await statement to pause execution until resuming;
task<> tcp_echo_server() {
  char data[1024];
  for (;;) {
    size_t n = co_await socket.async_read_some(buffer(data));
    co_await async_write(socket, buffer(data, n));
  }
}
  • the co_yield keyword to suspend execution, returning a value;
generator iota(int n = 0) {

  while(true)

    co_yield n++;

}
  • the co_return keyword to complete execution that returns a value.
lazy f() {

  co_return 7;

}

Coroutines cannot use simple return statements, auto, or Concept types, and variable arguments.

KK operator

C++ 20 introduced the three-way comparison operator <=> and immediately got the nickname spaceship operator, which means spaceship operator. This operator for two variables a and b defines one of three: a>b, a=b or a operator yourself, or the compiler will automatically generate one for you.

The easiest way to understand with an example is what exactly the new three-way comparison operator is needed for.

#include 
struct Data
{
	int i;
	int j;
	bool operator<(const Data& rhs) const {
		return i < rhs.i || (i == rhs.i && j < rhs.j);
	}
};
int main()
{
	std::set d;
	d.insert(Data{ 1,2 });
}

One gets the impression that the bool operator <… code is too much for a simple operator in order to avoid compilation errors. Well, if other operators are also needed: >, ==, ≤, ≥ it is inconvenient to display this entire block every time. Now, thanks to the <=> operator, we get the same thing in an easier way.
Note that we need an additional header file, so #include. In fact, we got more than we asked for, since now we can use all comparison operators at once, not just <.

#include 
#include 
struct Data
{
	int i;
	int j;
	auto operator<=>(const Data& rhs) const = default;
};
int main()
{
	Data d1{ 1, 4 };
	Data d2{ 3, 2 };
	d1 == d2;
	d1 < d2;
	d1 <= d2;
	std::set d;
	d.insert(Data{ 1,2 });
}