Last active
September 14, 2023 16:02
-
-
Save jemand2001/adb0cd09460bdcc44ca50e346eb7599c to your computer and use it in GitHub Desktop.
monadic std::expected using coroutines
This file contains hidden or 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
| #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'; | |
| } |
This file contains hidden or 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
| // 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