diff --git a/include/exec/sequence.hpp b/include/exec/sequence.hpp index a7f2d3ef8..925b679ab 100644 --- a/include/exec/sequence.hpp +++ b/include/exec/sequence.hpp @@ -19,6 +19,10 @@ #include "../stdexec/__detail/__variant.hpp" #include "../stdexec/execution.hpp" +#include "completion_signatures.hpp" + +#include + STDEXEC_PRAGMA_PUSH() STDEXEC_PRAGMA_IGNORE_GNU("-Wmissing-braces") @@ -54,18 +58,11 @@ namespace experimental::execution STDEXEC_ATTRIBUTE(host, device) constexpr void _start_next() noexcept { - STDEXEC_TRY - { - (*_start_next_)(this); - } - STDEXEC_CATCH_ALL - { - STDEXEC::set_error(static_cast(_rcvr), std::current_exception()); - } + (*_start_next_)(this); } Rcvr _rcvr; - void (*_start_next_)(_opstate_base*) = nullptr; + void (*_start_next_)(_opstate_base*) noexcept = nullptr; }; template @@ -120,15 +117,18 @@ namespace experimental::execution { template STDEXEC_ATTRIBUTE(host, device, always_inline) - constexpr auto - operator()(_Ts&&... __ts) const STDEXEC_AUTO_RETURN(_Tuple{static_cast<_Ts&&>(__ts)...}); + constexpr _Tuple operator()(_Ts&&... __ts) const + noexcept(STDEXEC::__nothrow_constructible_from<_Tuple, _Ts...>) + { + return _Tuple{static_cast<_Ts&&>(__ts)...}; + } }; template struct _opstate; - template - struct _opstate : _opstate_base + template + struct _opstate : _opstate_base { using operation_state_concept = STDEXEC::operation_state_t; @@ -146,20 +146,25 @@ namespace experimental::execution using _mk_child_ops_variant_fn = STDEXEC::__mzip_with2, STDEXEC::__qq>; - using _ops_variant_t = STDEXEC::__minvoke< - _mk_child_ops_variant_fn, - STDEXEC::__tuple, + using __is_last_mask_t = STDEXEC::__mfill_c>>; + STDEXEC::__mbind_back_q>; + + using _ops_variant_t = STDEXEC::__minvoke<_mk_child_ops_variant_fn, + STDEXEC::__tuple, + __is_last_mask_t>; template STDEXEC_ATTRIBUTE(host, device) constexpr explicit _opstate(Rcvr&& rcvr, CvSndrs&& sndrs) + noexcept(::STDEXEC::__nothrow_applicable<__convert_tuple_fn<_senders_tuple_t>, CvSndrs> + && ::STDEXEC::__nothrow_connectable<::STDEXEC::__tuple_element_t<0, CvSndrs>, + _rcvr_t>) : _opstate_base{static_cast(rcvr)} + // move all but the first sender into the opstate: , _sndrs{ STDEXEC::__apply(__convert_tuple_fn<_senders_tuple_t>{}, static_cast(sndrs))} - // move all but the first sender into the opstate. { // Below, it looks like we are using `sndrs` after it has been moved from. This is not the // case. `sndrs` is moved into a tuple type that has `__ignore` for the first element. The @@ -170,19 +175,37 @@ namespace experimental::execution } template - static constexpr void _start_next(_opstate_base* _self) + static constexpr void _start_next(_opstate_base* _self) noexcept { constexpr auto __nth = sizeof...(Senders) - Remaining; auto* self = static_cast<_opstate*>(_self); auto& sndr = STDEXEC::__get<__nth + 1>(self->_sndrs); - auto& op = self->_ops.template __emplace_from<__nth + 1>(STDEXEC::connect, - std::move(sndr), - _rcvr_t{self}); - if constexpr (Remaining > 1) + constexpr bool nothrow = + STDEXEC::__nothrow_connectable, + _rcvr_t>; + STDEXEC_TRY + { + auto& op = self->_ops.template __emplace_from<__nth + 1>(STDEXEC::connect, + std::move(sndr), + _rcvr_t{self}); + if constexpr (Remaining > 1) + { + self->_start_next_ = &_start_next; + } + STDEXEC::start(op); + } + STDEXEC_CATCH_ALL { - self->_start_next_ = &_start_next; + if constexpr (nothrow) + { + STDEXEC::__std::unreachable(); + } + else + { + STDEXEC::set_error(static_cast(static_cast<_opstate*>(_self)->_rcvr), + std::current_exception()); + } } - STDEXEC::start(op); } STDEXEC_ATTRIBUTE(host, device) @@ -199,70 +222,65 @@ namespace experimental::execution _ops_variant_t _ops{STDEXEC::__no_init}; }; - // The completions of the sequence sender are the error and stopped completions of all the - // child senders plus the value completions of the last child sender. - template - struct _completions_fn - { - // When folding left, the first sender folded will be the last sender in the list. That is - // also when the "state" of the fold is void. For this case we want to include the value - // completions; otherwise, we want to exclude them. - template - struct _fold_left; - - template - struct _fold_left - { - using __t = STDEXEC::__gather_completion_signatures_t< - STDEXEC::__completion_signatures_of_t, - STDEXEC::set_value_t, - STDEXEC::__mconst>::__f, - STDEXEC::__cmplsigs::__default_completion, - STDEXEC::__mtry_q::__f, - STDEXEC::__t<_fold_left>>; - }; - - template - struct _fold_left - { - using __t = STDEXEC::__mtry_q::__f< - STDEXEC::completion_signatures, - STDEXEC::__completion_signatures_of_t>; - }; - - template - using __f = STDEXEC::__t<_fold_left>; - }; + template + concept __has_eptr_completion = + STDEXEC::sender_in + && exec::transform_completion_signatures(STDEXEC::get_completion_signatures(), + exec::ignore_completion(), + exec::decay_arguments(), + exec::ignore_completion()) + .__contains(STDEXEC::__fn_ptr_t()); template struct _sndr { using sender_concept = STDEXEC::sender_t; + // Even without an Env, we can sometimes still determine the completion signatures + // of the sequence sender. If any of the child senders has a + // set_error(exception_ptr) completion, then the sequence sender has a + // set_error(exception_ptr) completion. We don't have to ask if any connect call + // throws. template - using _completions_t = STDEXEC::__minvoke<_completions_fn, - STDEXEC::__copy_cvref_t, - Senders...>; - - template + requires(sizeof...(Env) > 0) + || __has_eptr_completion> + || (__has_eptr_completion || ...) STDEXEC_ATTRIBUTE(host, device) static consteval auto get_completion_signatures() { - if constexpr (STDEXEC::__decay_copyable) - { - return _completions_t{}; - } - else + if constexpr (!STDEXEC::__decay_copyable) { return STDEXEC::__throw_compile_time_error< STDEXEC::_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_, STDEXEC::_WITH_PRETTY_SENDER_<_sndr>>(); } + else + { + using __env_t = STDEXEC::__mfront>; + using __rcvr_t = STDEXEC::__receiver_archetype<__env_t>; + constexpr bool nothrow = (STDEXEC::__nothrow_connectable && ...); + + // The completions of the sequence sender are the error and stopped completions of all the + // child senders plus the value completions of the last child sender. + return exec::concat_completion_signatures( + exec::transform_completion_signatures( + STDEXEC::get_completion_signatures, Env...>(), + exec::ignore_completion()), + exec::transform_completion_signatures( + STDEXEC::get_completion_signatures(), + exec::ignore_completion())..., + STDEXEC::get_completion_signatures, Env...>(), + STDEXEC::__eptr_completion_unless()); + } } template STDEXEC_ATTRIBUTE(host, device) constexpr STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this Self&& self, Rcvr rcvr) + noexcept(STDEXEC::__nothrow_constructible_from< + _opstate, Senders...>, + Rcvr, + decltype((static_cast(self)._sndrs))>) { return _opstate, Senders...>{ static_cast(rcvr), diff --git a/include/stdexec/__detail/__meta.hpp b/include/stdexec/__detail/__meta.hpp index 9fc4a42a8..8f2424483 100644 --- a/include/stdexec/__detail/__meta.hpp +++ b/include/stdexec/__detail/__meta.hpp @@ -36,6 +36,9 @@ namespace STDEXEC template using __fn_t = _Ret(_Args...); + template + using __fn_ptr_t = _Ret (*)(_Args...); + template struct __mtype { @@ -49,7 +52,7 @@ namespace STDEXEC { // NB: This variable template is partially specialized for __type_index in __typeinfo.hpp: template - extern __fn_t *__mtypeof_v; + extern __fn_ptr_t __mtypeof_v; } // namespace __detail template @@ -765,11 +768,74 @@ namespace STDEXEC using __f = _Return(_Args...); }; - template - using __mfront_ = _Ty; +#if !STDEXEC_NO_STDCPP_PACK_INDEXING() + STDEXEC_PRAGMA_PUSH() + STDEXEC_PRAGMA_IGNORE_GNU("-Wc++26-extensions") + + template + struct __m_at_ + { + template + using __f = _Ts...[_Np::value]; + }; + + template + using __m_at = __minvoke<__m_at_<_Np::value == ~0ul>, _Np, _Ts...>; + + template + using __m_at_c = __minvoke<__m_at_<_Np == ~0ul>, __msize_t<_Np>, _Ts...>; + + STDEXEC_PRAGMA_POP() +#elif STDEXEC_HAS_BUILTIN(__type_pack_element) + template + struct __m_at_ + { + template + using __f = __type_pack_element<_Np::value, _Ts...>; + }; + + template + using __m_at = __minvoke<__m_at_<_Np::value == ~0ul>, _Np, _Ts...>; + + template + using __m_at_c = __minvoke<__m_at_<_Np == ~0ul>, __msize_t<_Np>, _Ts...>; +#else + template + using __void_ptr = void *; + + template + using __mtype_ptr = __mtype<_Ty> *; + + template + struct __m_at_; + + template + struct __m_at_<__indices<_Is...>> + { + template + static _Up __f_(__void_ptr<_Is>..., _Up *, _Us *...); + template + using __f = __t()...))>; + }; + + template + using __m_at_c = __minvoke<__m_at_<__make_indices<_Np>>, _Ts...>; + + template + using __m_at = __m_at_c<_Np::value, _Ts...>; +#endif + + namespace __detail + { + template + using __mfront_ = _Ty; + } // namespace __detail + + template + using __mfront = __minvoke_q<__detail::__mfront_, _As...>; template - using __mfront = __minvoke_q<__mfront_, _As...>; + using __mback = __m_at_c; template requires(sizeof...(_As) == 1) @@ -957,63 +1023,6 @@ namespace STDEXEC using __f = __minvoke_q<__mor, __mcall1<_Fn, _Args>...>; }; -#if !STDEXEC_NO_STDCPP_PACK_INDEXING() - STDEXEC_PRAGMA_PUSH() - STDEXEC_PRAGMA_IGNORE_GNU("-Wc++26-extensions") - - template - struct __m_at_ - { - template - using __f = _Ts...[_Np::value]; - }; - - template - using __m_at = __minvoke<__m_at_<_Np::value == ~0ul>, _Np, _Ts...>; - - template - using __m_at_c = __minvoke<__m_at_<_Np == ~0ul>, __msize_t<_Np>, _Ts...>; - - STDEXEC_PRAGMA_POP() -#elif STDEXEC_HAS_BUILTIN(__type_pack_element) - template - struct __m_at_ - { - template - using __f = __type_pack_element<_Np::value, _Ts...>; - }; - - template - using __m_at = __minvoke<__m_at_<_Np::value == ~0ul>, _Np, _Ts...>; - - template - using __m_at_c = __minvoke<__m_at_<_Np == ~0ul>, __msize_t<_Np>, _Ts...>; -#else - template - using __void_ptr = void *; - - template - using __mtype_ptr = __mtype<_Ty> *; - - template - struct __m_at_; - - template - struct __m_at_<__indices<_Is...>> - { - template - static _Up __f_(__void_ptr<_Is>..., _Up *, _Us *...); - template - using __f = __t()...))>; - }; - - template - using __m_at_c = __minvoke<__m_at_<__make_indices<_Np>>, _Ts...>; - - template - using __m_at = __m_at_c<_Np::value, _Ts...>; -#endif - template concept __mset_contains = (STDEXEC_IS_BASE_OF(__mtype<_Ty>, _Set) && ...); diff --git a/test/exec/sequence/test_merge_each.cpp b/test/exec/sequence/test_merge_each.cpp index eb741fc60..44cbc39ed 100644 --- a/test/exec/sequence/test_merge_each.cpp +++ b/test/exec/sequence/test_merge_each.cpp @@ -236,8 +236,8 @@ namespace auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok>); - STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>>); + STATIC_REQUIRE(ex::__ok>>); STATIC_REQUIRE(ex::__callable); @@ -275,8 +275,8 @@ namespace auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok>); - STATIC_REQUIRE(ex::__ok>); + STATIC_REQUIRE(ex::__ok>>); + STATIC_REQUIRE(ex::__ok>>); STATIC_REQUIRE(ex::__callable); @@ -337,8 +337,8 @@ namespace auto merged = merge_each(sequences); using merged_t = decltype(merged); - STATIC_REQUIRE(ex::__ok<__item_types_of_t>); - STATIC_REQUIRE(__equivalent, + STATIC_REQUIRE(ex::__ok<__item_types_of_t>>); + STATIC_REQUIRE(__equivalent>, ex::completion_signatures>); diff --git a/test/exec/test_sequence.cpp b/test/exec/test_sequence.cpp index 9443a6cc4..758f25b5a 100644 --- a/test/exec/test_sequence.cpp +++ b/test/exec/test_sequence.cpp @@ -74,6 +74,7 @@ TEST_CASE("sequence produces a sender", "[sequence]") STATIC_REQUIRE(!ex::__callable); auto s0 = exec::sequence(ex::just(42)); + static_assert(ex::__nothrow_connectable>>); STATIC_REQUIRE(ex::sender); STATIC_REQUIRE(ex::sender_in); check_val_types>>(s0); @@ -88,8 +89,24 @@ TEST_CASE("sequence produces a sender", "[sequence]") STATIC_REQUIRE(!ex::sender_in); STATIC_REQUIRE(ex::sender_in); check_val_types const &>>, env_t>(s1); - check_err_types, env_t>(s1); + check_err_types, env_t>(s1); check_sends_stopped(s1); + + auto s2 = exec::sequence(ex::just(), ex::just(42)); + STATIC_REQUIRE(ex::sender); + STATIC_REQUIRE(!ex::sender_in); + STATIC_REQUIRE(ex::sender_in>); + check_val_types>>(s2); + check_err_types>(s2); + check_sends_stopped(s2); + + auto s3 = exec::sequence(ex::just(true), ex::just(42)); + STATIC_REQUIRE(ex::sender); + STATIC_REQUIRE(!ex::sender_in); + STATIC_REQUIRE(ex::sender_in>); + check_val_types>>(s3); + check_err_types>(s3); + check_sends_stopped(s3); } TEST_CASE("sequence with one argument works", "[sequence]") @@ -155,7 +172,16 @@ TEST_CASE("sequence with sender with throwing connect", "[sequence]") { auto err = std::make_exception_ptr(connect_exception{}); auto sndr = exec::sequence(ex::just(big{}), throwing_connect{}, ex::just(big{}, 42)); - auto op = ex::connect(std::move(sndr), expect_error_receiver{err}); + check_err_types, ex::env<>>(std::move(sndr)); + auto op = ex::connect(std::move(sndr), expect_error_receiver{err}); ex::start(op); } -#endif // !STDEXEC_NO_STDCPP_EXCEPTIONS() + +TEST_CASE("sequence with sender with only first sender with throwing connect", "[sequence]") +{ + auto err = std::make_exception_ptr(connect_exception{}); + auto sndr = exec::sequence(throwing_connect{}, ex::just()); + check_err_types, ex::env<>>(sndr); + CHECK_THROWS(ex::connect(std::move(sndr), empty_recv::recv0{})); +} +#endif // !STDEXEC_NO_STD_EXCEPTIONS()