Created
October 31, 2021 22:24
-
-
Save kopp/6b83032d3bf9710b2346d2debb09ffdc to your computer and use it in GitHub Desktop.
C++20 Coroutines with cout for the intermediate steps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (c) 2021 kopp, MIT licensed | |
// see https://en.cppreference.com/w/cpp/language/coroutines | |
// works with g++ 11.1 with -std=c++20 | |
#include <coroutine> | |
#include <iostream> | |
#include <string> | |
struct Promise; // forward declaration | |
using Handle = std::coroutine_handle<Promise>; | |
struct ReturnType | |
{ | |
Handle coroutine_handle; | |
ReturnType(Promise& promise): coroutine_handle(Handle::from_promise(promise)) { std::cout << "ReturnType::constructor on " << this << " for promise at " << &promise << "\n"; } | |
ReturnType(const ReturnType& other) = delete; | |
ReturnType& operator= (const ReturnType& other) = delete; | |
ReturnType& operator= (ReturnType&& other) = delete; | |
ReturnType(ReturnType&& other) = delete; | |
~ReturnType() { std::cout << "ReturnType::destructor\n"; } | |
int get_value_in_promise(); // implemented below since Promise is incomplete here | |
void resume() { coroutine_handle.resume(); std::cout << "ReturnType::resume\n"; } | |
}; | |
struct Promise | |
{ | |
// In order to be able to modify the ReturnType later | |
// The following methods need to be provided so that the | |
// function can be a coroutine using co_await: | |
// initial_suspend | |
// final_suspend | |
// unhandled_exception | |
// get_return_object | |
std::suspend_never initial_suspend() { std::cout << "Promise::initial_suspend\n"; return {}; } | |
std::suspend_never final_suspend() noexcept { std::cout << "Promise::final_suspend\n"; return {}; } | |
void unhandled_exception() {} | |
ReturnType get_return_object() { std::cout << "Promise::get_return_object\n"; return {*this}; } | |
// Additionally, the following methods need to be provided so | |
// that the coroutine can use co_return: | |
// return_value | |
// The return_value can return whatever it likes -- to pass a | |
// result to the caller, it has to communicate this to the | |
// ReturnType object that get_return_type produced initially. | |
// A simple way to do so, is, to store the value here and make | |
// the return value retrieve it from a "shared" location. | |
void return_value(int i) { value = i; std::cout << "Promise::return_value at " << this << " set to " << i << "\n"; } | |
Promise() { std::cout << "Promise::constructor at " << this << "\n"; } | |
~Promise() { std::cout << "Promise::destructor of " << this << "\n"; } | |
int value{99}; | |
}; | |
int ReturnType::get_value_in_promise() | |
{ | |
const auto value = coroutine_handle.promise().value; | |
std::cout << "ReturnType::get_value_in_promise at " << this << " for promise at " << &(coroutine_handle.promise()) << ": " << value << "\n"; | |
return value; | |
} | |
struct Parameter | |
{ | |
int integer; | |
Parameter(int i): integer(i) { std::cout << "Parameter::constructor at " << this << "\n"; } | |
Parameter(const Parameter& other): integer(other.integer) { std::cout << "Parameter::copy_constructor to " << this << "\n"; } | |
}; | |
// For the coroutine to be well-formed, it is necessary, that the | |
// Promise can be determinede from its return type. | |
// Simplest case is to just make the Promise a member type of the | |
// return type with name `promise_type`. | |
// Alternatively just define the trait: | |
template<> | |
struct std::coroutine_traits<ReturnType, Parameter> { | |
using promise_type = Promise; | |
}; | |
// This is the actual coroutine: A functoin that uses a `co_...` | |
// statement and has a return type that satisfies some requirements. | |
ReturnType coroutine(Parameter param) | |
{ | |
std::cout << "coroutine body\n"; | |
/* | |
std::cout << "coroutine body: before co_await suspend_never\n"; | |
co_await std::suspend_never{}; | |
std::cout << "coroutine body: after co_await suspend_never\n"; | |
std::cout << "coroutine body: before co_await suspend_always\n"; | |
co_await std::suspend_always{}; | |
std::cout << "coroutine body: after co_await suspend_always\n"; | |
*/ | |
std::cout << "coroutine body: before co_return\n"; | |
co_return param.integer + 7; | |
} | |
int main() | |
{ | |
std::cout << "caller: before executing the coroutine\n"; | |
ReturnType returned_value = coroutine({42}); | |
const auto value = returned_value.get_value_in_promise(); // accesses value in destructed object | |
std::cout << "got value: " << value << "\n"; | |
std::cout << "caller: after executing the coroutine\n"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment