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