std::execution::when_all Is Never Nothrow ConnectableSection: 33.9.12.12 [exec.when.all] Status: New Submitter: Robert Leahy Opened: 2026-01-11 Last modified: 2026-02-18
Priority: 2
View all other issues in [exec.when.all].
View all issues with New status.
Discussion:
The behavior of algorithms in the standard is described in terms of exposition-only machinery
33.9.2 [exec.snd.expos].
Particularly the behavior of
std::execution::connect
33.9.10 [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
33.9.12.12 [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
32.3.9.3 [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).
[2026-02-18; Reflector poll.]
Set priority to 2 after reflector poll.
Wording needs additional tightening because value_type isn't a variant
and errors_variant does not use monostate.
Proposed resolution:
Update 33.9.12.12 [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{}; } };[...]