Skip to content

Instantly share code, notes, and snippets.

@jemand2001
Last active September 14, 2023 16:02
Show Gist options
  • Select an option

  • Save jemand2001/adb0cd09460bdcc44ca50e346eb7599c to your computer and use it in GitHub Desktop.

Select an option

Save jemand2001/adb0cd09460bdcc44ca50e346eb7599c to your computer and use it in GitHub Desktop.
monadic std::expected using coroutines
#include <coroutine>
#include <expected>
#include <optional>
#include <iostream>
#include "return_object_holder.hpp"
using std::coroutine_traits;
template <typename R, typename E>
using p_t = coroutine_traits<std::expected<R, E>>::promise_type;
template <typename E>
struct expectation_failed {
E value;
};
template <typename R, typename E>
struct coroutine_traits<std::expected<R, E>> {
struct promise_type {
return_object_holder<std::expected<R, E>> *value;
auto get_return_object() { return make_return_object_holder(value); }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(const R &ret) { value->emplace(std::move(ret)); }
void return_value(const std::unexpected<E> &err) { value->emplace(std::move(err)); }
void unhandled_exception() {
try {
std::rethrow_exception(std::current_exception());
} catch (expectation_failed<E>& err) {
value->emplace(std::unexpected{err.value});
}
}
};
};
template <typename R, typename E>
struct expected_awaiter {
std::expected<R, E> value;
bool await_ready() { return value.has_value(); }
// template <typename OuterRet=R>
// std::coroutine_handle<p_t<OuterRet, E>>
void await_suspend(auto& h) {
h.promise().value->emplace(std::unexpected{value.error()});
}
R await_resume() {
return value.value();
}
};
template <typename R, typename E>
expected_awaiter<R, E> operator co_await(const std::expected<R, E>& expected) {
// std::cout << "inside co_await the awaited value is " << (expected.has_value() ? "" : "not ") << "present\n";
expected_awaiter<R, E> awaiter{expected};
// std::cout << expected.has_value();
return awaiter;
}
std::expected<int, const char *> test1() {
std::cout << "in test1\n";
co_return 12;
}
std::expected<int, const char *> test2() {
std::cout << "in test2\n";
int x = co_await test1();
std::cout << "after test1\n";
std::cout << "test1 returned " << x << '\n';
co_return std::unexpected{"hello there!"};
}
std::expected<double, const char *> test3() {
std::cout << "in test3\n";
auto x = co_await test2();
std::cout << "after test3\n";
co_return x + 0.2;
}
int main() {
auto t = test3();
std::cout << "test3().has_value(): " << std::boolalpha << t.has_value() << '\n';
std::cout << "test3().error(): " << t.error() << '\n';
}
// slightly modified from
// https://github.com/toby-allsopp/coroutine_monad/blob/master/return_object_holder.h
#ifndef RETURN_OBJECT_HOLDER_H
#define RETURN_OBJECT_HOLDER_H
#include <optional>
#include <utility>
#include <iostream>
// An object that starts out unitialized. Initialized by a call to emplace.
template <typename T>
using deferred = std::optional<T>;
template <typename T>
struct return_object_holder {
// The staging object that is returned (by copy/move) to the caller of the coroutine.
deferred<T> stage;
return_object_holder*& p;
// When constructed, we assign a pointer to ourselves to the supplied reference to
// pointer.
return_object_holder(return_object_holder*& p) : stage{}, p(p) { p = this; }
// Copying doesn't make any sense (which copy should the pointer refer to?).
return_object_holder(return_object_holder const&) = delete;
// To move, we just update the pointer to point at the new object.
return_object_holder(return_object_holder&& other)
: stage(std::move(other.stage)), p(other.p) {
p = this;
}
// Assignment doesn't make sense.
void operator=(return_object_holder const&) = delete;
void operator=(return_object_holder&&) = delete;
// A non-trivial destructor is required until
// https://bugs.llvm.org//show_bug.cgi?id=28593 is fixed.
// ~return_object_holder() { std::cout << ""; }
// Construct the staging value; arguments are perfect forwarded to T's constructor.
template <typename... Args>
void emplace(Args&&... args) {
// std::cout << this << ": emplace" << std::endl;
stage.emplace(std::forward<Args>(args)...);
}
// We assume that we will be converted only once, so we can move from the staging
// object. We also assume that `emplace` has been called at least once.
operator T() {
// std::cout << this << ": operator T" << std::endl;
return std::move(*stage);
}
};
template <typename T>
auto make_return_object_holder(return_object_holder<T>*& p) {
return return_object_holder<T>{p};
}
#endif // RETURN_OBJECT_HOLDER_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment