The Big Four
Get an overview of how each of the Big Four brings modernization to C++.
We'll cover the following
Each feature of the Big Four changes the way we program in modern C++. Let me start with concepts.
Concepts
Generic programming with templates enables it to define functions and classes which can be used with various types. As a result, it is not uncommon that you instantiate a template with the wrong type. The result can be many pages of cryptic error messages. This problem ends with concepts.
Concepts empower you to write requirements for template parameters that are checked by the compiler and revolutionize the way we think about and write generic code. Here is why:
-
Requirements for template parameters become part of their public interface.
-
The overloading of functions or specializations of class templates can be based on concepts.
-
We get improved error messages because the compiler checks the defined template parameter requirements against the given template arguments.
Additionally, this is not the end of the story.
-
You can use predefined concepts or define your own.
-
The usage of
auto
and concepts is unified. Instead ofauto
, you can use a concept. -
If a function declaration uses a concept, it automatically becomes a function template. Writing function templates is, therefore, as easy as writing a function.
The following code snippet demonstrates the definition and the usage of the straightforward concept Integral
:
template <typename T>concept Integral = std::is_integral<T>::value;Integral auto gcd(Integral auto a, Integral auto b) {if( b == 0 ) return a;else return gcd(b, a % b);}
The Integral
concept requires from its type parameter T
that std::is_integral<T>::value
is true
.
std::is_integral<T>::value
is a value from the type traits library checking at compile time if T
is integral. If std::is_integral<T>::value
evaluates to true
, all is fine; otherwise, you get a compile-time error.
The gcd
algorithm determines the greatest common divisor based on the Euclidean algorithm. The code uses the so-called abbreviated function template syntax to define gcd
. Here, gcd
requires that its arguments and return type support the concept Integral
. gcd
is a kind of function template that puts requirements on its arguments and return value. When I remove the syntactic sugar, you can see the real nature of gcd
.
Here is the semantically equivalent gcd
algorithm, using a requires
clause:
template <typename T>requires Integral<T>T gcd(T a, T b) {if( b == 0 ) return a;else return gcd(b, a % b);}
The requires
clause states the requirements on the type parameters of gcd
.
Modules
Modules promise a lot:
- Faster compile times
- Reduce the need to define macros
- Express the logical structure of the code
- Make header files obsolete
- Get rid of ugly macro workarounds
Here is the first simple math
module:
export module math;export int add(int fir,int sec){return fir + sec;}
The expression export module math
(line 1) is the module declaration. Putting export
before the function add
(line 3), exports the function. Now, it can be used by a consumer of the module.
import math;int main() {add(2000, 20);}
The expression import math
imports the math
module and makes the exported names visible in the current scope.
The ranges library
The ranges library supports algorithms which
- can operate directly on containers; you don’t need iterators to specify a range
- can be evaluated lazily
- can be composed
To make it short: The ranges library supports functional patterns.
The following example demonstrates function composition using the pipe symbol.
int main() {std::vector<int> ints{0, 1, 2, 3, 4, 5};auto even = [](int i){ return i % 2 == 0; };auto square = [](int i) { return i * i; };for (int i : ints | std::views::filter(even) | std::views::transform(square)) {std::cout << i << ' '; // 0 4 16}}
Lambda expression even
(line 3) is a lambda expression that returns true
if an argument i
is even. Lambda expression square
(line 4) maps the argument i
to its square.
Line 6 demonstrate function composition, which you have to read from left to right: for (int i : ints | std::views::filter(even) | std::views::transform(square))
. Apply on each element of ints
the even
filter and map each remaining element to its square. If you are familiar with functional programming, this reads like prose.
Coroutines
Coroutines are generalized functions that can be suspended and resumed later while maintaining their state. Coroutines are a convenient way to write event-driven applications. Event-driven applications can be simulations, games, servers, user interfaces, or even algorithms. Coroutines are also typically used for cooperative multitasking.
C++20 does not provide concrete coroutines, instead, C++20 provides a framework for implementing coroutines. This framework consists of more than 20 functions, some of which you must implement, some of which you can override. Therefore, you can tailor coroutines to your needs.
The following code snippet uses a generator to create a potentially infinite data-stream. The chapter coroutines provides the implementation of the Generator
.
Generator<int> getNext(int start = 0, int step = 1){auto value = start;while (true) {co_yield value;value += step;}}int main() {std::cout << '\n';std::cout << "getNext():";auto gen1 = getNext();for (int i = 0; i <= 10; ++i) {gen1.next();std::cout << " " << gen1.getValue();}std::cout << "\n\n";std::cout << "getNext(100, -10):";auto gen2 = getNext(100, -10);for (int i = 0; i <= 20; ++i) {gen2.next();std::cout << " " << gen2.getValue();}std::cout << "\n";}
The function getNext
is a coroutine because it uses the keyword co_yield
. getNext
has an infinite loop that returns the value at co_yield
(line 4). A call to next
(line 15 and line 24) resumes the coroutine and the following getValue
call gets the value. After the getNext
call returns, the coroutine pauses once again, until the next call next
.
There is one big unknown in this example: the return value Generator<int>
of the getNext
function. This is where the complication begins, which I describe in full depth in the coroutines chapter.