A Minimal but Complete Example
Learn how to create a fully functional coroutine example by learning about the return object, promise type, awaitable types, passing the coroutine around, and allocating its state.
Overview
Let’s start with a minimal example to understand how coroutines work. Firstly, we implement a small coroutine that is suspended and resumed before it returns:
auto coroutine() -> Resumable { // Initial suspendstd::cout << "3 ";co_await std::suspend_always{}; // Suspend (explicit)std::cout << "5 ";} // Final suspend then return
Secondly, we create the caller of the coroutine. Pay attention to the output and the control flow of this program. Here it is:
int main() {std::cout << "1 ";auto resumable = coroutine(); // Create coroutine statestd::cout << "2 ";resumable.resume(); // Resumestd::cout << "4 ";resumable.resume(); // Resumestd::cout << "6 ";} // Destroy coroutine state// Outputs: 1 2 3 4 5 6
Thirdly, the return object of the coroutine, Resumable
, needs to be defined:
class Resumable { // The return objectstruct Promise { /*...*/ }; // Nested class, see belowstd::coroutine_handle<Promise> h_;explicit Resumable(std::coroutine_handle<Promise> h) : h_{h} {}public:using promise_type = Promise;Resumable(Resumable&& r) : h_{std::exchange(r.h_, {})} {}~Resumable() { if (h_) { h_.destroy(); } }bool resume() {if (!h_.done()) { h_.resume(); }return !h_.done();}};
Finally, the promise type is implemented as a nested class inside the Resumable
, like this:
struct Promise {Resumable get_return_object() {using Handle = std::coroutine_handle<Promise>;return Resumable{Handle::from_promise(*this)};}auto initial_suspend() { return std::suspend_always{}; }auto final_suspend() noexcept { return std::suspend_always{}; }void return_void() {}void unhandled_exception() { std::terminate(); }};
This example is minimal but walks through a lot of things that are worth paying attention to and need to be understood:
- The function
coroutine()
is a coroutine because it contains the explicit suspend/resume point usingco_await
- The coroutine doesn’t yield any values but still needs to return a type (the
Resumable
) with certain constraints so that the caller can resume the coroutine - We are using an awaitable type called
std::suspend_always
- The
resume()
function of the resumable object resumes the coroutine from the point it was suspended - The
Resumable
is the owner of the coroutine state. When theResumable
object is destructed, it destroys the coroutine using thecoroutine_handle
The relationship between the caller, the coroutine, the coroutine handle, the promise, and the resumable is illustrated in the following diagram:
Now it’s time to look a little closer at each part. We’ll begin with the Resumable
type.
The coroutine return object
Our coroutine returns an object of type Resumable
...
Get hands-on with 1400+ tech skills courses.