This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
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-01-11
Priority: Not Prioritized
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).
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{}; } };[...]