Building With C++ Coroutines: Series Introduction
C++20 introduces a number of exciting new features: constraints and concepts, modules, ranges, new primitives in the thread support library, etc. While all of these features are interesting and deserving of study, it is the language support for coroutines that I find the most compelling.
Anyone with some degree of experience writing programs that make use of asynchronous constructs knows that one of the most difficult and time-consuming parts of designing such programs is the issue of asynchronous state management: how do we ensure that any necessary context remains valid throughout the lifetime of the asynchronous operation with which it is associated? How do we then ensure that this context is released (destroyed or recycled) once the operation completes? How do we efficiently track the current state of multi-stage asynchronous operations? In my experience these are all non-trivial problems that require careful consideration when authoring a new program utilizing asynchronous constructs.
Coroutines, however, provide an elegant way to solve all of these problems while at the same time simplifying the code that we need to write to do so. They introduce a generally-applicable way to unify the execution state of an operation with the context / data that the operation needs. Moreover, they often enable us to do so more efficiently (in terms of execution time and memory overhead) than we would otherwise be able to achieve with even carefully handcrafted solutions.
There are a number of excellent resources on the topic of C++ coroutines that already exist and have certainly helped me arrive at my current level of understanding. To name a few:
- Lewis Baker’s Blog: Lewis Baker has a number of blog posts on the internal workings of coroutines, including the
awaiter
interface, thepromise
interface, and the mechanics of asymmetric transfer. - Don’t Panic Blog Dawid Pilarski has a number of posts on coroutines that offer a gentle introduction to working with the low-level coroutine interfaces that the standard gives us.
- The Coroutines TS Speaking of the standard, as far as technical specifications go, the coroutines TS is remarkably readable.
- The
cppcoro
Library:cppcoro
is a library full of beautiful coroutine abstractions, and it serves as the primary motivation for many of my experiments with coroutines. It is often after reading the interface description for a type / function incppcoro
that I begin wrestling with the implementation of a new coroutine-based application, primitive, or algorithm. - Conference Presentations A number of skilled speakers have presented on the topic of C++ coroutines at various C++ conferences, and we are fortunate enough to have many of these presentations available on Youtube.
With this series I am not attempting to recapitulate the content already covered in the resources cited above. Instead, I hope to fill what I see as a substantial gap in the currently available resources regarding C++ coroutines.
Existing resources like those available on Lewis Baker’s blog do a great job of explaining the low-level details of how coroutines are implemented by the compiler and the way in which one may go about defining their own coroutines and coroutine types. However, this is often where they stop, meaning that they do not go on to describe how to take these low-level coroutine primitives and use them to build useful abstractions that improve the quality and performance of our code. Of course, libraries like cppcoro
and Facebook’s folly
already support implementations of various useful coroutine abstractions, but I found the source for these abstractions difficult to understand when I was first learning how to integrate coroutines into my own code. Thus, I’m hoping that my experiments and accompanying explanations can serve as a sort of “bridge” between the initial understanding of the low-level mechanics needed get a coroutine compiling and the high-level, generic abstractions that exist in these libraries.
The above paragraph implies that the posts in this series will assume a basic familiarity with coroutines and the requirements of coroutine types; you should know (approximately) how the awaiter
and promise
interfaces work and the mechanics of the methods required to implement them.