The idea behind a deferred future (achieved only by calling std::async
with std::launch::deferred
flag) is that the callback is called only when someone tries to wait or to pull the futuristic value or exception of the future. by then the callback isn't executed.
What happens if I attach a continuation to a deferred future with std::future::then
? the deferred future is lost (then
invalidates the future) and a new future is returned instead.
In this case, according to the standard, what should happen? is the new future is a deferred future as well? would it just deadlock? this question is not addressed in the latest documentation.
This, in my opinion, seems to be a bug in the TS. Or at least an underdocumented trap.
Here is the text from the TS:
template <class F> see below then(F&& func);
Requires:
INVOKE(DECAY_COPY (std::forward<F>(func)), std::move(*this)) shall be a valid expression.
Effects:
The function creates a shared state that is associated with the returned future object. Additionally,
When the object's shared state is ready, the continuation
INVOKE(DECAY_COPY(std::forward<F>(func)), std::move(*this))
is called on an unspecified thread of execution with the call toDECAY_COPY()
being evaluated in the thread that called then.Any value returned from the continuation is stored as the result in the shared state of the resulting future. Any exception propagated from the execution of the continuation is stored as the exceptional result in the shared state of the resulting future.
Returns:
When
result_of_t<decay_t<F>(future<R>)>
isfuture<R2>
, for some type R2, the function returnsfuture<R2>
. Otherwise, the function returnsfuture<result_of_t<decay_t<F>(future<R>)>>
. [ Note: The rule above is referred to as implicit unwrapping. Without this rule, the return type of then taking a callable returning afuture<R>
would have beenfuture<future<R>>
. This rule avoids such nested future objects. The type off2
below isfuture<int>
and notfuture<future<int>>
:[ Example:
future<int> f1 = g(); future<int> f2 = f1.then([](future<int> f) { future<int> f3 = h(); return f3; });
— end example ]
— end note ]
Postconditions:
valid() == false
on the original future.valid() == true
on the future returned from then. [ Note: In case of implicit unwrapping, the validity of the future returned from thenfunc cannot be established until after the completion of the continuation. If it is not valid, the resulting future becomes ready with an exception of typestd::future_error
, with an error condition ofstd::future_errc::broken_promise
. — end note ]
There is no special case for a deferred future task. If that deferred future task is not ready before calling .then
, there is no way for it to become ready, so there is no way for the decayed copy of func
to be invoked.
The text for shared_future
is similar; there, you can still cause the shared_future
to become ready after calling .then
however.
If this is intended; that .then
on a not-ready deferred unique future will result in a return value of a future
that can never be made ready -- this should be explicit in the TS/standard. If this is not intended, the standard text needs to be changed.
Note that these changes do not appear in the N4762 draft standard published in 2018.
I am not sure how the standard should fix this; the .then
semantics are reasonable for a shared_future
but not for a future
, and differing semantics would be surprising.
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