@@ -51,17 +51,15 @@ namespace STDEXEC
5151 // ! their value datums.
5252 // !
5353 // ! @c when_all is the canonical *parallel composition* primitive in the
54- // ! sender model. You give it one or more senders; it returns a single
54+ // ! sender model. You give it zero or more senders; it returns a single
5555 // ! sender that, when connected and started, starts *all* of the input
5656 // ! senders concurrently. When every input has completed, @c when_all's
5757 // ! sender completes with a value tuple that is the concatenation of every
5858 // ! input's value datums.
5959 // !
60- // ! If any one input fails or is stopped, @c when_all requests stop on the
61- // ! others (via an internal @c inplace_stop_source) and completes with
62- // ! that error (or with @c set_stopped). This makes @c when_all naturally
63- // ! fail-fast: as soon as one branch has gone bad, the rest are asked to
64- // ! wind down.
60+ // ! If any one input can fail or be stopped, @c when_all uses an internal
61+ // ! @c inplace_stop_source. An unhappy completion requests stop on the
62+ // ! other inputs before the combined operation completes.
6563 // !
6664 // ! @code{.cpp}
6765 // ! auto s = stdexec::when_all(
@@ -101,8 +99,7 @@ namespace STDEXEC
10199 // ! set_value_t(V1..., V2..., ..., Vn...) // concatenation of every input
102100 // ! set_error_t(Eij)... // union across all inputs
103101 // ! set_error_t(std::exception_ptr) // added if any decay-copy may throw
104- // ! set_stopped_t() // added if any input has it,
105- // ! // or if cancellation may happen
102+ // ! set_stopped_t() // added if any input has it
106103 // ! @endcode
107104 // !
108105 // ! The value datums of each input are decay-copied into the resulting
@@ -163,19 +160,34 @@ namespace STDEXEC
163160 // ! input has completed.
164161 // !
165162 // ! @tparam _Senders A pack of types each satisfying @c stdexec::sender.
166- // ! Must be non-empty. Each must have exactly one
167- // ! @c set_value_t completion signature in the
168- // ! ambient environment.
163+ // ! Each must have exactly one @c set_value_t completion
164+ // ! signature in the ambient environment.
169165 // !
170166 // ! @param __sndrs The senders to compose. Forwarded into the result.
171167 // !
172- // ! @returns A sender that, when connected and started, concurrently
173- // ! starts every input and value-completes with the
174- // ! concatenation of the input's value datums.
175- template <sender... _Senders>
176- constexpr auto operator ()(_Senders&&... __sndrs) const -> __well_formed_sender auto
168+ // ! @returns @c just() for no inputs, the input sender for one input, or a
169+ // ! sender that concurrently starts every input and concatenates
170+ // ! their value datums for two or more inputs.
171+ constexpr auto operator ()() const noexcept
172+ {
173+ return just ();
174+ }
175+
176+ template <sender _Sender>
177+ constexpr auto operator ()(_Sender&& __sndr) const noexcept (__nothrow_decay_copyable<_Sender>)
177178 {
178- return __make_sexpr<when_all_t >(__ (), static_cast <_Senders&&>(__sndrs)...);
179+ return static_cast <_Sender&&>(__sndr);
180+ }
181+
182+ template <sender _Sender0, sender _Sender1, sender... _Senders>
183+ constexpr auto operator ()(_Sender0&& __sndr0, _Sender1&& __sndr1, _Senders&&... __sndrs) const
184+ noexcept (__nothrow_decay_copyable<_Sender0, _Sender1, _Senders...>) -> __well_formed_sender
185+ auto
186+ {
187+ return __make_sexpr<when_all_t >(__ (),
188+ static_cast <_Sender0&&>(__sndr0),
189+ static_cast <_Sender1&&>(__sndr1),
190+ static_cast <_Senders&&>(__sndrs)...);
179191 }
180192 };
181193
@@ -394,8 +406,8 @@ namespace STDEXEC
394406 }
395407
396408 template <class _Env >
397- using __env_t = decltype (__when_all::__mk_env(__declval<_Env>(),
398- __declval<inplace_stop_source&>()));
409+ using __stoppable_env_t = decltype (__when_all::__mk_env(__declval<_Env>(),
410+ __declval<inplace_stop_source&>()));
399411
400412 template <class _Sender , class _Env >
401413 concept __max1_sender =
@@ -423,6 +435,17 @@ namespace STDEXEC
423435 using __nothrow_decay_copyable_results_t =
424436 STDEXEC ::__nothrow_decay_copyable_results_t <__completion_signatures_of_t <_Sender, _Env...>>;
425437
438+ template <class _Sender , class _Env >
439+ inline constexpr bool __can_fail = !__never_sends<set_error_t , _Sender, _Env>
440+ || sends_stopped<_Sender, _Env>
441+ || !__nothrow_decay_copyable_results_t <_Sender, _Env>::value;
442+
443+ template <class _Env , class ... _Senders>
444+ inline constexpr bool __uses_stop_source = (__can_fail<_Senders, _Env> || ...);
445+
446+ template <class _Env , class ... _Senders>
447+ using __env_t = __if_c<__uses_stop_source<_Env, _Senders...>, __stoppable_env_t <_Env>, _Env>;
448+
426449 template <class ... _Env>
427450 struct __completions
428451 {
@@ -460,6 +483,23 @@ namespace STDEXEC
460483 __concat_completion_signatures_t >...>;
461484 };
462485
486+ template <class ... _Env>
487+ struct __completions_for ;
488+
489+ template <>
490+ struct __completions_for <>
491+ {
492+ template <class ... _Senders>
493+ using __f = __completions<>::template __f<_Senders...>;
494+ };
495+
496+ template <class _Env >
497+ struct __completions_for <_Env>
498+ {
499+ template <class ... _Senders>
500+ using __f = __completions<__env_t <_Env, _Senders...>>::template __f<_Senders...>;
501+ };
502+
463503 template <class _Receiver , class _ValuesTuple >
464504 constexpr void __set_values (_Receiver& __rcvr, _ValuesTuple& __values) noexcept
465505 {
@@ -472,29 +512,33 @@ namespace STDEXEC
472512 static_cast <_ValuesTuple&&>(__values));
473513 }
474514
475- template <class _Env , class _Sender >
476- using __values_opt_tuple_t =
477- value_types_of_t <_Sender, __env_t <_Env>, __decayed_tuple, __optional>;
515+ template <class _ChildEnv , class _Sender >
516+ using __values_opt_tuple_t = value_types_of_t <_Sender, _ChildEnv, __decayed_tuple, __optional>;
478517
479- template <class _Env , __max1_sender<__env_t <_Env>>... _Senders>
518+ template <class _Env , class ... _Senders>
519+ requires (__max1_sender<_Senders, __env_t <_Env, _Senders...>> && ...)
480520 struct __traits
481521 {
522+ using __child_env = __env_t <_Env, _Senders...>;
523+
482524 // tuple<optional<tuple<Vs1...>>, optional<tuple<Vs2...>>, ...>
483- using __values_tuple = __minvoke<
484- __mwith_default<__mtransform<__mbind_front_q<__values_opt_tuple_t , _Env>, __q<__tuple>>,
485- __ignore>,
486- _Senders...>;
525+ using __values_tuple =
526+ __minvoke<__mwith_default<
527+ __mtransform<__mbind_front_q<__values_opt_tuple_t , __child_env>, __q<__tuple>>,
528+ __ignore>,
529+ _Senders...>;
487530
488531 using __collect_errors = __mbind_front_q<__mset_insert, __mset<>>;
489532
490533 using __errors_list =
491534 __minvoke<__mconcat<>,
492- __if<__mand<__nothrow_decay_copyable_results_t <_Senders, _Env >...>,
535+ __if<__mand<__nothrow_decay_copyable_results_t <_Senders, __child_env >...>,
493536 __mlist<>,
494537 __mlist<std::exception_ptr>>,
495- __error_types_of_t <_Senders, __env_t <_Env> , __q<__mlist>>...>;
538+ __error_types_of_t <_Senders, __child_env , __q<__mlist>>...>;
496539
497- using __errors_variant = __mapply<__q<__uniqued_variant>, __errors_list>;
540+ using __errors_variant = __mapply<__q<__uniqued_variant>, __errors_list>;
541+ static constexpr bool __uses_stop_source = __when_all::__uses_stop_source<_Env, _Senders...>;
498542 };
499543
500544 struct _INVALID_ARGUMENTS_TO_WHEN_ALL_
@@ -515,7 +559,10 @@ namespace STDEXEC
515559 // error state, which trumps cancellation.)
516560 if (__state_->__state_ .compare_exchange_strong (__expected, __stopped))
517561 {
518- __state_->__stop_source_ .request_stop ();
562+ if constexpr (_State::__uses_stop_source)
563+ {
564+ __state_->__stop_source_ .request_stop ();
565+ }
519566 }
520567
521568 // Arrive in order to decrement the count again and complete if needed.
@@ -525,12 +572,19 @@ namespace STDEXEC
525572 _State* __state_;
526573 };
527574
528- template <class _ErrorsVariant , class _ValuesTuple , class _Receiver , bool _SendsStopped>
575+ template <class _ErrorsVariant ,
576+ class _ValuesTuple ,
577+ class _Receiver ,
578+ bool _SendsStopped,
579+ bool _UsesStopSource>
529580 struct __state
530581 {
531- using __receiver_t = _Receiver;
582+ using __receiver_t = _Receiver;
583+ static constexpr bool __uses_stop_source = _UsesStopSource;
532584 using __stop_callback_t =
533585 stop_callback_for_t <stop_token_of_t <env_of_t <_Receiver>>, __forward_stop_request<__state>>;
586+ using __stop_source_t = __if_c<_UsesStopSource, inplace_stop_source, __empty>;
587+ using __on_stop_t = __if_c<_UsesStopSource, __optional<__stop_callback_t >, __empty>;
534588
535589 constexpr void __arrive () noexcept
536590 {
@@ -543,7 +597,10 @@ namespace STDEXEC
543597 constexpr void __complete () noexcept
544598 {
545599 // Stop callback is no longer needed. Destroy it.
546- __on_stop_.reset ();
600+ if constexpr (_UsesStopSource)
601+ {
602+ __on_stop_.reset ();
603+ }
547604 // All child operations have completed and arrived at the barrier.
548605 switch (__state_.load (__std::memory_order_relaxed))
549606 {
@@ -579,13 +636,15 @@ namespace STDEXEC
579636 STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
580637 _Receiver __rcvr_;
581638 __std::atomic<std::size_t > __count_;
582- inplace_stop_source __stop_source_{};
639+ STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
640+ __stop_source_t __stop_source_{};
583641 // Could be non-atomic here and atomic_ref everywhere except __completion_fn
584- __std::atomic<__state_t > __state_{__started};
585- _ErrorsVariant __errors_{__no_init};
642+ __std::atomic<__state_t > __state_{__started};
643+ _ErrorsVariant __errors_{__no_init};
644+ STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
645+ _ValuesTuple __values_{};
586646 STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS
587- _ValuesTuple __values_{};
588- __optional<__stop_callback_t > __on_stop_{};
647+ __on_stop_t __on_stop_{};
589648 };
590649
591650 template <class ... _Senders>
@@ -622,18 +681,12 @@ namespace STDEXEC
622681 }
623682 };
624683
625- // A when_all with no senders completes inline with no values.
626684 template <>
627685 struct __attrs <>
628686 {
687+ template <class _Tag >
629688 [[nodiscard]]
630- constexpr auto query (__get_completion_behavior_t <set_value_t >) const noexcept
631- {
632- return __completion_behavior::__inline_completion;
633- }
634-
635- [[nodiscard]]
636- constexpr auto query (__get_completion_behavior_t <set_stopped_t >) const noexcept
689+ constexpr auto query (__get_completion_behavior_t <_Tag>) const noexcept
637690 {
638691 return __completion_behavior::__inline_completion;
639692 }
@@ -642,17 +695,18 @@ namespace STDEXEC
642695 template <class _Receiver >
643696 static constexpr auto __mk_state_fn (_Receiver&& __rcvr) noexcept
644697 {
645- return [&]<__max1_sender<__env_t <env_of_t <_Receiver>>>... _Child>(__ignore,
646- __ignore,
647- _Child&&...) noexcept
698+ return [&]<class ... _Child>(__ignore, __ignore, _Child&&...) noexcept
699+ requires (__max1_sender<_Child, __env_t <env_of_t <_Receiver>, _Child...>> && ...)
648700 {
649701 using _Traits = __traits<env_of_t <_Receiver>, _Child...>;
650702 using _ErrorsVariant = _Traits::__errors_variant;
651703 using _ValuesTuple = _Traits::__values_tuple;
704+ using _ChildEnv = _Traits::__child_env;
652705 using _State = __state<_ErrorsVariant,
653706 _ValuesTuple,
654707 _Receiver,
655- (sends_stopped<_Child, env_of_t <_Receiver>> || ...)>;
708+ (sends_stopped<_Child, _ChildEnv> || ...),
709+ _Traits::__uses_stop_source>;
656710 return _State{static_cast <_Receiver&&>(__rcvr), sizeof ...(_Child)};
657711 };
658712 }
@@ -663,7 +717,7 @@ namespace STDEXEC
663717 struct __when_all_impl : __sexpr_defaults
664718 {
665719 template <class _Self , class ... _Env>
666- using __completions_t = __children_of<_Self, __when_all::__completions< __env_t < _Env> ...>>;
720+ using __completions_t = __children_of<_Self, __when_all::__completions_for< _Env...>>;
667721
668722 static constexpr auto __get_attrs =
669723 []<class ... _Child>(__ignore, __ignore, _Child const &...) noexcept
@@ -694,9 +748,15 @@ namespace STDEXEC
694748 }
695749
696750 static constexpr auto __get_env = []<class _State >(__ignore, _State const & __state) noexcept
697- -> __env_t <env_of_t <typename _State::__receiver_t const &>>
698751 {
699- return __when_all::__mk_env (STDEXEC::get_env (__state.__rcvr_ ), __state.__stop_source_ );
752+ if constexpr (_State::__uses_stop_source)
753+ {
754+ return __when_all::__mk_env (STDEXEC::get_env (__state.__rcvr_ ), __state.__stop_source_ );
755+ }
756+ else
757+ {
758+ return STDEXEC::get_env (__state.__rcvr_ );
759+ }
700760 };
701761
702762 static constexpr auto __get_state =
@@ -711,19 +771,18 @@ namespace STDEXEC
711771 []<class _State , class ... _Operations>(_State& __state,
712772 _Operations&... __child_ops) noexcept -> void
713773 {
714- // register stop callback:
715- __state.__on_stop_ .emplace (get_stop_token (STDEXEC::get_env (__state.__rcvr_ )),
716- __forward_stop_request<_State>{&__state});
717- (STDEXEC::start (__child_ops), ...);
718- if constexpr (sizeof ...(__child_ops) == 0 )
774+ if constexpr (_State::__uses_stop_source)
719775 {
720- __state.__complete ();
776+ __state.__on_stop_ .emplace (get_stop_token (STDEXEC::get_env (__state.__rcvr_ )),
777+ __forward_stop_request<_State>{&__state});
721778 }
779+ (STDEXEC::start (__child_ops), ...);
722780 };
723781
724782 template <class _State , class _Error >
725783 static constexpr void __set_error (_State& __state, _Error&& __err) noexcept
726784 {
785+ static_assert (_State::__uses_stop_source);
727786 // Transition to the "error" state and switch on the prior state.
728787 // TODO: What memory orderings are actually needed here?
729788 switch (__state.__state_ .exchange (__error))
@@ -769,6 +828,7 @@ namespace STDEXEC
769828 }
770829 else if constexpr (__same_as<_Set, set_stopped_t >)
771830 {
831+ static_assert (_State::__uses_stop_source);
772832 __state_t __expected = __started;
773833 // Transition to the "stopped" state if and only if we're in the
774834 // "started" state. (If this fails, it's because we're in an
0 commit comments