Before we start it should be mentioned that this repo is merely a combination of the following resources:
- CppCon 2016: James McNellis “Introduction to C++ Coroutines"
- How C++ coroutines work
- Notes I took while reading the resources above
Disclaimer: All the good parts in this repository should be accredited to James McNellis and Kirit Sælensminde (authors of the above) and all of the mistakes/bugs are due to insfuccient understanding of coroutines from my side.
I am writing the code in UNIX like system using Clang compiler (Apple LLVM version 9.1.0 (clang-902.0.39.2)). Hence I am using the #include <experimental/coroutine> header and the std::experimental namespace to use coroutines.
To compile a program using coroutines using Clang you will need to pass the following compilation flags -std=c++17 -fcoroutines-ts. The command should like something like this:
clang++ cooroutine_hello.cpp -std=c++17 -fcoroutines-ts -o coroutine_hello.out
The expression auto result = co_await expression is unfolded by the compiler as follows:
auto&& __a = expression;
if (!__a.await_ready()){ // ask the expression if its already calculated
__a.await_suspend(handle); //resume the execution
// Execution breakpoint, control can go back to the caller
}
auto result = __a.await_resume(); // fetch the resultAs a result to use the co_await we must have an expression that supports:
bool await_ready(): Decides if the control is returned to the caller after an await statementvoid await_suspend(handle)T await_resume()
Typical expressions that support the above are: std::suspend_always,std::suspend_never and std::future<T> (the latter only applies to MSVC compiler)
The struct promise_type should implement
void unhandled_exception(): What to do in case of an exceptionvoid return_void()orvoid return_value(T value_): The value can be retrieved if we use have a member variable in our promise and then docoroutine.promise().valueauto get_return_object()returns the parent objectinitial_suspend(): should I suspend after I create the functionfinal_suspend(): should I suspend after the function is executedyield_value(T const& current): If we want the promise to support theco_yieldkeyword
The caller and the coroutine communicate using a promise object
For the following example we will see how the compiler will unfold the function using the functions above:
1.Coroutine
resumable_thing counter(){
std::cout << "Counter: called\n";
for (unsigned i=1; ; ++i){
co_await std::experimental::suspend_always{}; // Control is returned to the caller
std::cout << "counter resumed #"<< i << " \n";
}
}2.The compiler will generate a struct:
struct counter_context{
resumable_thing::promise_type _promise;
unisgned i;
void* _instruction_pointer // make the function resumable
}3.The coroutine will be transformed
resumable_thing counter(){
/********* Compiler injected code *********
counter_context* _context = new counter_context();
_return = _context->_promise().get_return_object();
co_await _context->_promise().initial_suspend();
*********************************************/
std::cout << "Counter: called\n";
for (unsigned i=1; ; ++i){
co_await std::experimental::suspend_always{};
/********* Compiler injected code *********
Move this resumable_thing instance
Delete this resumable_thing instance
*********************************************/
std::cout << "counter resumed #"<< i << " \n";
}
/********* Compiler injected code *********
co_await _context->_promise().final_suspend();
delete _context;
*********************************************/
}co_return and co_yield keywords follow a similar logic with the above with only small changes.
- In a function
co_return valueis replaced by the compiler with_context->_promise().return_value(value). - In a function
co_yield valueis replaced by the compiler withco_await _context->_promise().yield_value(value).
Coroutine Standarization Proposal Resumamble Functions Yizhang82's blog