Most useful new C++ 20 features
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.
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include #include #include template concept Sorter = requires(T a) { { std::hash }; 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 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 | ~~~~~~~~~~~~~~^~~ 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.
1 2 3 4 5 6 7 8 9 10 11 |
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++.
1 2 3 4 |
template constexpr bool Movable = requires(T i) { i>>1; }; bool b1 = Movable bool b2 = Movable |
The main use case for requires-expression is to create concepts, just check for the required fields and methods within the type.
1 2 3 4 5 6 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
1 2 3 4 |
template void f(T&&) requires Eq template 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 ||.
1 2 3 4 5 6 7 8 9 |
template requires is_standard_layout_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.
1 2 3 4 5 6 7 8 9 10 11 |
template requires Sumable auto f1(T a, T b) requires Subtractable auto l = [] (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!..
1 2 3 4 5 6 7 8 9 10 11 |
//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;
1 2 3 4 5 6 7 |
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;
1 2 3 4 5 6 7 |
generator while(true) co_yield n++; } |
- the co_return keyword to complete execution that returns a value.
1 2 3 4 5 |
lazy 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
The easiest way to understand with an example is what exactly the new three-way comparison operator is needed for.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#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 <.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#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 }); } |
Related Posts
Leave a Reply Cancel reply
Service
Categories
- DEVELOPMENT (102)
- DEVOPS (53)
- FRAMEWORKS (25)
- IT (24)
- QA (14)
- SECURITY (13)
- SOFTWARE (13)
- UI/UX (6)
- Uncategorized (8)