1919#include " ../stdexec/__detail/__variant.hpp"
2020#include " ../stdexec/execution.hpp"
2121
22+ #include " completion_signatures.hpp"
23+
24+ #include < type_traits>
25+
2226STDEXEC_PRAGMA_PUSH ()
2327STDEXEC_PRAGMA_IGNORE_GNU(" -Wmissing-braces" )
2428
@@ -54,18 +58,11 @@ namespace experimental::execution
5458 STDEXEC_ATTRIBUTE (host, device)
5559 constexpr void _start_next () noexcept
5660 {
57- STDEXEC_TRY
58- {
59- (*_start_next_)(this );
60- }
61- STDEXEC_CATCH_ALL
62- {
63- STDEXEC::set_error (static_cast <Rcvr&&>(_rcvr), std::current_exception ());
64- }
61+ (*_start_next_)(this );
6562 }
6663
6764 Rcvr _rcvr;
68- void (*_start_next_)(_opstate_base*) = nullptr ;
65+ void (*_start_next_)(_opstate_base*) noexcept = nullptr ;
6966 };
7067
7168 template <class Rcvr >
@@ -120,15 +117,18 @@ namespace experimental::execution
120117 {
121118 template <class ... _Ts>
122119 STDEXEC_ATTRIBUTE (host, device, always_inline)
123- constexpr auto
124- operator ()(_Ts&&... __ts) const STDEXEC_AUTO_RETURN (_Tuple{static_cast <_Ts&&>(__ts)...});
120+ constexpr _Tuple operator ()(_Ts&&... __ts) const
121+ noexcept (STDEXEC::__nothrow_constructible_from<_Tuple, _Ts...>)
122+ {
123+ return _Tuple{static_cast <_Ts&&>(__ts)...};
124+ }
125125 };
126126
127127 template <class Rcvr , class ... Senders>
128128 struct _opstate ;
129129
130- template <class Rcvr , class Sender0 , class ... Senders>
131- struct _opstate <Rcvr, Sender0 , Senders...> : _opstate_base<Rcvr>
130+ template <class Rcvr , class CvSender0 , class ... Senders>
131+ struct _opstate <Rcvr, CvSender0 , Senders...> : _opstate_base<Rcvr>
132132 {
133133 using operation_state_concept = STDEXEC::operation_state_t ;
134134
@@ -146,20 +146,25 @@ namespace experimental::execution
146146 using _mk_child_ops_variant_fn =
147147 STDEXEC::__mzip_with2<STDEXEC::__q2<_child_opstate_t >, STDEXEC::__qq<STDEXEC::__variant>>;
148148
149- using _ops_variant_t = STDEXEC::__minvoke<
150- _mk_child_ops_variant_fn,
151- STDEXEC::__tuple<Sender0, Senders...>,
149+ using __is_last_mask_t =
152150 STDEXEC::__mfill_c<sizeof ...(Senders),
153151 STDEXEC::__mfalse,
154- STDEXEC::__mbind_back_q<STDEXEC::__mlist, STDEXEC::__mtrue>>>;
152+ STDEXEC::__mbind_back_q<STDEXEC::__mlist, STDEXEC::__mtrue>>;
153+
154+ using _ops_variant_t = STDEXEC::__minvoke<_mk_child_ops_variant_fn,
155+ STDEXEC::__tuple<CvSender0, Senders...>,
156+ __is_last_mask_t >;
155157
156158 template <class CvSndrs >
157159 STDEXEC_ATTRIBUTE (host, device)
158160 constexpr explicit _opstate (Rcvr&& rcvr, CvSndrs&& sndrs)
161+ noexcept (::STDEXEC::__nothrow_applicable<__convert_tuple_fn<_senders_tuple_t >, CvSndrs>
162+ && ::STDEXEC::__nothrow_connectable<::STDEXEC::__tuple_element_t <0 , CvSndrs>,
163+ _rcvr_t <sizeof ...(Senders) == 0>>)
159164 : _opstate_base<Rcvr>{static_cast <Rcvr&&>(rcvr)}
165+ // move all but the first sender into the opstate:
160166 , _sndrs{
161167 STDEXEC::__apply (__convert_tuple_fn<_senders_tuple_t >{}, static_cast <CvSndrs&&>(sndrs))}
162- // move all but the first sender into the opstate.
163168 {
164169 // Below, it looks like we are using `sndrs` after it has been moved from. This is not the
165170 // case. `sndrs` is moved into a tuple type that has `__ignore` for the first element. The
@@ -170,19 +175,37 @@ namespace experimental::execution
170175 }
171176
172177 template <std::size_t Remaining>
173- static constexpr void _start_next (_opstate_base<Rcvr>* _self)
178+ static constexpr void _start_next (_opstate_base<Rcvr>* _self) noexcept
174179 {
175180 constexpr auto __nth = sizeof ...(Senders) - Remaining;
176181 auto * self = static_cast <_opstate*>(_self);
177182 auto & sndr = STDEXEC::__get<__nth + 1 >(self->_sndrs );
178- auto & op = self->_ops .template __emplace_from <__nth + 1 >(STDEXEC::connect,
179- std::move (sndr),
180- _rcvr_t <Remaining == 1 >{self});
181- if constexpr (Remaining > 1 )
183+ constexpr bool nothrow =
184+ STDEXEC::__nothrow_connectable<STDEXEC::__m_at_c<__nth, Senders...>,
185+ _rcvr_t <Remaining == 1 >>;
186+ STDEXEC_TRY
187+ {
188+ auto & op = self->_ops .template __emplace_from <__nth + 1 >(STDEXEC::connect,
189+ std::move (sndr),
190+ _rcvr_t <Remaining == 1 >{self});
191+ if constexpr (Remaining > 1 )
192+ {
193+ self->_start_next_ = &_start_next<Remaining - 1 >;
194+ }
195+ STDEXEC::start (op);
196+ }
197+ STDEXEC_CATCH_ALL
182198 {
183- self->_start_next_ = &_start_next<Remaining - 1 >;
199+ if constexpr (nothrow)
200+ {
201+ STDEXEC::__std::unreachable ();
202+ }
203+ else
204+ {
205+ STDEXEC::set_error (static_cast <Rcvr&&>(static_cast <_opstate*>(_self)->_rcvr ),
206+ std::current_exception ());
207+ }
184208 }
185- STDEXEC::start (op);
186209 }
187210
188211 STDEXEC_ATTRIBUTE (host, device)
@@ -199,70 +222,65 @@ namespace experimental::execution
199222 _ops_variant_t _ops{STDEXEC::__no_init};
200223 };
201224
202- // The completions of the sequence sender are the error and stopped completions of all the
203- // child senders plus the value completions of the last child sender.
204- template <class ... Env>
205- struct _completions_fn
206- {
207- // When folding left, the first sender folded will be the last sender in the list. That is
208- // also when the "state" of the fold is void. For this case we want to include the value
209- // completions; otherwise, we want to exclude them.
210- template <class State , class ... Args>
211- struct _fold_left ;
212-
213- template <class State , class Head , class ... Tail>
214- struct _fold_left <State, Head, Tail...>
215- {
216- using __t = STDEXEC::__gather_completion_signatures_t <
217- STDEXEC::__completion_signatures_of_t <Head, Env...>,
218- STDEXEC::set_value_t ,
219- STDEXEC::__mconst<STDEXEC::completion_signatures<>>::__f,
220- STDEXEC::__cmplsigs::__default_completion,
221- STDEXEC::__mtry_q<STDEXEC::__concat_completion_signatures_t >::__f,
222- STDEXEC::__t <_fold_left<State, Tail...>>>;
223- };
224-
225- template <class Head >
226- struct _fold_left <void , Head>
227- {
228- using __t = STDEXEC::__mtry_q<STDEXEC::__concat_completion_signatures_t >::__f<
229- STDEXEC::completion_signatures<STDEXEC::set_error_t (std::exception_ptr)>,
230- STDEXEC::__completion_signatures_of_t <Head, Env...>>;
231- };
232-
233- template <class ... Sender>
234- using __f = STDEXEC::__t <_fold_left<void , Sender...>>;
235- };
225+ template <class Sender >
226+ concept __has_eptr_completion =
227+ STDEXEC::sender_in<Sender>
228+ && exec::transform_completion_signatures(STDEXEC::get_completion_signatures<Sender>(),
229+ exec::ignore_completion (),
230+ exec::decay_arguments<STDEXEC::set_error_t>(),
231+ exec::ignore_completion())
232+ .__contains(STDEXEC::__fn_ptr_t <STDEXEC::set_error_t , std::exception_ptr>());
236233
237234 template <class Sender0 , class ... Senders>
238235 struct _sndr <Sender0, Senders...>
239236 {
240237 using sender_concept = STDEXEC::sender_t ;
241238
239+ // Even without an Env, we can sometimes still determine the completion signatures
240+ // of the sequence sender. If any of the child senders has a
241+ // set_error(exception_ptr) completion, then the sequence sender has a
242+ // set_error(exception_ptr) completion. We don't have to ask if any connect call
243+ // throws.
242244 template <class Self , class ... Env>
243- using _completions_t = STDEXEC::__minvoke<_completions_fn<Env...>,
244- STDEXEC::__copy_cvref_t <Self, Sender0>,
245- Senders...>;
246-
247- template <class Self , class ... Env>
245+ requires (sizeof ...(Env) > 0 )
246+ || __has_eptr_completion<STDEXEC::__copy_cvref_t <Self, Sender0>>
247+ || (__has_eptr_completion<Senders> || ...)
248248 STDEXEC_ATTRIBUTE (host, device)
249249 static consteval auto get_completion_signatures ()
250250 {
251- if constexpr (STDEXEC::__decay_copyable<Self>)
252- {
253- return _completions_t <Self, Env...>{};
254- }
255- else
251+ if constexpr (!STDEXEC::__decay_copyable<Self>)
256252 {
257253 return STDEXEC::__throw_compile_time_error<
258254 STDEXEC::_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_,
259255 STDEXEC::_WITH_PRETTY_SENDER_<_sndr<Sender0, Senders...>>>();
260256 }
257+ else
258+ {
259+ using __env_t = STDEXEC::__mfront<Env..., STDEXEC::env<>>;
260+ using __rcvr_t = STDEXEC::__receiver_archetype<__env_t >;
261+ constexpr bool nothrow = (STDEXEC::__nothrow_connectable<Senders, __rcvr_t > && ...);
262+
263+ // The completions of the sequence sender are the error and stopped completions of all the
264+ // child senders plus the value completions of the last child sender.
265+ return exec::concat_completion_signatures (
266+ exec::transform_completion_signatures (
267+ STDEXEC::get_completion_signatures<STDEXEC::__copy_cvref_t <Self, Sender0>, Env...>(),
268+ exec::ignore_completion ()),
269+ exec::transform_completion_signatures (
270+ STDEXEC::get_completion_signatures<Senders, Env...>(),
271+ exec::ignore_completion ())...,
272+ STDEXEC::get_completion_signatures<STDEXEC::__mback<Senders...>, Env...>(),
273+ STDEXEC::__eptr_completion_unless<nothrow>());
274+ }
261275 }
262276
263277 template <STDEXEC::__decay_copyable Self, class Rcvr >
264278 STDEXEC_ATTRIBUTE (host, device)
265279 constexpr STDEXEC_EXPLICIT_THIS_BEGIN (auto connect)(this Self&& self, Rcvr rcvr)
280+ noexcept (STDEXEC::__nothrow_constructible_from<
281+ _opstate<Rcvr, STDEXEC::__copy_cvref_t <Self, Sender0>, Senders...>,
282+ Rcvr,
283+ decltype ((static_cast <Self&&>(self)._sndrs))>)
266284 {
267285 return _opstate<Rcvr, STDEXEC::__copy_cvref_t <Self, Sender0>, Senders...>{
268286 static_cast <Rcvr&&>(rcvr),
0 commit comments