C++ coroutine — 3
Table of contents
Introduction
- Coroutines are a generalization of functions in C++
- They are designed to make writing asynchronous code much easier
Parts
promise_type
coroutine_handle
- awaiter
- passed to
co_wait
std::suspend_always
std::suspend_never
- passed to
Awaiter methods
bool await_ready(){}
- Whether the co_await expression suspends.
- if false is returned, then
await_suspend
is called, to (mostly) suspend
void await_suspend(){}
- May suspend the coroutine, or schedule the coroutine state for destruction
void await_resume(){}
- May return the result of the entire
co_await
expression
- May return the result of the entire
It is possible to create your own awaiters, things that can act as operands to the co_await operator, by setting up structs/classes that overload the co_await operator
struct My_Awaitable {
auto operator co_await() {
return std::suspend_always{};
}
};
Function
- Can be called
- Can return something
Coroutines
- Can be called
- Can return something
- Can be paused
- Can be resumed
When paused the function state is save on heap topically.
- local variables
- parameters
- pointer to resume this function
Lazy computation
auto f = some_coroutine(); // store computation info in f
// Use f to manipulate the computation
Keywords
C++ 20 introduces three keywords that help pause and resume coroutines
co_yield
: suspends the execution and returns a valueco_return
: completes execution and optionally returns a valueco_await
: suspends the execution until resumed
If a function has one of those keywords, it becomes a coroutine. There is no other special syntax for coroutines.
Which function can be a coroutine
It’s not every function in C++ that can be a coroutine. The functions below can’t be coroutines.
constexpr
functions- constructors
- destructors
- the main function
C++20 warnings
C++ 20 doesn’t provide actual usable coroutine types like Corolype
It provides the low level infrastructure to build them (promises, awaitables, coroutine handles,…
Building your own coroutine types is not recommended. It’s only reserved for hard core, highly experienced library developers who really know what they’re doing
It is expected that C++23 will provide high level coroutine types built into C++, ready to use just by including some headers
If you want to use them know, there are third party libraries that can help, like cppcoro and some others
See Also
#include <coroutine>
#include <iostream>
struct CoroType {
struct promise_type {
int m_value;
CoroType get_return_object() { return CoroType(this); }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept {
std::rethrow_exception(std::current_exception());
}
std::suspend_always yield_value(int val) {
m_value = val;
return {};
}
void return_value(int val) {
std::cout << "Returning value " << std::endl;
m_value = val;
}
// void return_void() { std::cout << "Returning void..." << std::endl; }
};
CoroType(promise_type* p)
: m_handle(std::coroutine_handle<promise_type>::from_promise(*p)) {}
~CoroType() {
std::cout << "Handle destroyed..." << std::endl;
m_handle.destroy();
}
std::coroutine_handle<promise_type> m_handle;
};
CoroType do_work() {
std::cout << "Starting the coroutine..." << std::endl;
co_yield 1;
co_yield 2;
co_yield 3;
co_return 4;
}
int main() {
auto task = do_work();
task.m_handle(); // This resumes the couroutine. When next suspension point
// is hit it pauses
std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
task.m_handle(); // This resumes the couroutine. When next suspension point
// is hit it pauses
std::cout << std::boolalpha;
std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
std::cout << "coro done : " << task.m_handle.done() << std::endl;
task.m_handle(); //
std::cout << std::boolalpha;
std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
std::cout << "coro done : " << task.m_handle.done() << std::endl;
task.m_handle(); //
std::cout << std::boolalpha;
std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
std::cout << "coro done : " << task.m_handle.done() << std::endl;
std::cout << "Done!" << std::endl;
return 0;
}
Possible output
Starting the coroutine...
value : 1
value : 2
coro done : false
value : 3
coro done : false
Returning value
value : 4
coro done : true
Done!
Handle destroyed...