34 Execution control library [exec]

34.9 Senders [exec.snd]

34.9.12 Sender adaptors [exec.adapt]

34.9.12.11 execution​::​when_all [exec.when.all]

when_all and when_all_with_variant both adapt multiple input senders into a sender that completes when all input senders have completed.
when_all only accepts senders with a single value completion signature and on success concatenates all the input senders' value result datums into its own value completion operation.
when_all_with_variant(sndrs...) is semantically equivalent to when_all(into_variant(sndrs)...), where sndrs is a pack of subexpressions whose types model sender.
The names when_all and when_all_with_variant denote customization point objects.
Let sndrs be a pack of subexpressions, let Sndrs be a pack of the types decltype((sndrs))..., and let CD be the type common_type_t<decltype(get-domain-early(sndrs))...>.
The expressions when_all(sndrs...) and when_all_with_variant(sndrs...) are ill-formed if any of the following is true:
  • sizeof...(sndrs) is 0, or
  • (sender<Sndrs> && ...) is false, or
  • CD is ill-formed.
The expression when_all(sndrs...) is expression-equivalent to: transform_sender(CD(), make-sender(when_all, {}, sndrs...))
The exposition-only class template impls-for ([exec.snd.general]) is specialized for when_all_t as follows: namespace std::execution { template<> struct impls-for<when_all_t> : default-impls { static constexpr auto get-attrs = see below; static constexpr auto get-env = see below; static constexpr auto get-state = see below; static constexpr auto start = see below; static constexpr auto complete = see below; }; }
The member impls-for<when_all_t>​::​get-attrs is initialized with a callable object equivalent to the following lambda expression: [](auto&&, auto&&... child) noexcept { if constexpr (same_as<CD, default_domain>) { return empty_env(); } else { return MAKE-ENV(get_domain, CD()); } }
The member impls-for<when_all_t>​::​get-env is initialized with a callable object equivalent to the following lambda expression: []<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept { return JOIN-ENV( MAKE-ENV(get_stop_token, state.stop_src.get_token()), get_env(rcvr)); }
The member impls-for<when_all_t>​::​get-state is initialized with a callable object equivalent to the following lambda expression: []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(e) -> decltype(e) { return e; } where e is the expression std::forward<Sndr>(sndr).apply(make-state<Rcvr>()) and where make-state is the following exposition-only class template: template<class Sndr, class Env> concept max-1-sender-in = sender_in<Sndr, Env> && // exposition only (tuple_size_v<value_types_of_t<Sndr, Env, tuple, tuple>> <= 1); enum class disposition { started, error, stopped }; // exposition only template<class Rcvr> struct make-state { template<max-1-sender-in<env_of_t<Rcvr>>... Sndrs> auto operator()(auto, auto, Sndrs&&... sndrs) const { using values_tuple = see below; using errors_variant = see below; using stop_callback = stop_callback_of_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{}; } };
Let copy-fail be exception_ptr if decay-copying any of the child senders' result datums can potentially throw; otherwise, none-such, where none-such is an unspecified empty class type.
The alias values_tuple denotes the type tuple<value_types_of_t<Sndrs, env_of_t<Rcvr>, decayed-tuple, optional>...> if that type is well-formed; otherwise, tuple<>.
The alias errors_variant denotes the type variant<none-such, copy-fail, Es...> with duplicate types removed, where Es is the pack of the decayed types of all the child senders' possible error result datums.
The member void state-type​::​complete(Rcvr& rcvr) noexcept behaves as follows:
  • If disp is equal to disposition​::​started, evaluates: auto tie = []<class... T>(tuple<T...>& t) noexcept { return tuple<T&...>(t); }; auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); }; on_stop.reset(); apply( [&](auto&... opts) noexcept { apply(set, tuple_cat(tie(*opts)...)); }, values);
  • Otherwise, if disp is equal to disposition​::​error, evaluates: on_stop.reset(); visit( [&]<class Error>(Error& error) noexcept { if constexpr (!same_as<Error, none-such>) { set_error(std::move(rcvr), std::move(error)); } }, errors);
  • Otherwise, evaluates: on_stop.reset(); set_stopped(std::move(rcvr));
The member impls-for<when_all_t>​::​start is initialized with a callable object equivalent to the following lambda expression: []<class State, class Rcvr, class... Ops>( State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void { state.on_stop.emplace( get_stop_token(get_env(rcvr)), on-stop-request{state.stop_src}); if (state.stop_src.stop_requested()) { state.on_stop.reset(); set_stopped(std::move(rcvr)); } else { (start(ops), ...); } }
The member impls-for<when_all_t>​::​complete is initialized with a callable object equivalent to the following lambda expression: []<class Index, class State, class Rcvr, class Set, class... Args>( this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void { if constexpr (same_as<Set, set_error_t>) { if (disposition::error != state.disp.exchange(disposition::error)) { state.stop_src.request_stop(); TRY-EMPLACE-ERROR(state.errors, std::forward<Args>(args)...); } } else if constexpr (same_as<Set, set_stopped_t>) { auto expected = disposition::started; if (state.disp.compare_exchange_strong(expected, disposition::stopped)) { state.stop_src.request_stop(); } } else if constexpr (!same_as<decltype(State::values), tuple<>>) { if (state.disp == disposition::started) { auto& opt = get<Index::value>(state.values); TRY-EMPLACE-VALUE(complete, opt, std::forward<Args>(args)...); } } state.arrive(rcvr); } where TRY-EMPLACE-ERROR(v, e), for subexpressions v and e, is equivalent to: try { v.template emplace<decltype(auto(e))>(e); } catch (...) { v.template emplace<exception_ptr>(current_exception()); } if the expression decltype(auto(e))(e) is potentially throwing; otherwise, v.template emplace<decltype(auto(e))>(e); and where TRY-EMPLACE-VALUE(c, o, as...), for subexpressions c, o, and pack of subexpressions as, is equivalent to: try { o.emplace(as...); } catch (...) { c(Index(), state, rcvr, set_error, current_exception()); return; } if the expression decayed-tuple<decltype(as)...>{as...} is potentially throwing; otherwise, o.emplace(
as...)
.
The expression when_all_with_variant(sndrs...) is expression-equivalent to: transform_sender(CD(), make-sender(when_all_with_variant, {}, sndrs...));
Given subexpressions sndr and env, if sender-for<decltype((sndr)), when_all_with_variant_t> is false, then the expression when_all_with_variant.transform_sender(sndr, env) is ill-formed; otherwise, it is equivalent to: auto&& [_, _, ...child] = sndr; return when_all(into_variant(std::forward_like<decltype((sndr))>(child))...);
[Note 1: 
This causes the when_all_with_variant(sndrs...) sender to become when_all(into_variant(sndrs)...) when it is connected with a receiver whose execution domain does not customize when_all_with_variant.
— end note]