C++ A Generator Coroutine
A coroutine is a function that can pause in the middle and resume later. A generator is a coroutine that hands out one value each time it pauses.
A generator of squares
The squares function pauses at each co_yield and hands the value out. final_suspend is the pause the coroutine takes when its body ends. Its word noexcept promises this step never throws, and the promise gets its own lesson later.
#include <coroutine>
#include <iostream>
class Generator {
public:
struct promise_type {
int current;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {
}
void unhandled_exception() {
}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
h = handle;
}
~Generator() {
h.destroy();
}
bool next() {
h.resume();
return !h.done();
}
int value() {
return h.promise().current;
}
private:
std::coroutine_handle<promise_type> h;
};
Generator squares() {
for (int i = 1; i <= 10; i++) {
co_yield i * i;
}
}
int main() {
Generator gen = squares();
for (int k = 0; k < 4; k++) {
gen.next();
std::cout << gen.value() << std::endl;
}
return 0;
}Nothing runs until asked
Making the generator runs none of its body. The body starts only when main calls next(), so the Created line prints first.
#include <coroutine>
#include <iostream>
class Generator {
public:
struct promise_type {
int current;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {
}
void unhandled_exception() {
}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
h = handle;
}
~Generator() {
h.destroy();
}
bool next() {
h.resume();
return !h.done();
}
int value() {
return h.promise().current;
}
private:
std::coroutine_handle<promise_type> h;
};
Generator steps() {
std::cout << "Body started" << std::endl;
co_yield 10;
co_yield 20;
}
int main() {
Generator gen = steps();
std::cout << "Created" << std::endl;
gen.next();
std::cout << "First: " << gen.value() << std::endl;
return 0;
}A generator ends
next() returns false once the body has finished. The loop stops on that answer, and main prints the verdict.
#include <coroutine>
#include <iostream>
class Generator {
public:
struct promise_type {
int current;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {
}
void unhandled_exception() {
}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
h = handle;
}
~Generator() {
h.destroy();
}
bool next() {
h.resume();
return !h.done();
}
int value() {
return h.promise().current;
}
private:
std::coroutine_handle<promise_type> h;
};
Generator threeSteps() {
co_yield 10;
co_yield 20;
co_yield 30;
}
int main() {
Generator gen = threeSteps();
bool has = gen.next();
while (has) {
std::cout << gen.value() << std::endl;
has = gen.next();
}
std::cout << std::boolalpha << "Has more: " << has << std::endl;
std::cout << "Done" << std::endl;
return 0;
}Example
#include <coroutine>
#include <iostream>
class Generator {
public:
struct promise_type {
int current;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
std::suspend_always yield_value(int value) {
current = value;
return {};
}
void return_void() {
}
void unhandled_exception() {
}
};
explicit Generator(std::coroutine_handle<promise_type> handle) {
h = handle;
}
~Generator() {
h.destroy();
}
bool next() {
h.resume();
return !h.done();
}
int value() {
return h.promise().current;
}
private:
std::coroutine_handle<promise_type> h;
};
Generator countdown() {
for (int i = 5; i >= 1; i--) {
co_yield i;
}
}
int main() {
Generator gen = countdown();
while (gen.next()) {
std::cout << gen.value() << std::endl;
}
return 0;
}Key points
- Use co_yield to hand a value to the caller and pause the coroutine.
- A coroutine handle is the remote control for a paused coroutine.
- Calling resume() on the handle runs the body until the next pause.
- initial_suspend makes the body wait until the first resume call.
- The wrapper owns the handle and destroys it in its destructor.
In this example, the countdown generator yields 5 and pauses. Each next() call resumes it for one more number. The loop ends when next() reports the body is finished, right after the 1.
Note
A paused coroutine keeps its place and its local values until it resumes.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
When does the body of a generator start running?