Mastering C++20 Coroutines: From Concepts to Practical Implementations
This article provides a comprehensive introduction to C++20 coroutines, covering fundamental concepts, stackful versus stackless implementations, detailed code demos—including a simple coroutine, a generator for Fibonacci numbers, and a task framework—while explaining the underlying compiler transformations and practical usage.
This article is an introductory guide to C++20 coroutines. It explains the concept of coroutines, their operations (create, suspend, resume, destroy), and how they differ from regular functions.
Coroutine Concepts
A coroutine is a logic unit that can be paused and resumed. Unlike a normal function that only has call and return, a coroutine supports create, suspend, resume, and destroy. The article discusses how the operating system creates processes and threads, the role of the stack and heap, and calling conventions.
Stackful vs Stackless Coroutines
Stackful coroutines allocate a separate stack for each coroutine, which incurs higher creation cost and memory usage. Stackless coroutines, used by C++20, store all state on the heap, making them lightweight but requiring more refactoring of existing code.
Simple Coroutine Demo
#include <iostream>
#include <coroutine>
template<bool READY>
struct Awaiter {
bool await_ready() noexcept {
std::cout << "await_ready: " << READY << std::endl;
return READY;
}
void await_resume() noexcept {
std::cout << "await_resume" << std::endl;
}
void await_suspend(std::coroutine_handle<>) noexcept {
std::cout << "await_suspend" << std::endl;
}
};
struct TaskPromise {
struct promise_type {
TaskPromise get_return_object() {
std::cout << "get_return_object" << std::endl;
return TaskPromise{std::coroutine_handle<promise_type>::from_promise(*this)};
}
Awaiter<true> initial_suspend() noexcept { std::cout << "initial_suspend" << std::endl; return {}; }
Awaiter<true> final_suspend() noexcept { std::cout << "final_suspend" << std::endl; return {}; }
void unhandled_exception() { std::cout << "unhandled_exception" << std::endl; }
void return_void() noexcept { std::cout << "return_void" << std::endl; }
};
void resume() { std::cout << "resume" << std::endl; handle.resume(); }
std::coroutine_handle<promise_type> handle;
};
TaskPromise task_func() {
std::cout << "task first run" << std::endl;
co_await Awaiter<false>{};
std::cout << "task resume" << std::endl;
}
int main() {
auto promise = task_func();
promise.resume();
return 0;
}Running this code produces the following output:
get_return_object
initial_suspend
await_ready: 1
await_resume
task first run
await_ready: 0
await_suspend
resume
await_resume
task resume
return_void
final_suspend
await_ready: 1
await_resumeGenerator Example (Fibonacci)
#include <iostream>
#include <coroutine>
template<typename T>
struct Generator {
struct promise_type {
Generator get_return_object() { return Generator{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
void return_value(T t) noexcept { v = t; }
std::suspend_always yield_value(T t) { v = t; return {}; }
T v{};
};
bool has_next() { return !handle.done(); }
size_t next() { handle.resume(); return handle.promise().v; }
std::coroutine_handle<promise_type> handle;
};
Generator<size_t> fib(size_t max_count) {
co_yield 1;
size_t a = 0, b = 1, count = 0;
while (++count < max_count - 1) {
co_yield a + b;
b = a + b;
a = b - a;
}
co_return a + b;
}
int main() {
size_t max_count = 10;
auto generator = fib(max_count);
size_t i = 0;
while (generator.has_next()) {
std::cout << "No." << ++i << ": " << generator.next() << std::endl;
}
return 0;
}Output:
No.1: 1
No.2: 1
No.3: 2
No.4: 3
No.5: 5
No.6: 8
No.7: 13
No.8: 21
No.9: 34
No.10: 55Task Framework Example
#include <iostream>
#include <functional>
#include <deque>
#include <optional>
#include <coroutine>
#include <thread>
template<typename T>
class Task {
public:
struct promise_type;
using promise_handle_t = std::coroutine_handle<promise_type>;
explicit Task(promise_handle_t h) : handle(h) {}
Task(Task&& t) noexcept : handle(std::exchange(t.handle, {})) {}
~Task() { if (handle) handle.destroy(); }
struct promise_type {
Task get_return_object() { return Task(promise_handle_t::from_promise(*this)); }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
void return_value(T t) { data_ = t; notify_callbacks(); }
void on_completed(std::function<void(T)>&& cb) {
if (data_) cb(*data_); else callbacks_.push_back(std::move(cb));
}
T get() { return *data_; }
private:
void notify_callbacks() { for (auto& cb : callbacks_) cb(*data_); callbacks_.clear(); }
std::optional<T> data_;
std::deque<std::function<void(T)>> callbacks_;
};
T get_result() { return handle.promise().get(); }
void then(std::function<void(T)>&& cb) { handle.promise().on_completed(std::move(cb)); }
private:
promise_handle_t handle;
};
Task<int> task1() { std::cout << "task1 run" << std::endl; co_return 1; }
Task<int> task2() { std::cout << "task2 run" << std::endl; co_return 2; }
Task<int> call_task() {
std::cout << "call_task" << std::endl;
int d1 = co_await task1();
std::cout << "call_task task1 data: " << d1 << std::endl;
int d2 = co_await task2();
std::cout << "call_task task2 data: " << d2 << std::endl;
co_return d1 + d2;
}
int main() {
Task<int> t = call_task();
t.then([](int data){ std::cout << "call_task data: " << data << std::endl; });
return 0;
}Running this program prints:
call_task
task1 run
call_task task1 data: 1
task2 run
call_task task2 data: 2
call_task data: 3Effort is made to become the most understandable "C++20 coroutine" principle analysis article on the internet.
References
Lewis Baker’s coroutine series: https://lewissbaker.github.io/
cppreference on coroutines: https://en.cppreference.com/w/cpp/language/coroutines
cppcoro library: https://github.com/lewissbaker/cppcoro
folly coro: https://github.com/facebook/folly/tree/main/folly/experimental/coro
Alibaba async_simple: https://github.com/alibaba/async_simple
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
