Created on 2025-08-31.00:00:00 last changed 1 week ago
Proposed resolution:
In [task.state], add
exposition-only data members result and
error to the exposition-only class
state:
namespace std::execution {
template<class T, class Environment>
template<receiver Rcvr>
class task<T, Environment>::state { // exposition only
...
Environment environment; // exposition only
optional<T> result; // exposition only; present only if is_void_v<T> is false
exception_ptr error; // exposition-only
};
}
Remove the exposition-only data members
result and errors from
the class promise_type in
[task.promise]:
namespace std::execution {
template<class T, class Environment>
class task<T, Environment>::promise_type {
...
stop_token_type token; // exposition only
optional<T> result; // exposition only; present only if is_void_v<T> is false
error-variant errors; // exposition only
};
}
The definition of error-variant isn't needed, i.e., remove [task.promise] paragraph 2:
-2-error-variantis avariant<monostate, remove_cvref_t<E>...>, with duplicate types removed, whereE...are template arguments of the specialization ofexecution::completion_signaturesdenoted byerror_types.
In [task.promise] change paragraph 7 to use the members added to state:
auto final_suspend() noexcept-7- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the completion of the asynchronous operation associated with
STATE(*this)by invoking. Letstbe a reference toSTATE(*this). The asynchronous completion first destroys the coroutine frame usingst.handle.destroy()and then invokes:
- -7.1-
set_error(std::move(ifRCVR(*this)st.rcvr), std::move(est.error))errors.index()is greater than zero andeis the value held byerrorsbool(st.error)istrue, otherwise- -7.2-
set_value(std::move(ifRCVR(*this)st.rcvr))is_void_v<T>istrue, and otherwise- -7.3-
set_value(std::move(.RCVR(*this)st.rcvr), *std::move(st.result))
Change the specification of yield_value to destroy the coroutine frame before invoking the set_error completion, i.e., change [task.promise] paragraph 9:
-9- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the calling coroutine to be suspended and then completes the asynchronous operation associated with
STATE(*this)by. Letstbe a reference toSTATE(*this). Then the asynchronous operation completes by first destroying the coroutine frame usingst.handle.destroy()and then invokingset_error(std::move(.RCVR(*this)st.rcvr), Cerr(std::move(err.error)))
Change the specification of unhandled_stopped to destroy the coroutine frame before invoking the set_stopped completion, i.e., change [task.promise] paragraph 13:
coroutine_handle<> unhandled_stopped();-13- Effects: Completes the asynchronous operation associated with
STATE(*this)by. Letstbe a reference toSTATE(*this). The asynchronous operation is completed by first destroying the coroutine frame usingst.handle.destroy()and then invokingset_stopped(std::move(.RCVR(*this)st.rcvr))
Proposed resolution:
In [task.state], add
exposition-only data members result and
error to the exposition-only class
state:
namespace std::execution {
template<class T, class Environment>
template<receiver Rcvr>
class task<T, Environment>::state { // exposition only
...
Environment environment; // exposition only
optional<T> result; // exposition only; present only if is_void_v<T> is false
exception_ptr error; // exposition-only
};
}
Remove the exposition-only data members
result and errors from
the class promise_type in
[task.promise]:
namespace std::execution {
template<class T, class Environment>
class task<T, Environment>::promise_type {
...
stop_token_type token; // exposition only
optional<T> result; // exposition only; present only if is_void_v<T> is false
error-variant errors; // exposition only
};
}
The definition of error-variant isn't needed, i.e., remove [task.promise] paragraph 2:
-2-error-variantis avariant<monostate, remove_cvref_t<E>...>, with duplicate types removed, whereE...are template arguments of the specialization ofexecution::completion_signaturesdenoted byerror_types.
In [task.promise] change paragraph 7 to use the members added to state:
auto final_suspend() noexcept-7- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the completion of the asynchronous operation associated with
STATE(*this)by invoking. Letstbe a reference toSTATE(*this). The asynchronous completion first destroys the coroutine frame usingst.handle.destroy()and then invokes:
- -7.1-
set_error(std::move(ifRCVR(*this)st.rcvr), std::move(est.error))errors.index()is greater than zero andeis the value held byerrorsbool(st.error)istrue, otherwise- -7.2-
set_value(std::move(ifRCVR(*this)st.rcvr))is_void_v<T>istrue, and otherwise- -7.3-
set_value(std::move(.RCVR(*this)st.rcvr), *std::move(st.result))
Change the specification of yield_value to destroy the coroutine frame before invoking the set_error completion, i.e., change [task.promise] paragraph 9:
-9- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the calling coroutine to be suspended and then completes the asynchronous operation associated with
STATE(*this)by. Letstbe a reference toSTATE(*this). Then the asynchronous operation completes by first destroying the coroutine frame usingst.handle.destroy()and then invokingset_error(std::move(.RCVR(*this)st.rcvr), Cerr(std::move(err.error)))
Change the specification of unhandled_stopped to destroy the coroutine frame before invoking the set_stopped completion, i.e., change [task.promise] paragraph 13:
coroutine_handle<> unhandled_stopped();-13- Effects: Completes the asynchronous operation associated with
STATE(*this)by. Letstbe a reference toSTATE(*this). The asynchronous operation is completed by first destroying the coroutine frame usingst.handle.destroy()and then invokingset_stopped(std::move(.RCVR(*this)st.rcvr))
[ 2025-11-07; Jonathan provides improved wording ]
Incorporate change from LWG 4466 and add `_v` to `is_void`.
Still needs update w.r.t `unhandled_exception`.
[ Kona 2025-11-06 ]
SG1 think resolution looks good, but noted pre-existing issue that 7.3 doesn't use `std::move` on `*st.result`. (Would need to be `*std::move(st.result)` to handle optional<T&> correctly.)
LWG 4415 renames `uncaught_exception` to `unhandled_exception`.
In [task.state], add
exposition-only data members result and
error to the exposition-only class
state:
namespace std::execution {
template<class T, class Environment>
template<receiver Rcvr>
class task<T, Environment>::state { // exposition only
...
Environment environment; // exposition only
optional<T> result; // exposition only; present only if is_void_v<T> is false
exception_ptr error; // exposition-only
};
}
Remove the exposition-only data members
result and errors from
the class promise_type in
[task.promise]:
namespace std::execution {
template<class T, class Environment>
class task<T, Environment>::promise_type {
...
stop_token_type token; // exposition only
optional<T> result; // exposition only; present only if is_void_v<T> is false
error-variant errors; // exposition only
};
}
The definition of error-variant isn't needed, i.e., remove [task.promise] paragraph 2:
-2-error-variantis avariant<monostate, remove_cvref_t<E>...>, with duplicate types removed, whereE...are template arguments of the specialization ofexecution::completion_signaturesdenoted byerror_types.
In [task.promise] change paragraph 7 to use the members added to state:
auto final_suspend() noexcept-7- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the completion of the asynchronous operation associated with
STATE(*this)by invoking. Letstbe a reference toSTATE(*this). The asynchronous completion first destroys the coroutine frame usingst.handle.destroy()and then invokes:
- -7.1-
set_error(std::move(ifRCVR(*this)st.rcvr), std::move(est.error))errors.index()is greater than zero andeis the value held byerrorsbool(st.error)istrue, otherwise- -7.2-
set_value(std::move(ifRCVR(*this)st.rcvr))is_void<T>istrue, and otherwise- -7.3-
set_value(std::move(.RCVR(*this)st.rcvr), *st.result)
Change the specification of yield_value to destroy the coroutine frame before invoking the set_error completion, i.e., change [task.promise] paragraph 9:
-9- Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the calling coroutine to be suspended and then completes the asynchronous operation associated with
STATE(*this)by. Letstbe a reference toSTATE(*this). Then the asynchronous operation completes by first destroying the coroutine frame usingst.handle.destroy()and then invokingset_error(std::move(.RCVR(*this)st.rcvr), Cerr(std::move(err.error)))
Change the specification of unhandled_stopped to destroy the coroutine frame before invoking the set_stopped completion, i.e., change [task.promise] paragraph 13:
coroutine_handle<> unhandled_stopped();-13- Effects: Completes the asynchronous operation associated with
STATE(*this)by. Letstbe a reference toSTATE(*this). The asynchronous operation is completed by first destroying the coroutine frame usingst.handle.destroy()and then invokingset_stopped(std::move(.RCVR(*this)st.rcvr))
[ 2025-10-17; Reflector poll. ]
Set priority to 2 after reflector poll.
"P/R is incomplete - also needs to update at least `promise_type::uncaught_exception()`."
The specification of task doesn't spell out when the
coroutine frame is destroyed (i.e., when handle.destroy()
is called). As a result the lifetime of arguments passed to the
coroutine and/or of local variables in the coroutine body may be
longer than expected.
The intention is that the coroutine frame is destroyed before any
of the completion functions is called. One implication of this
requirement is that the result and error objects can't be stored
in the promise_type when the completion function is
called although the exposition-only members result
and errors imply exactly that. Instead the data
needs to be stored in the operation state object or it needs to be
moved to a different place before calling destroy().
The proposed resolution is to add a paragraph to the specification
of promise_type in [task.promise] that spells
out that the coroutine frame is destroyed before any of the completion
functions is called. To actually do that the exposition-only members
result and errors can't
remain as members of the promise_type. While writing
the relevant change it turned out that errors
is a variant which only ever stores an
exception_ptr (the other potential errors are immediately
reported via the awaiter return from yield_value).
Thus, the variant can be replaced with an
exception_ptr.
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2025-11-08 03:09:29 | admin | set | status: immediate -> open |
| 2025-11-08 02:50:59 | admin | set | messages: + msg15607 |
| 2025-11-08 02:50:59 | admin | set | status: new -> immediate |
| 2025-11-08 02:29:11 | admin | set | messages: + msg15605 |
| 2025-10-27 09:58:11 | admin | set | messages: + msg15443 |
| 2025-10-17 14:47:24 | admin | set | messages: + msg15232 |
| 2025-09-02 20:31:35 | admin | set | messages: + msg15013 |
| 2025-08-31 00:00:00 | admin | create | |