Since the Coroutines TS has been accepted into C++20 at the ISO meeting at Kona, I started playing around with them a bit for myself. Clang already has decent support for coroutines but the implementation of the library support is still lacking. In particular, the Awaitable types such as std::future
, std::generator
, etc. have not been implemented yet.
Thus, I took it upon myself to make std::future
awaitable. I largely followed the talk by James McNellis at CppCon 2016, specifically this slide:
This being 2019, I actually had some trouble with the (presumably untested?) code on this slide:
operator co_await
is not a thing anymore? Instead one should use the optional await_transform
of the promise_type
. Not sure I got this right, though.then
continuation of the future captures the handle by value, but the resume
member function is not const-qualified. I worked around this by making the lambda mutable
.Also, then
and is_ready
are not available in std::future
but are part of std::experimental::future
which is still missing from my libc++ version. To avoid dealing with the Awaiter and to implement future continuations, I wrote a derived future class which is Awaitable and an Awaiter. It is my understanding that eventually both would also be true of std::future
. You can see my example on Compiler Explorer. It does compile.
However, it also does segfault. This happens in await_resume
when get()
is called. This is actually not surprising since valid()
returns false
at that point (making the call to get()
UB). I think this is because when then
is used to continue the future, the original future object is moved into the async future, thus invalidating the old future (*this
at the time await_resume
is called, so after the move). My implementation of then
is loosely inspired by this answer and this code I found on GitHub. Those may not be ideal, but cppreference explicitly states valid() == false
as a postcondition of calling then
, so I believe it is correct to move out of the original future.
What am I missing here? This "bug" seems present already in the above slide. How can I reconcile this issue? Does anybody know of a (working) existing implementation of an Awaitable future? Thanks.
As you mentioned yourself, the issue is because the future
has moved-from after calling .then()
. The trick is to move it back when it's ready. This can be done if the continuation passed to .then()
takes the future
, not the value it holds.
Here are the functions I took from your code and changed. I also changed them from passing things to std::async
as parameters to just capturing them, as this looks more intuitive to me, but this isn't the important change here.
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w())> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
fut.wait();
return w();
})};
}
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w(std::move(*this)))> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
return w(std::move(fut));
})};
}
void await_suspend(std::experimental::coroutine_handle<> ch) {
then([ch, this](auto fut) mutable {
*this = std::move(fut);
ch.resume();
});
}
BTW, VS2017 complains about having both set_exception()
and unhandled_exception()
in the promise type. I removed set_exception()
and changed unhandled_exception()
to this:
void unhandled_exception() {
_promise.set_exception(std::current_exception());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With