Created on 2026-01-11.00:00:00 last changed 1 week ago
Proposed resolution:
Update [exec.when.all] as follows:
[...]
and where
make-stateis the following exposition-only class template:enum class disposition { started, error, stopped }; // exposition only template<class Rcvr> struct make-state { template<class... Sndrs> auto operator()(auto, auto, Sndrs&&... sndrs) const noexcept { using values_tuple = see below; using errors_variant = see below; using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>; struct state-type { void arrive(Rcvr& rcvr) noexcept { // exposition only if (0 == --count) { complete(rcvr); } } void complete(Rcvr& rcvr) noexcept; // exposition only atomic<size_t> count{sizeof...(sndrs)}; // exposition only inplace_stop_source stop_src{}; // exposition only atomic<disposition> disp{disposition::started}; // exposition only errors_variant errors{}; // exposition only values_tuple values{}; // exposition only optional<stop_callback> on_stop{nullopt}; // exposition only }; return state-type{}; } };[...]
The behavior of algorithms in the standard is described in terms of exposition-only machinery
[exec.snd.expos].
Particularly the behavior of
std::execution::connect
[exec.connect]
for such algorithms is described in terms of
basic-sender::connect
which is conditionally
noexcept:
The expression in the
noexceptclause of theconnectmember function ofbasic-senderis:is_nothrow_constructible_v<basic-operation<Self, Rcvr>, Self, Rcvr>
The constructor of
basic-operation
is also conditionally
noexcept:
The expression in the
noexceptclause of the constructor ofbasic-operationis:is_nothrow_constructible_v<basic-state<Self, Rcvr>, Self, Rcvr> && noexcept(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>()))
This cascade of chasing conditional
noexcept
doesn't end there, instead moving to
basic-state:
The expression in the
noexceptclause of the constructor ofbasic-stateisis_nothrow_move_constructible_v<Rcvr> && nothrow-callable>decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&> && (same_as<state-type<Sndr, Rcvr>, get-state-result> || is_nothrow_constructible_v<state-type<Sndr, Rcvr>, get-state-result>)where
get-state-resultis
call-result-t<decltype(impls-for<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>.
This finally implicates something in
[exec.when.all]
as we must now look to the
noexcept
clause of its
get-state implementation:
The member
impls-for<when_all_t>::get-stateis initialized with a callable object equivalent to the following lambda expression:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(noexcept(e)) -> decltype(e) { return e; }where
eis the expressionstd::forward<Sndr>(sndr).apply(make-state<Rcvr>())and where
make-stateis the following exposition-only class template:enum class disposition { started, error, stopped }; // exposition only template<class Rcvr> struct make-state { template<class... Sndrs> auto operator()(auto, auto, Sndrs&&... sndrs) const { using values_tuple = see below; using errors_variant = see below; using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>; struct state-type { void arrive(Rcvr& rcvr) noexcept { // exposition only if (0 == --count) { complete(rcvr); } } void complete(Rcvr& rcvr) noexcept; // exposition only atomic<size_t> count{sizeof...(sndrs)}; // exposition only inplace_stop_source stop_src{}; // exposition only atomic<disposition> disp{disposition::started}; // exposition only errors_variant errors{}; // exposition only values_tuple values{}; // exposition only optional<stop_callback> on_stop{nullopt}; // exposition only }; return state-type{}; } };
Notice that the overloaded function call operator does not have a
noexcept
clause meaning connecting a
std::execution::when_all
sender is never
noexcept.
This is a library wording issue because if the design intent was for the above-described operation to be unconditionally
noexcept(false)
there would be no reason for
get-state
to feature
noexcept(noexcept(e))
(since that expression would've been intended to be a contradiction).
Moreover in returning a default-initialized
state-type
the above-discussed function call operator performs the following operations:
atomic<size_t>
with an initial value, which does not throw,
inplace_stop_source,
which is
noexcept(true)
per
[stopsource.inplace.mem],
atomic<disposition>
with an initial value, which does not throw,
monostate
alternative, which does not throw, and
Since none of these throw the overloaded function call operator can be made
noexcept(true).
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2026-01-11 21:21:52 | admin | set | messages: + msg15850 |
| 2026-01-11 00:00:00 | admin | create | |