diff --git a/CPP20.md b/CPP20.md index 7567eee..d50598e 100644 --- a/CPP20.md +++ b/CPP20.md @@ -4,6 +4,7 @@ Many of these descriptions and examples come from various resources (see [Acknowledgements](#acknowledgements) section), summarized in my own words. C++20 includes the following new language features: +- [coroutines](#coroutines) - [concepts](#concepts) - [designated initializers](#designated-initializers) - [template syntax for lambdas](#template-syntax-for-lambdas) @@ -34,6 +35,54 @@ C++20 includes the following new library features: ## C++20 Language Features +### Coroutines +_Coroutines_ are special functions that can have their execution suspended and resumed. To define a coroutine, the `co_return`, `co_await`, or `co_yield` keywords must be present in the function's body. C++20's coroutines are stackless; unless optimized out by the compiler, their state is allocated on the heap. + +An example of a coroutine is a _generator_ function, which yields (i.e. generates) a value at each invocation: +```c++ +generator range(int start, int end) { + while (start < end) { + co_yield start; + start++; + } + + // Implicit co_return at the end of this function: + // co_return; +} + +for (int n : range(0, 10)) { + std::cout << n << std::endl; +} +``` +The above `range` generator function generates values starting at `start` until `end` (exclusive), with each iteration step yielding the current value stored in `start`. The generator maintains its state across each invocation of `range` (in this case, the invocation is for each iteration in the for loop). `co_yield` takes the given expression, yields (i.e. returns) its value, and suspends the coroutine at that point. Upon resuming, execution continues after the `co_yield`. + +Another example of a coroutine is a _task_, which is an asynchronous computation that is executed when the task is awaited: +```c++ +task echo(socket s) { + for (;;) { + auto data = co_await s.async_read(); + co_await async_write(s, data); + } + + // Implicit co_return at the end of this function: + // co_return; +} +``` +In this example, the `co_await` keyword is introduced. This keyword takes an expression and suspends execution if the thing you're awaiting on (in this case, the read or write) is not ready, otherwise you continue execution. (Note that under the hood, `co_yield` uses `co_await`.) + +Using a task to lazily evaluate a value: +```c++ +task calculate_meaning_of_life() { + co_return 42; +} + +auto meaning_of_life = calculate_meaning_of_life(); +// ... +co_await meaning_of_life; // == 42 +``` + +**Note:** While these examples illustrate how to use coroutines at a basic level, there is lots more going on when the code is compiled. These examples are not meant to be complete coverage of C++20's coroutines. Since the `generator` and `task` classes are not provided by the standard library yet, I used the cppcoro library to compile these examples. + ### Concepts _Concepts_ are named compile-time predicates which constrain types. They take the following form: ``` diff --git a/README.md b/README.md index 2c6f844..bf2ba86 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Many of these descriptions and examples come from various resources (see [Acknowledgements](#acknowledgements) section), summarized in my own words. C++20 includes the following new language features: +- [coroutines](#coroutines) - [concepts](#concepts) - [designated initializers](#designated-initializers) - [template syntax for lambdas](#template-syntax-for-lambdas) @@ -127,6 +128,54 @@ C++11 includes the following new library features: ## C++20 Language Features +### Coroutines +_Coroutines_ are special functions that can have their execution suspended and resumed. To define a coroutine, the `co_return`, `co_await`, or `co_yield` keywords must be present in the function's body. C++20's coroutines are stackless; unless optimized out by the compiler, their state is allocated on the heap. + +An example of a coroutine is a _generator_ function, which yields (i.e. generates) a value at each invocation: +```c++ +generator range(int start, int end) { + while (start < end) { + co_yield start; + start++; + } + + // Implicit co_return at the end of this function: + // co_return; +} + +for (int n : range(0, 10)) { + std::cout << n << std::endl; +} +``` +The above `range` generator function generates values starting at `start` until `end` (exclusive), with each iteration step yielding the current value stored in `start`. The generator maintains its state across each invocation of `range` (in this case, the invocation is for each iteration in the for loop). `co_yield` takes the given expression, yields (i.e. returns) its value, and suspends the coroutine at that point. Upon resuming, execution continues after the `co_yield`. + +Another example of a coroutine is a _task_, which is an asynchronous computation that is executed when the task is awaited: +```c++ +task echo(socket s) { + for (;;) { + auto data = co_await s.async_read(); + co_await async_write(s, data); + } + + // Implicit co_return at the end of this function: + // co_return; +} +``` +In this example, the `co_await` keyword is introduced. This keyword takes an expression and suspends execution if the thing you're awaiting on (in this case, the read or write) is not ready, otherwise you continue execution. (Note that under the hood, `co_yield` uses `co_await`.) + +Using a task to lazily evaluate a value: +```c++ +task calculate_meaning_of_life() { + co_return 42; +} + +auto meaning_of_life = calculate_meaning_of_life(); +// ... +co_await meaning_of_life; // == 42 +``` + +**Note:** While these examples illustrate how to use coroutines at a basic level, there is lots more going on when the code is compiled. These examples are not meant to be complete coverage of C++20's coroutines. Since the `generator` and `task` classes are not provided by the standard library yet, I used the cppcoro library to compile these examples. + ### Concepts _Concepts_ are named compile-time predicates which constrain types. They take the following form: ```