Question
How would std::optional need to be modified in order to make it a monad via coroutines?
There's not really much when searching for c++coroutine "optional", but from the final part of this answer I'd deduce that std::optional
can't be made to work with the coroutine below non-intrusively, i.e. without changing std::optional
. However, I want to understand the details of this fact.
Here's the coroutine:
template<typename T>
std::optional<T> plus(std::optional<T> a, std::optional<T> b) {
co_return (co_await a) + (co_await b);
}
By "work with the coroutine" I mean that this should pass:
assert(plus(std::make_optional(3), std::make_optional(4)) == std::make_optional(7));
assert(plus(std::make_optional(3), std::nullopt) == std::nullopt);
assert(plus(std::nullopt, std::make_optional(4)) == std::nullopt);
I guess I'm a bit confused by the fact that std::optional
plays at the same time several roles
- type return type of the coroutine, because it is... the return type of the coroutine
- the awaitable, because it is operand to
co_await
- the container of the value that is passed to
co_return
(if neitherco_await
has suspended yet because its operand wasstd::nullopt
)
and I'm not sure which of these roles, if any, implies that std::optional
would need to be modified.
Especially 1 and 3 together are most confusing to me, because the coroutine should return the object via std_optional<T> promise_type::get_return_object()
, but at that time the returned object is empty (or is it?), but then the co_return
, if the code gets to that point, would need to alter that very object, but what would void promise_type::return_value(auto v)
do to make that happen?
Below is some attempt to reason about the first part of the question (i.e. "does std::optional
need to be modified?") and here is a failed attempt at implementing a std_optional
alternative to std::optional
.
In the coroutine body, both co_await
expression will/won't cause suspension depending on whether their argument is/isn't empty; whether it is empty is determined by the await_ready
member function of the awaitable (and the value returned to the coroutine if the optional isn't empty would be available via await_resume
of the same awaitable).
struct awaitable {
bool await_ready() const { return o.has_value(); }
auto await_resume() { return o.value(); }
//...
}
Now, despite the operands to co_await
are std::optional
, that per se doesn't mean std::optional
need to be made an awaitable (which would be intrusive, as one would have to open std::optional
and define the 3 await_*
functions in it), but one can just define a co_await
operator with a custom awaitable:
template<typename T>
auto operator co_await(std_optional<T> o) {
struct awaitable {
std_optional<T> o;
bool await_ready() const { return o.has_value(); }
void await_suspend(std::coroutine_handle<>) {}
auto await_resume() { return o.value(); }
};
return awaitable{o};
}
As far as the two co_await
s expressions in plus
are concerned, the promise type too can be provided in a way that is not intrusive to std::optional
:
template<typename T, typename ...Args>
struct std::coroutine_traits<std_optional<T>, Args...> {
struct promise_type {
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std_optional<T> get_return_object() { return {}; }
void unhandled_exception() { throw; }
void return_value(auto) {}
T val;
};
};
Indeed, with this just in place, we can see that a call like this
auto o = plus(std::make_optional(3), std::make_optional(4))
will result in evaluating (co_await a) + (co_await b)
to 7
, which is correct, but what does not happen is that that 7
should be made available in the returned std::optional
.
My understanding is that putting 7
in the std::optional
returned to plus
's caller is return_value
's job.
(¹) On the other hand this proposal by Barry Revzin incidentally mentions the same thing (but for std::expected
), saying that it can be made to work in a fully-conformant way, but I'm not sure if that's implying that no change to std::expected
would be needed or what.