Coroutines in C++

Understand how C++20 introduces coroutines and what distinguishes a C++ function as a coroutine.

Introduction to coroutines in C++20

The coroutines added to C++20 are stackless coroutines. There are options to use stackful coroutines in C++ as well by using third-party libraries. The most well-known cross-platform library is Boost.Fiber. C++20 stackless coroutines introduce new language constructs, while Boost.Fiber is a library that can be used with C++11 and onward. We will not discuss stackful coroutines any further in this book but will instead focus on the stackless coroutines standardized in C++20.

The stackless coroutines in C++20 were designed with the following goals:

  • Scalable in the sense that they add very little memory overhead. This makes it possible to have many more coroutines alive compared to the possible number of threads or stackful coroutines alive.
  • Efficient context switching means that suspending and resuming a coroutine should be about as cheap as an ordinary function call.
  • Highly flexible. C++ coroutines have more than 15 customization points, which gives application developers and library writers a lot of freedom to configure and shape coroutines as they like. Decisions about how coroutines are supposed to work can be determined by us developers rather than being hardcoded in a language specification. One example is whether a coroutine should be suspended directly after being called or continue executing to the first explicit suspend point. Such questions are usually hard-coded in other languages, but in C++, we can customize this behavior using customization points.
  • Do not require C++ exceptions to handle errors. This means that we can use coroutines in environments where exceptions are turned off. Remember that coroutines are low-level features comparable to ordinary functions, which can be highly useful in embedded environments and systems with real-time requirements.

With these goals in mind, it’s probably not surprising that C++ coroutines can initially be a bit complicated to grasp at first.

What’s included in standard C++ (and what’s not)?

Some C++ features are pure library features (such as the Ranges library), whereas others are pure language features (such as type inference with the help of the auto keyword). However, some features require additions to both the core language and the standard library. C++ coroutines are one of those features; they introduce new keywords to the language, but also add new types to the standard library.

On the language side, to recap, we have the following keywords related to coroutines:

  • co_await: An operator that suspends the current coroutine
  • co_yield: Returns a value to the caller and suspends the coroutine
  • co_return: Completes the execution of a coroutine and can, optionally, return a value

On the library side, there is a new <coroutine> header including the following:

  • std::coroutine_handle: A template class that refers to the coroutine state, enabling the suspending and resuming of the coroutine
  • std::suspend_never: A trivial awaitable type that never suspends
  • std::suspend_always: A trivial awaitable type that always suspends
  • std::coroutine_traits: Used to define the promise type of a coroutine

Get hands-on with 1400+ tech skills courses.