Skip to content

Instantly share code, notes, and snippets.

@kopp
Created October 31, 2021 22:24
Show Gist options
  • Save kopp/6b83032d3bf9710b2346d2debb09ffdc to your computer and use it in GitHub Desktop.
Save kopp/6b83032d3bf9710b2346d2debb09ffdc to your computer and use it in GitHub Desktop.
C++20 Coroutines with cout for the intermediate steps
// (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