diff --git a/include/exec/repeat_n.hpp b/include/exec/repeat_n.hpp index 29fbfa1e9..d198087f9 100644 --- a/include/exec/repeat_n.hpp +++ b/include/exec/repeat_n.hpp @@ -32,6 +32,9 @@ STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-value") namespace experimental::execution { + struct repeat_n_t; + struct _THE_INPUT_SENDER_MUST_HAVE_VOID_VALUE_COMPLETION_; + namespace __repeat_n { using namespace STDEXEC; @@ -50,9 +53,8 @@ namespace experimental::execution virtual constexpr void __cleanup() noexcept = 0; virtual constexpr void __repeat() noexcept = 0; - _Receiver __rcvr_; - std::size_t __count_; - trampoline_scheduler __sched_{}; + _Receiver __rcvr_; + std::size_t __count_; protected: ~__opstate_base() noexcept = default; @@ -109,8 +111,13 @@ namespace experimental::execution __result_of, _Child &>; using __child_op_t = STDEXEC::connect_result_t<__bouncy_sndr_t, __receiver_t>; + static constexpr bool __nothrow_connect = + __nothrow_invocable + && __nothrow_invocable, _Child &> + && __nothrow_connectable<__bouncy_sndr_t, __receiver_t>; + constexpr explicit __opstate(std::size_t __count, _Child __child, _Receiver __rcvr) - noexcept(__nothrow_move_constructible<_Child> && noexcept(__connect())) + noexcept(__nothrow_move_constructible<_Child> && __nothrow_connect) : __opstate_base<_Receiver>{static_cast<_Receiver &&>(__rcvr), __count} , __child_(std::move(__child)) { @@ -132,13 +139,10 @@ namespace experimental::execution } } - constexpr auto __connect() noexcept( - __nothrow_invocable - && __nothrow_invocable, _Child &> - && __nothrow_connectable<__bouncy_sndr_t, __receiver_t>) -> __child_op_t & + constexpr auto __connect() noexcept(__nothrow_connect) -> __child_op_t & { return __child_op_.__emplace_from(STDEXEC::connect, - exec::sequence(STDEXEC::schedule(this->__sched_), + exec::sequence(STDEXEC::schedule(trampoline_scheduler{}), __child_), __receiver_t{this}); } @@ -165,7 +169,7 @@ namespace experimental::execution } STDEXEC_CATCH_ALL { - if constexpr (!noexcept(__connect())) + if constexpr (!__nothrow_connect) { STDEXEC::set_error(std::move(this->__rcvr_), std::current_exception()); } @@ -180,9 +184,6 @@ namespace experimental::execution STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE __opstate(std::size_t, _Child, _Receiver) -> __opstate<_Child, _Receiver>; - struct repeat_n_t; - struct _THE_INPUT_SENDER_MUST_HAVE_VOID_VALUE_COMPLETION_; - template using __values_t = // There's something funny going on with __if_c here. Use std::conditional_t instead. :-( @@ -215,6 +216,13 @@ namespace experimental::execution struct __impls : __sexpr_defaults { + static constexpr auto __get_attrs = + [](__ignore, __ignore, _Child const &__child) noexcept + -> __seq::__attrs, _Child &> + { + return {STDEXEC::schedule(trampoline_scheduler{}), const_cast<_Child &>(__child)}; + }; + template static consteval auto __get_completion_signatures() { @@ -232,24 +240,24 @@ namespace experimental::execution static_cast<_Receiver &&>(__rcvr)); }; }; + } // namespace __repeat_n - struct repeat_n_t + struct repeat_n_t + { + template + constexpr auto operator()(_Sender &&__sndr, std::size_t __count) const // + -> STDEXEC::__well_formed_sender auto { - template - constexpr auto operator()(_Sender &&__sndr, std::size_t __count) const - { - return __make_sexpr(__count, static_cast<_Sender &&>(__sndr)); - } + return STDEXEC::__make_sexpr(__count, static_cast<_Sender &&>(__sndr)); + } - STDEXEC_ATTRIBUTE(always_inline) - constexpr auto operator()(std::size_t __count) const noexcept - { - return __closure(*this, __count); - } - }; - } // namespace __repeat_n + STDEXEC_ATTRIBUTE(always_inline) + constexpr auto operator()(std::size_t __count) const noexcept + { + return STDEXEC::__closure(*this, __count); + } + }; - using __repeat_n::repeat_n_t; inline constexpr repeat_n_t repeat_n{}; } // namespace experimental::execution diff --git a/include/exec/repeat_until.hpp b/include/exec/repeat_until.hpp index d0ac876b2..fd124c053 100644 --- a/include/exec/repeat_until.hpp +++ b/include/exec/repeat_until.hpp @@ -33,6 +33,9 @@ STDEXEC_PRAGMA_IGNORE_EDG(not_used_in_template_function_params) namespace experimental::execution { + struct repeat_t; + struct repeat_until_t; + struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_; struct _EXPECTING_A_SENDER_OF_VOID_; @@ -40,31 +43,24 @@ namespace experimental::execution { using namespace STDEXEC; - struct repeat_t; - struct repeat_until_t; - template struct __opstate_base { constexpr explicit __opstate_base(_Receiver &&__rcvr) noexcept : __rcvr_{static_cast<_Receiver &&>(__rcvr)} - { - static_assert(__nothrow_constructible_from, - "trampoline_scheduler c'tor is always expected to be noexcept"); - } + {} virtual constexpr void __cleanup() noexcept = 0; virtual constexpr void __repeat() noexcept = 0; - _Receiver __rcvr_; - trampoline_scheduler __sched_{}; + _Receiver __rcvr_; protected: ~__opstate_base() noexcept = default; }; template - concept __bool_constant = __decay_t<_Boolean>::value == _Expected; + concept __is_bool_constant = __decay_t<_Boolean>::value == _Expected; template struct __receiver @@ -74,13 +70,13 @@ namespace experimental::execution template constexpr void set_value(_Booleans &&...__bools) noexcept { - if constexpr ((__bool_constant<_Booleans, true> && ...)) + if constexpr ((__is_bool_constant<_Booleans, true> && ...)) { // Always done: __state_->__cleanup(); STDEXEC::set_value(std::move(__state_->__rcvr_)); } - else if constexpr ((__bool_constant<_Booleans, false> && ...)) + else if constexpr ((__is_bool_constant<_Booleans, false> && ...)) { // Never done: __state_->__repeat(); @@ -159,8 +155,13 @@ namespace experimental::execution __result_of, _Child &>; using __child_op_t = STDEXEC::connect_result_t<__bouncy_sndr_t, __receiver_t>; + static constexpr bool __nothrow_connect = + __nothrow_invocable + && __nothrow_invocable, _Child &> + && __nothrow_connectable<__bouncy_sndr_t, __receiver_t>; + constexpr explicit __opstate(_Child __child, _Receiver __rcvr) - noexcept(__nothrow_move_constructible<_Child> && noexcept(__connect())) + noexcept(__nothrow_move_constructible<_Child> && __nothrow_connect) : __opstate_base<_Receiver>(std::move(__rcvr)) , __child_(std::move(__child)) { @@ -172,13 +173,10 @@ namespace experimental::execution STDEXEC::start(*__child_op_); } - constexpr auto __connect() noexcept( - __nothrow_invocable - && __nothrow_invocable, _Child &> - && __nothrow_connectable<__bouncy_sndr_t, __receiver_t>) -> __child_op_t & + constexpr auto __connect() noexcept(__nothrow_connect) -> __child_op_t & { return __child_op_.__emplace_from(STDEXEC::connect, - exec::sequence(STDEXEC::schedule(this->__sched_), + exec::sequence(STDEXEC::schedule(trampoline_scheduler{}), __child_), __receiver_t{this}); } @@ -196,7 +194,7 @@ namespace experimental::execution } STDEXEC_CATCH_ALL { - if constexpr (!noexcept(__connect())) + if constexpr (!__nothrow_connect) { STDEXEC::set_error(static_cast<_Receiver &&>(this->__rcvr_), std::current_exception()); } @@ -213,8 +211,24 @@ namespace experimental::execution STDEXEC_PRAGMA_POP() + struct _never + { + STDEXEC_ATTRIBUTE(host, device, always_inline) + constexpr std::false_type operator()() const noexcept + { + return {}; + } + }; + struct __repeat_until_impl : __sexpr_defaults { + static constexpr auto __get_attrs = + [](__ignore, __ignore, _Child const &__child) noexcept + -> __seq::__attrs, _Child &> + { + return {STDEXEC::schedule(trampoline_scheduler{}), const_cast<_Child &>(__child)}; + }; + template static constexpr auto __transform_values = []() { @@ -226,7 +240,7 @@ namespace experimental::execution _WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_), _WITH_PRETTY_SENDER_<_Child>>(); } - else if constexpr ((__bool_constant<_Args, false> && ...)) + else if constexpr ((__is_bool_constant<_Args, false> && ...)) { return STDEXEC::completion_signatures{}; } @@ -294,7 +308,7 @@ namespace experimental::execution return exec::concat_completion_signatures(__sigs, __eptr_sigs, __bouncer_sigs); } } - }; + } static constexpr auto __connect = [](_Sender &&__sndr, _Receiver __rcvr) noexcept( @@ -307,74 +321,74 @@ namespace experimental::execution }; }; - struct repeat_until_t + struct __repeat_impl : __repeat_until_impl { - template - constexpr auto operator()(_Sender &&__sndr) const - { - return __make_sexpr({}, static_cast<_Sender &&>(__sndr)); - } - - STDEXEC_ATTRIBUTE(always_inline) - constexpr auto operator()() const + template + static consteval auto __get_completion_signatures() { - return __closure(*this); + using __child_t = __call_result_t, _never>; + using __parent_t = __call_result_t; + return __repeat_until_impl::__get_completion_signatures<__parent_t, _Env...>(); } }; + } // namespace __repeat - struct repeat_t + struct repeat_until_t + { + template + constexpr auto operator()(_Sender &&__sndr) const -> STDEXEC::__well_formed_sender auto { - struct _never - { - STDEXEC_ATTRIBUTE(host, device, always_inline) - constexpr std::false_type operator()() const noexcept - { - return {}; - } - }; + return STDEXEC::__make_sexpr({}, static_cast<_Sender &&>(__sndr)); + } - template - constexpr auto operator()(_Sender &&__sndr) const - { - return __make_sexpr({}, static_cast<_Sender &&>(__sndr)); - } + STDEXEC_ATTRIBUTE(always_inline) + constexpr auto operator()() const + { + return STDEXEC::__closure(*this); + } + }; + + inline constexpr repeat_until_t repeat_until{}; + + struct repeat_t + { + template + constexpr auto operator()(_Sender &&__sndr) const -> STDEXEC::__well_formed_sender auto + { + return STDEXEC::__make_sexpr({}, static_cast<_Sender &&>(__sndr)); + } + + STDEXEC_ATTRIBUTE(always_inline) + constexpr auto operator()() const + { + return STDEXEC::__closure(*this); + } + + template + static constexpr auto + transform_sender(STDEXEC::set_value_t, _CvSender &&__sndr, _Env const &) noexcept + { + using namespace STDEXEC; + using __child_t = __child_of<_CvSender>; + using __values_t = value_types_of_t<__child_t, _Env, __mlist, __mlist>; + auto &[__tag, __ign, __child] = __sndr; - STDEXEC_ATTRIBUTE(always_inline) - constexpr auto operator()() const + if constexpr (__same_as<__values_t, __mlist<>> || __same_as<__values_t, __mlist<__mlist<>>>) { - return __closure(*this); + return repeat_until(then(static_cast<__child_t &&>(__child), __repeat::_never{})); } - - template - static constexpr auto - transform_sender(STDEXEC::set_value_t, _CvSender &&__sndr, _Env const &) noexcept + else { - using namespace STDEXEC; - using __child_t = __child_of<_CvSender>; - using __values_t = value_types_of_t<__child_t, _Env, __mlist, __mlist>; - auto &[__tag, __ign, __child] = __sndr; - - if constexpr (__same_as<__values_t, __mlist<>> || __same_as<__values_t, __mlist<__mlist<>>>) - { - return repeat_until_t()(then(static_cast<__child_t &&>(__child), _never{})); - } - else - { - return __not_a_sender<_WHAT_(_INVALID_ARGUMENT_, _EXPECTING_A_SENDER_OF_VOID_), - _WHERE_(_IN_ALGORITHM_, repeat_until_t), - _WITH_PRETTY_SENDER_<__child_t>, - _WITH_ENVIRONMENT_(_Env)>(); - } + return __not_a_sender<_WHAT_(_INVALID_ARGUMENT_, _EXPECTING_A_SENDER_OF_VOID_), + _WHERE_(_IN_ALGORITHM_, repeat_until_t), + _WITH_PRETTY_SENDER_<__child_t>, + _WITH_ENVIRONMENT_(_Env)>(); } - }; - } // namespace __repeat + } + }; - using __repeat::repeat_t; inline constexpr repeat_t repeat{}; - using __repeat::repeat_until_t; - inline constexpr repeat_until_t repeat_until{}; - /// deprecated interfaces using repeat_effect_t [[deprecated("use exec::repeat_t instead")]] = repeat_t; using repeat_effect_until_t [[deprecated("use exec::repeat_until_t instead")]] = repeat_until_t; @@ -389,7 +403,7 @@ namespace exec = experimental::execution; namespace STDEXEC { template <> - struct __sexpr_impl : exec::__repeat::__repeat_until_impl + struct __sexpr_impl : exec::__repeat::__repeat_impl {}; template <> diff --git a/include/exec/trampoline_scheduler.hpp b/include/exec/trampoline_scheduler.hpp index 0281b812a..00a3c7d23 100644 --- a/include/exec/trampoline_scheduler.hpp +++ b/include/exec/trampoline_scheduler.hpp @@ -63,9 +63,12 @@ namespace experimental::execution struct __attrs { - template <__one_of _Tag> + template <__one_of _Tag, __queryable_with _Env> [[nodiscard]] - constexpr auto query(get_completion_scheduler_t<_Tag>) const noexcept -> __scheduler; + constexpr auto query(get_completion_scheduler_t<_Tag>, _Env const & __env) const noexcept + { + return get_scheduler(__env); + } template <__one_of _Tag, __queryable_with _Env> [[nodiscard]] @@ -303,13 +306,6 @@ namespace experimental::execution __op->__execute(); } } - - template <__one_of _Tag> - [[nodiscard]] - constexpr auto __attrs::query(get_completion_scheduler_t<_Tag>) const noexcept -> __scheduler - { - return __scheduler{__max_recursion_depth_}; - } } // namespace __trampoline using trampoline_scheduler = __trampoline::__scheduler; diff --git a/include/stdexec/__detail/__basic_sender.hpp b/include/stdexec/__detail/__basic_sender.hpp index 109839535..6f5d19b5d 100644 --- a/include/stdexec/__detail/__basic_sender.hpp +++ b/include/stdexec/__detail/__basic_sender.hpp @@ -216,8 +216,13 @@ namespace STDEXEC }; template - concept __has_get_completion_signatures_impl = requires { - __sexpr_impl<_Tag>::template __get_completion_signatures<_Self, _Env...>(); + inline constexpr bool __has_get_completion_signatures_v = requires { + __sexpr_impl<_Tag>::template __get_completion_signatures<_Self>(); + }; + + template + inline constexpr bool __has_get_completion_signatures_v<_Tag, _Self, _Env> = requires { + __sexpr_impl<_Tag>::template __get_completion_signatures<_Self, _Env>(); }; } // namespace __detail @@ -332,13 +337,16 @@ namespace STDEXEC { using sender_concept = sender_t; - using __desc_t = decltype(_DescriptorFn()); - using __tag_t = __desc_t::__tag; + using __desc_t = decltype(_DescriptorFn()); + using __tag_t = __desc_t::__tag; + using __base_t = __minvoke<__desc_t, __qq<__tuple>>; + using __get_attrs_t = __mtypeof<__sexpr_impl<__tag_t>::__get_attrs>; + using __attrs_t = __apply_result_t<__get_attrs_t, __base_t const &>; STDEXEC_ATTRIBUTE(nodiscard, always_inline) - constexpr auto get_env() const noexcept -> decltype(auto) + constexpr auto get_env() const noexcept -> __attrs_t { - return __apply(__sexpr_impl<__tag_t>::__get_attrs, __c_upcast<__sexpr>(*this)); + return __apply(__sexpr_impl<__tag_t>::__get_attrs, __c_upcast<__base_t>(*this)); } template @@ -347,11 +355,11 @@ namespace STDEXEC using namespace __detail; static_assert(STDEXEC_IS_BASE_OF(__sexpr, __decay_t<_Self>)); using __self_t = __copy_cvref_t<_Self, __sexpr>; - if constexpr (__has_get_completion_signatures_impl<__tag_t, __self_t, _Env...>) + if constexpr (__has_get_completion_signatures_v<__tag_t, __self_t, _Env...>) { return __sexpr_impl<__tag_t>::template __get_completion_signatures<__self_t, _Env...>(); } - else if constexpr (__has_get_completion_signatures_impl<__tag_t, __self_t>) + else if constexpr (__has_get_completion_signatures_v<__tag_t, __self_t>) { return __sexpr_impl<__tag_t>::template __get_completion_signatures<__self_t>(); } diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index ecc6f8848..433dc2f44 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -856,10 +856,10 @@ namespace STDEXEC #if __cplusplus >= 2022'11L # define STDEXEC_CONSTEXPR_CXX23 constexpr -# define STDEXEC_STATIC_CONSTEXPR_LOCAL static constexpr +# define STDEXEC_CONSTEXPR_LOCAL static constexpr #else # define STDEXEC_CONSTEXPR_CXX23 -# define STDEXEC_STATIC_CONSTEXPR_LOCAL constexpr +# define STDEXEC_CONSTEXPR_LOCAL constexpr #endif //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/include/stdexec/__detail/__domain.hpp b/include/stdexec/__detail/__domain.hpp index 711da8efd..95af03ab4 100644 --- a/include/stdexec/__detail/__domain.hpp +++ b/include/stdexec/__detail/__domain.hpp @@ -143,7 +143,7 @@ namespace STDEXEC { template using __indeterminate_domain_t = __if_c>; template @@ -164,12 +164,15 @@ namespace STDEXEC } // namespace __detail //////////////////////////////////////////////////////////////////////////////////////////////// + template + using __completion_domain_t = __call_result_or_t, + indeterminate_domain<>, + _Attrs, + _Env const &...>; + template requires __sends<_Tag, _Sender, _Env...> - using __completion_domain_of_t = __call_result_or_t, - indeterminate_domain<>, - env_of_t<_Sender>, - _Env const &...>; + using __completion_domain_of_t = __completion_domain_t<_Tag, env_of_t<_Sender>, _Env const &...>; template using __common_domain_t = __t<__detail::__common_domain<_Domains...>>; @@ -235,10 +238,10 @@ namespace STDEXEC //! @brief A wrapper around an environment that hides the get_scheduler and get_domain //! queries. template - struct __hide_scheduler : __hide_query<_Env, get_scheduler_t, get_domain_t> + struct __hide_scheduler : __hide_query<_Env, get_scheduler_t> { constexpr explicit __hide_scheduler(_Env &&__env) noexcept - : __hide_query<_Env, get_scheduler_t, get_domain_t>{static_cast<_Env &&>(__env), {}, {}} + : __hide_query<_Env, get_scheduler_t>{static_cast<_Env &&>(__env), {}} {} }; @@ -262,18 +265,20 @@ namespace STDEXEC // accept an environment. struct __read_query_t { - template - requires __queryable_with<_Attrs, get_completion_domain_t> - constexpr auto operator()(_Attrs const &, __ignore = {}) const noexcept + template + requires(__queryable_with<_Attrs, get_completion_domain_t, _Env const &> || ...) + || __queryable_with<_Attrs, get_completion_domain_t> + constexpr auto operator()(_Attrs const &, _Env const &...) const noexcept { - return __decay_t<__query_result_t<_Attrs, get_completion_domain_t>>{}; - } - - template - requires __queryable_with<_Attrs, get_completion_domain_t, _Env const &> - constexpr auto operator()(_Attrs const &, _Env const &) const noexcept - { - return __decay_t<__query_result_t<_Attrs, get_completion_domain_t, _Env const &>>{}; + if constexpr ((__queryable_with<_Attrs, get_completion_domain_t, _Env const &> || ...)) + { + return (__decay_t<__query_result_t<_Attrs, get_completion_domain_t, _Env const &>>{}, + ...); + } + else + { + return __decay_t<__query_result_t<_Attrs, get_completion_domain_t>>{}; + } } }; @@ -396,6 +401,11 @@ namespace STDEXEC } }; + template + concept __has_completion_domain_for = + __sends<_Tag, _Sender, _Env...> + && __callable, env_of_t<_Sender>, _Env const &...>; + struct get_domain_t { template diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 711bec425..6d610d17d 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -92,6 +92,13 @@ namespace STDEXEC struct __join_fn { + template + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + constexpr auto operator()(_Env &&__env) const noexcept -> _Env + { + return static_cast<_Env &&>(__env); + } + template STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) constexpr auto operator()(_Env1 &&__env1, _Env2 &&__env2) const noexcept -> decltype(auto) @@ -145,7 +152,6 @@ namespace STDEXEC return __join(__root_env{}, static_cast &&>(__env)); } }; - } // namespace __env using __env::__join_env_t; @@ -211,6 +217,22 @@ namespace STDEXEC struct env<_Env> : _Env {}; + template + struct env<_Env &> + { + template + requires __queryable_with<_Env, _Query, _Args...> + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + constexpr auto query(_Query, _Args &&...__args) const + noexcept(__nothrow_queryable_with<_Env, _Query, _Args...>) + -> __query_result_t<_Env, _Query, _Args...> + { + return __query<_Query>()(__env_, static_cast<_Args &&>(__args)...); + } + + _Env &__env_; + }; + template struct env<_Env1, _Env2> { diff --git a/include/stdexec/__detail/__finally.hpp b/include/stdexec/__detail/__finally.hpp index fd00a9763..044ea0f01 100644 --- a/include/stdexec/__detail/__finally.hpp +++ b/include/stdexec/__detail/__finally.hpp @@ -37,10 +37,8 @@ namespace STDEXEC struct __sender; } // namespace __final - struct _THE_FINAL_SENDER_MUST_BE_A_SENDER_OF_VOID_ - {}; - struct _INVALID_ARGUMENT_TO_THE_FINALLY_ALGORITHM_ - {}; + struct _THE_FINAL_SENDER_MUST_BE_A_SENDER_OF_VOID_; + struct _INVALID_ARGUMENT_TO_THE_FINALLY_ALGORITHM_; struct __finally_t { @@ -125,10 +123,12 @@ namespace STDEXEC } }; - using __mk_secondary_env_t = __mk_secondary_env_t; + using __mk_secondary_env_t = + STDEXEC::__mk_secondary_env_t; - template - using __env2_t = __call_result_t<__mk_secondary_env_t, _CvInitialSender, _ReceiverEnv>; + template + using __env2_t = + __secondary_env_t<_CvInitialSender, _Env, set_value_t, set_error_t, set_stopped_t>; template using __final_env_t = __join_env_t<_Env2 const &, __fwd_env_t<_ReceiverEnv>>; @@ -234,6 +234,7 @@ namespace STDEXEC using __env2_t = __final::__env2_t<_CvInitialSender, env_of_t<_Receiver>>; using __base_t = __final_opstate_t<_CvInitialSender, _CvFinalSender, _Receiver>; using __initial_results_t = __base_t::__results_t; + using __cv_fn = __copy_cvref_fn<_CvInitialSender>; constexpr explicit __opstate(_CvInitialSender&& __initial, _CvFinalSender&& __final, @@ -241,7 +242,7 @@ namespace STDEXEC : __base_t(&__cleanup_initial_opstate, static_cast<_CvFinalSender&&>(__final), static_cast<_Receiver&&>(__rcvr), - __mk_secondary_env_t{}(__initial, STDEXEC::get_env(__rcvr))) + __mk_secondary_env_t{}(__cv_fn{}, __initial, get_env(__rcvr))) { __initial_opstate_.__emplace_from(STDEXEC::connect, static_cast<_CvInitialSender&&>(__initial), diff --git a/include/stdexec/__detail/__let.hpp b/include/stdexec/__detail/__let.hpp index 716053c64..039b0ade4 100644 --- a/include/stdexec/__detail/__let.hpp +++ b/include/stdexec/__detail/__let.hpp @@ -64,8 +64,8 @@ namespace STDEXEC using __on_not_callable = __mbind_front_q<__callable_error_t, decltype(__let_from_set<_SetTag>)>; - template - using __env2_t = __call_result_t<__mk_secondary_env_t<_SetTag>, _Sender, _Env const &...>; + template + using __env2_t = __secondary_env_t<_Sender, _Env, _SetTag>; template using __result_env_t = __join_env_t<__env2_t<_SetTag, _Sender, _Env>, _Env>; @@ -103,8 +103,7 @@ namespace STDEXEC _Env2 const & __env_; }; - struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_ - {}; + struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_; template struct _NESTED_ERROR_; @@ -189,8 +188,7 @@ namespace STDEXEC __cmplsigs::__default_completion, __mtry_q<__concat_completion_signatures_t>::__f>; - struct _THE_SENDERS_RETURNED_BY_THE_GIVEN_FUNCTION_DO_NOT_SHARE_A_COMMON_DOMAIN_ - {}; + struct _THE_SENDERS_RETURNED_BY_THE_GIVEN_FUNCTION_DO_NOT_SHARE_A_COMMON_DOMAIN_; template struct __try_common_domain_fn @@ -262,15 +260,16 @@ namespace STDEXEC using __env2_t = _Env2; using __second_rcvr_t = __rcvr_env<_Receiver, _Env2>; - template + template constexpr explicit __opstate_base(_SetTag, + _CvFn __cv, _Sender const & __sndr, _Fun __fn, _Receiver&& __rcvr) noexcept : __rcvr_(static_cast<_Receiver&&>(__rcvr)) , __fn_(STDEXEC::__allocator_aware_forward(static_cast<_Fun&&>(__fn), __rcvr_)) // TODO(ericniebler): this needs a fallback: - , __env2_(__mk_secondary_env_t<_SetTag>()(__sndr, STDEXEC::get_env(__rcvr_))) + , __env2_(__mk_secondary_env_t<_SetTag>()(__cv, __sndr, STDEXEC::get_env(__rcvr_))) {} constexpr virtual void __start_next() = 0; @@ -368,12 +367,12 @@ namespace STDEXEC //! The core of the operation state for `let_*`. //! This gets bundled up into a larger operation state (`__detail::__op_state<...>`). - template + template struct __opstate final : __opstate_base<_SetTag, _Fun, _Receiver, - __let::__env2_t<_SetTag, _Child, env_of_t<_Receiver>>, + __let::__env2_t<_SetTag, _CvChild, env_of_t<_Receiver>>, _Tuples...> { using __env2_t = __opstate::__opstate_base::__env2_t; @@ -381,19 +380,20 @@ namespace STDEXEC using __second_rcvr_t = __opstate::__opstate_base::__second_rcvr_t; using __op_state_variant_t = - __variant, + __variant, __mapply<__submit_datum_for<_Receiver, _Fun, _SetTag, __env2_t>, _Tuples>...>; - constexpr explicit __opstate(_Child&& __child, _Fun __fn, _Receiver&& __rcvr) - noexcept(__nothrow_connectable<_Child, __first_rcvr_t> + constexpr explicit __opstate(_CvChild&& __child, _Fun __fn, _Receiver&& __rcvr) + noexcept(__nothrow_connectable<_CvChild, __first_rcvr_t> && __nothrow_move_constructible<_Fun>) : __opstate::__opstate_base(_SetTag(), + __copy_cvref_fn<_CvChild>{}, __child, static_cast<_Fun&&>(__fn), static_cast<_Receiver&&>(__rcvr)) { __storage_.__emplace_from(STDEXEC::connect, - static_cast<_Child&&>(__child), + static_cast<_CvChild&&>(__child), __first_rcvr_t{this}); } diff --git a/include/stdexec/__detail/__parallel_scheduler_backend.hpp b/include/stdexec/__detail/__parallel_scheduler_backend.hpp index a259b014f..80627915e 100644 --- a/include/stdexec/__detail/__parallel_scheduler_backend.hpp +++ b/include/stdexec/__detail/__parallel_scheduler_backend.hpp @@ -23,6 +23,7 @@ #include "../functional.hpp" // IWYU pragma: keep for __with_default #include "../stop_token.hpp" // IWYU pragma: keep for get_stop_token_t #include "__any_allocator.hpp" +#include "__inline_scheduler.hpp" #include "__optional.hpp" #include "__queries.hpp" #include "__schedulers.hpp" @@ -322,9 +323,14 @@ namespace STDEXEC return __token ? *__token : inplace_stop_token{}; } - // Implemented in __task_scheduler.hpp + // A template because task_scheduler is not a complete type yet. + template [[nodiscard]] - auto query(get_scheduler_t) const noexcept -> task_scheduler; + auto query(get_scheduler_t) const noexcept -> _TaskScheduler + { + auto __sched = __rcvr_.template try_query<_TaskScheduler>(get_scheduler); + return __sched ? *__sched : _TaskScheduler(inline_scheduler{}); + } system_context_replaceability::receiver_proxy& __rcvr_; }; diff --git a/include/stdexec/__detail/__schedulers.hpp b/include/stdexec/__detail/__schedulers.hpp index bb7266c07..a3dd63433 100644 --- a/include/stdexec/__detail/__schedulers.hpp +++ b/include/stdexec/__detail/__schedulers.hpp @@ -150,24 +150,25 @@ namespace STDEXEC // accept an environment. struct __read_query_t { - template - requires __queryable_with<_Attrs, _GetComplSch> - constexpr auto operator()(_Attrs const &__attrs, __ignore = {}) const noexcept - -> __decay_t<__query_result_t<_Attrs, _GetComplSch>> - { - static_assert(noexcept(__attrs.query(_GetComplSch{}))); - static_assert(scheduler<__query_result_t<_Attrs, _GetComplSch>>); - return __attrs.query(_GetComplSch{}); - } + using __self_t = get_completion_scheduler_t; - template - requires __queryable_with<_Attrs, _GetComplSch, _Env const &> - constexpr auto operator()(_Attrs const &__attrs, _Env const &__env) const noexcept - -> __decay_t<__query_result_t<_Attrs, _GetComplSch, _Env const &>> + template + requires(__queryable_with<_Attrs, __self_t, _Env const &> || ...) + || __queryable_with<_Attrs, __self_t> + constexpr auto operator()(_Attrs const &__attrs, _Env const &...__env) const noexcept { - static_assert(noexcept(__attrs.query(_GetComplSch{}, __env))); - static_assert(scheduler<__query_result_t<_Attrs, _GetComplSch, _Env const &>>); - return __attrs.query(_GetComplSch{}, __env); + if constexpr ((__queryable_with<_Attrs, __self_t, _Env const &> || ...)) + { + static_assert(noexcept(__attrs.query(__self_t{}, __env...))); + static_assert(scheduler<__query_result_t<_Attrs, __self_t, _Env const &...>>); + return __attrs.query(__self_t{}, __env...); + } + else + { + static_assert(noexcept(__attrs.query(__self_t{}))); + static_assert(scheduler<__query_result_t<_Attrs, __self_t>>); + return __attrs.query(__self_t{}); + } } }; @@ -304,6 +305,11 @@ namespace STDEXEC } }; + template + concept __has_completion_scheduler_for = + __sends<_Tag, _Sender, _Env...> + && __callable, env_of_t<_Sender>, _Env const &...>; + struct __execute_may_block_caller_t : __query<__execute_may_block_caller_t, true> { template @@ -352,7 +358,7 @@ namespace STDEXEC template requires __sends<_Tag, _Sender, _Env...> using __completion_scheduler_of_t = - __call_result_t, env_of_t<_Sender>, const _Env &...>; + __call_result_t, env_of_t<_Sender>, _Env const &...>; // TODO(ericniebler): examine all uses of this struct. template @@ -430,7 +436,7 @@ namespace STDEXEC } else { - return __sched_env{std::move(__sch)}; + return __sched_env{std::move(__sch), __env}; } } @@ -439,17 +445,15 @@ namespace STDEXEC template struct __mk_secondary_env_impl { - template - constexpr auto operator()(_Sender const &__sndr, _Env const &__env) const noexcept + template + constexpr auto operator()(_CvFn, _Sender const &__sndr, _Env const &__env) const noexcept { - using __env_t = __fwd_env_t<_Env const &>; - using __attrs_t = env_of_t<_Sender const &>; using __domain_t = __make_domain_t<__completion_domain_of_t<_SetTags, _Sender, _Env>...>; // We can only know the scheduler that the secondary sender is started on if there // is exactly one kind of completion that starts the secondary sender. - constexpr bool __has_completion_scheduler = + STDEXEC_CONSTEXPR_LOCAL bool __has_completion_scheduler = sizeof...(_SetTags) == 1 - && (__callable, __attrs_t, __env_t> || ...); + && (__has_completion_scheduler_for<_SetTags, __mcall1<_CvFn, _Sender>, _Env> || ...); if constexpr (__has_completion_scheduler) { @@ -473,12 +477,12 @@ namespace STDEXEC //! be used as the scheduler/domain for the second sender. //! //! This environment is used by the \c let_[value|error|stopped] algorithms as well as - //! the \c finally algorithm. + //! the \c finally algorithm and \c sequence algorithms. //! //! \note This env assumes that the results of the first sender are decay-copied into //! the operation state of the composite sender. //! - //! \tparam _PrimarySender The sender whose completion is starting the next sender. + //! \tparam _CvSender The sender whose completion is starting the next sender. //! \tparam _Env The environment of the receiver connected to the primary sender. //! \tparam _SetTags The completions that cause the next sender to start. For example, //! for \c let_value, this would be \c set_value_t, and for \c finally, this would be @@ -486,18 +490,22 @@ namespace STDEXEC template struct __mk_secondary_env_t { - template - constexpr auto operator()(_PrimarySender const &__sndr, _Env const &__env) const noexcept + template + constexpr auto operator()(_CvFn __cv, _Sender const &__sndr, _Env const &__env) const noexcept { using namespace __detail; using __env_t = __fwd_env_t<_Env const &>; - using __never_sends_fn = __mbind_back_q<__never_sends_t, _PrimarySender, __env_t>; - using __remove_if_fn = __mremove_if<__never_sends_fn, __qq<__mk_secondary_env_impl>>; - using __impl_t = __minvoke<__remove_if_fn, _SetTags...>; - return __impl_t{}(__sndr, __env); + using __never_sends_fn = __mbind_back_q<__never_sends_t, _Sender, __env_t>; + using __make_env_fn = __mremove_if<__never_sends_fn, __qq<__mk_secondary_env_impl>>; + using __impl_t = __minvoke<__make_env_fn, _SetTags...>; + return __impl_t{}(__cv, __sndr, __env); } }; + template + using __secondary_env_t = + __call_result_t<__mk_secondary_env_t<_SetTags...>, __copy_cvref_fn<_CvSender>, _CvSender, _Env>; + ////////////////////////////////////////////////////////////////////////////////////////// // __infallible_scheduler template diff --git a/include/stdexec/__detail/__sender_concepts.hpp b/include/stdexec/__detail/__sender_concepts.hpp index 2ae837442..622d12b97 100644 --- a/include/stdexec/__detail/__sender_concepts.hpp +++ b/include/stdexec/__detail/__sender_concepts.hpp @@ -103,6 +103,15 @@ namespace STDEXEC template using __never_sends_t = __mbool<__never_sends<_Tag, _Sender, _Env...>>; + template + using __is_eptr = __mbool<__decays_to<_Error, std::exception_ptr>>; + + template + concept __has_eptr_completion = sender_in<_Sender, _Env...> // + && __error_types_t<__completion_signatures_of_t<_Sender, _Env...>, + __q1<__is_eptr>, + __qq<__mor_t>>::value; + template concept __single_value_sender = sender_in<_Sender, _Env...> // && requires { typename __single_sender_value_t<_Sender, _Env...>; }; diff --git a/include/stdexec/__detail/__sequence.hpp b/include/stdexec/__detail/__sequence.hpp index 7c1b0f404..0a18dd383 100644 --- a/include/stdexec/__detail/__sequence.hpp +++ b/include/stdexec/__detail/__sequence.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 NVIDIA Corporation + * Copyright (c) 2026 NVIDIA Corporation * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with @@ -15,369 +15,482 @@ */ #pragma once -#include "__concepts.hpp" +#include "__execution_fwd.hpp" + +// include these after __execution_fwd.hpp +#include "__basic_sender.hpp" #include "__connect.hpp" -#include "__receivers.hpp" +#include "__just.hpp" +#include "__schedulers.hpp" +#include "__senders.hpp" #include "__transform_completion_signatures.hpp" -#include "__tuple.hpp" #include "__variant.hpp" -#include - STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_EDG(expr_has_no_effect) +STDEXEC_PRAGMA_IGNORE_EDG(type_qualifiers_ignored_on_reference) STDEXEC_PRAGMA_IGNORE_GNU("-Wmissing-braces") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-value") namespace STDEXEC { + struct __sequence_t; + struct _ALL_SENDERS_BUT_THE_LAST_MUST_BE_SENDERS_OF_VOID_; + namespace __seq { + using __mk_env2_t = __mk_secondary_env_t; + + template + using __env2_t = __secondary_env_t<_CvSender1, _Env, set_value_t>; + template struct __sndr; - } // namespace __seq - struct __sequence_t - { - template - STDEXEC_ATTRIBUTE(nodiscard, host, device) - constexpr auto operator()(_Sender __sndr) const - noexcept(STDEXEC::__nothrow_move_constructible<_Sender>) -> _Sender; + ////////////////////////////////////////////////////////////////////////////////////// + // Attributes for __sequence. This is a bit more complicated than the attributes for + // other algorithms because we need to be able to query the completion scheduler (for + // example) of the second sender from the context of the first sender's completions. + template + struct __attrs; - template - requires(sizeof...(_Senders) > 1) - STDEXEC_ATTRIBUTE(nodiscard, host, device) - constexpr auto operator()(_Senders... sndrs) const - noexcept(STDEXEC::__nothrow_move_constructible<_Senders...>) -> __seq::__sndr<_Senders...>; - }; + template <> + struct __attrs<> : __just::__attrs + {}; - namespace __seq - { - template - struct __opstate_base + template + struct __attrs<_CvSender> { - template - STDEXEC_ATTRIBUTE(host, device) - constexpr void __set_value([[maybe_unused]] Args&&... args) noexcept + template + requires __queryable_with, _Query, _Args...> + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + constexpr auto operator()(_Query, _Args &&...__args) const + noexcept(__nothrow_queryable_with, _Query, _Args...>) + -> __query_result_t, _Query, _Args...> { - STDEXEC::set_value(static_cast<_Receiver&&>(__rcvr_), static_cast(args)...); + return __query<_Query>()(STDEXEC::get_env(__sndr_), static_cast<_Args &&>(__args)...); } - STDEXEC_ATTRIBUTE(host, device) - constexpr void __start_next() noexcept + _CvSender __sndr_; + }; + + template + struct __attrs<_CvSender1, _CvSender2> + { + private: + using __joined_t = env, env_of_t<_CvSender1>>; + + // The env to use when querying _CvSender2: + template + using __env2_t = __secondary_env_t<_CvSender1 const &, _Env, set_value_t>; + + template + constexpr auto __mk_env2(_Env &__env) const noexcept -> __env2_t<_Env> + { + return __mk_env2_t()(__cp{}, __sndr1_, __env); + } + + public: + template + requires __has_completion_scheduler_for...> + [[nodiscard]] + constexpr auto query(get_completion_scheduler_t, _Env &&...__env) const noexcept + { + return STDEXEC::get_completion_scheduler(STDEXEC::get_env(__sndr2_), + __mk_env2(__env)...); + } + + // We only know the error or stopped completion scheduler if exactly one of the two + // senders knows its error/stopped completion scheduler. + template <__one_of _Tag, class... _Env> + requires(__has_completion_scheduler_for<_Tag, _CvSender1, __fwd_env_t<_Env>...> + != __has_completion_scheduler_for<_Tag, _CvSender2, __env2_t<_Env>...>) + [[nodiscard]] + constexpr auto query(get_completion_scheduler_t<_Tag>, _Env &&...__env) const noexcept + { + if constexpr (__has_completion_scheduler_for<_Tag, _CvSender2, __env2_t<_Env>...>) + { + return STDEXEC::get_completion_scheduler<_Tag>(STDEXEC::get_env(__sndr2_), + __mk_env2(__env)...); + } + else + { + return STDEXEC::get_completion_scheduler<_Tag>(STDEXEC::get_env(__sndr1_), + __fwd_env(__env)...); + } + } + + // The value completion domain is the domain of the second sender, started from the + // completing context of the first sender. + template + [[nodiscard]] + constexpr auto query(get_completion_domain_t, _Env &&...) const noexcept + { + using __domain_t = + __completion_domain_t, __env2_t<_Env>...>; + return __domain_t(); + } + + // The set_error/set_stopped completion domains are the common domain of the two + // senders. + template <__one_of _Tag, class... _Env> + [[nodiscard]] + constexpr auto query(get_completion_domain_t<_Tag>, _Env &&...) const noexcept + { + using __domain_t = + __common_domain_t<__completion_domain_t<_Tag, env_of_t<_CvSender1>, __fwd_env_t<_Env>...>, + __completion_domain_t<_Tag, env_of_t<_CvSender2>, __env2_t<_Env>...>>; + return __domain_t(); + } + + // The completion behavior of a pair of senders in sequence is the weakest + // completion behavior of the two senders. + template + [[nodiscard]] + constexpr auto query(__get_completion_behavior_t<_Tag>, _Env &&...) const noexcept { - (*__start_next_)(this); + return __completion_behavior::__common( + STDEXEC::__get_completion_behavior<_Tag, _CvSender1, __fwd_env_t<_Env>...>(), + STDEXEC::__get_completion_behavior<_Tag, _CvSender2, __env2_t<_Env>...>()); } - _Receiver __rcvr_; - void (*__start_next_)(__opstate_base*) noexcept = nullptr; + // For queries that are not related to completion schedulers, domains, or behaviors, + // we can just check _CvSender2 and then _CvSender1. + template <__forwarding_query _Query, class... _Args> + requires(!__completion_query<_Query>) && __queryable_with<__joined_t, _Query, _Args...> + [[nodiscard]] + constexpr auto operator()(_Query, _Args &&...__args) const + noexcept(__nothrow_queryable_with<__joined_t, _Query, _Args...>) + -> __query_result_t<__joined_t, _Query, _Args...> + { + return __query<_Query>()(__joined_t{STDEXEC::get_env(__sndr2_), STDEXEC::get_env(__sndr1_)}, + static_cast<_Args &&>(__args)...); + } + + _CvSender1 __sndr1_; + _CvSender2 __sndr2_; }; - template - struct __rcvr_base + template + struct __attrs<_Child1, _Child2, _Rest...> : __attrs<__sndr<_Child1, _Child2>, _Rest...> + {}; + + ////////////////////////////////////////////////////////////////////////////////////// + // __state: the part of the opstate that is referenced by __rcvr2. + template + struct __state { - using receiver_concept = STDEXEC::receiver_t; + constexpr explicit __state(_Receiver &&__rcvr, _Env2 &&__env2) noexcept + : __rcvr_(static_cast<_Receiver &&>(__rcvr)) + , __env_(static_cast<_Env2 &&>(__env2)) + {} + + virtual constexpr void __start_next() noexcept = 0; + + _Receiver __rcvr_; + STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS + _Env2 const __env_; + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // __rcvr2: the receiver that is connected to the successor sender. + template + struct __rcvr2 + { + using receiver_concept = receiver_t; + using __env_t = __join_env_t<_Env2 const &, __fwd_env_t>>; + + template + constexpr void set_value(_As &&...__as) noexcept + { + STDEXEC::set_value(static_cast<_Receiver &&>(__self_->__rcvr_), + static_cast<_As &&>(__as)...); + } template - STDEXEC_ATTRIBUTE(host, device) - constexpr void set_error(_Error&& err) && noexcept + constexpr void set_error(_Error &&__error) noexcept { - STDEXEC::set_error(static_cast<_Receiver&&>(__opstate_->__rcvr_), - static_cast<_Error&&>(err)); + STDEXEC::set_error(static_cast<_Receiver &&>(__self_->__rcvr_), + static_cast<_Error &&>(__error)); } - STDEXEC_ATTRIBUTE(host, device) void set_stopped() && noexcept + constexpr void set_stopped() noexcept { - STDEXEC::set_stopped(static_cast<_Receiver&&>(__opstate_->__rcvr_)); + STDEXEC::set_stopped(static_cast<_Receiver &&>(__self_->__rcvr_)); } - // TODO: use the predecessor's completion scheduler as the current scheduler here. - STDEXEC_ATTRIBUTE(nodiscard, host, device) - constexpr auto get_env() const noexcept -> STDEXEC::env_of_t<_Receiver> + [[nodiscard]] + constexpr auto get_env() const noexcept -> __env_t { - return STDEXEC::get_env(__opstate_->__rcvr_); + return __env::__join(__self_->__env_, __fwd_env(STDEXEC::get_env(__self_->__rcvr_))); } - __opstate_base<_Receiver>* __opstate_; + __state<_Receiver, _Env2> *__self_; }; - template - struct __rcvr : __rcvr_base<_Receiver> + template + struct __rcvr1 { - using receiver_concept = STDEXEC::receiver_t; + using receiver_concept = receiver_t; - template - STDEXEC_ATTRIBUTE(always_inline, host, device) - constexpr void set_value(_Args&&... __args) && noexcept + constexpr void set_value() noexcept { - if constexpr (_IsLast) - { - this->__opstate_->__set_value(static_cast<_Args&&>(__args)...); - } - else - { - this->__opstate_->__start_next(); - } + __state_->__start_next(); } - }; - template - struct __convert_tuple_fn - { - template - STDEXEC_ATTRIBUTE(host, device, always_inline) - constexpr _Tuple operator()(_Ts&&... __ts) const - noexcept(STDEXEC::__nothrow_constructible_from<_Tuple, _Ts...>) + template + constexpr void set_error(_Error &&__error) noexcept { - return _Tuple{static_cast<_Ts&&>(__ts)...}; + STDEXEC::set_error(std::move(__state_->__rcvr_), static_cast<_Error &&>(__error)); + } + + constexpr void set_stopped() noexcept + { + STDEXEC::set_stopped(std::move(__state_->__rcvr_)); + } + + [[nodiscard]] + constexpr auto get_env() const noexcept -> __fwd_env_t> + { + return __fwd_env(STDEXEC::get_env(__state_->__rcvr_)); } - }; - template - struct __opstate; + __state<_Receiver, _Env2> *__state_; + }; - template - struct __opstate<_Receiver, _CvSender0, _Senders...> : __opstate_base<_Receiver> + template + struct __opstate final : __state<_Receiver, __env2_t<_CvSender1, env_of_t<_Receiver>>> { - using operation_state_concept = STDEXEC::operation_state_t; - - // We will be connecting the first sender in the opstate constructor, so we don't need to - // store it in the opstate. The use of `STDEXEC::__ignore` causes the first sender to not - // be stored. - using __senders_t = STDEXEC::__tuple; - - template - using __rcvr_t = __seq::__rcvr<_Receiver, IsLast>; - - template - using __child_opstate_t = STDEXEC::connect_result_t<_Sender, __rcvr_t>; - - using __mk_child_ops_variant_fn = - STDEXEC::__mzip_with2, STDEXEC::__qq>; - - using __is_last_mask_t = - STDEXEC::__mfill_c>; - - using __ops_variant_t = STDEXEC::__minvoke<__mk_child_ops_variant_fn, - STDEXEC::__tuple<_CvSender0, _Senders...>, - __is_last_mask_t>; - - template - STDEXEC_ATTRIBUTE(host, device) - constexpr explicit __opstate(_Receiver&& __rcvr, _CvSenders&& __sndrs) - noexcept(::STDEXEC::__nothrow_applicable<__convert_tuple_fn<__senders_t>, _CvSenders> - && ::STDEXEC::__nothrow_connectable<::STDEXEC::__tuple_element_t<0, _CvSenders>, - __rcvr_t>) - : __opstate_base<_Receiver>{static_cast<_Receiver&&>(__rcvr)} - // move all but the first sender into the opstate: - , __sndrs_{ - STDEXEC::__apply(__convert_tuple_fn<__senders_t>{}, static_cast<_CvSenders&&>(__sndrs))} + using __cv_fn = __copy_cvref_fn<_CvSender1>; + + constexpr explicit __opstate(_CvSender1 &&__sndr1, _Sender2 __sndr2, _Receiver __rcvr) + noexcept(__nothrow_constructible) + : __opstate(__sndr1, __sndr2, __rcvr, __mk_env2_t()(__cv_fn{}, __sndr1, get_env(__rcvr))) + {} + + STDEXEC_IMMOVABLE(__opstate); + + void start() & noexcept { - // 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 result is that the first sender in `__sndrs` is not - // moved from, but the rest are. - __ops_.template __emplace_from<0>(STDEXEC::connect, - STDEXEC::__get<0>(static_cast<_CvSenders&&>(__sndrs)), - __rcvr_t{this}); + STDEXEC::start(__var::__get<0>(__opstate_)); } - template - static constexpr void __start_next(__opstate_base<_Receiver>* __self_) noexcept + constexpr void __start_next() noexcept { - constexpr auto __nth = sizeof...(_Senders) - _Remaining; - auto* __self = static_cast<__opstate*>(__self_); - auto& __sndr = STDEXEC::__get<__nth + 1>(__self->__sndrs_); - constexpr bool __is_nothrow = - STDEXEC::__nothrow_connectable, - __rcvr_t<_Remaining == 1>>; STDEXEC_TRY { - auto& __op = - __self->__ops_.template __emplace_from<__nth + 1>(STDEXEC::connect, - std::move(__sndr), - __rcvr_t<(_Remaining == 1)>{__self}); - if constexpr (_Remaining > 1) - { - __self->__start_next_ = &__start_next<_Remaining - 1>; - } + auto &__op = // + __opstate_.__emplace_from(STDEXEC::connect, std::move(__sndr2_), __rcvr2_t{this}); STDEXEC::start(__op); } STDEXEC_CATCH_ALL { - if constexpr (__is_nothrow) - { - STDEXEC::__std::unreachable(); - } - else + if constexpr (!__nothrow_connectable<_Sender2, __rcvr2_t>) { - STDEXEC::set_error(static_cast<_Receiver&&>(static_cast<__opstate*>(__self_)->__rcvr_), - std::current_exception()); + STDEXEC::set_error(std::move(this->__rcvr_), std::current_exception()); } } } - STDEXEC_ATTRIBUTE(host, device) - constexpr void start() noexcept + private: + using __env2_t = __seq::__env2_t<_CvSender1, env_of_t<_Receiver>>; + using __rcvr1_t = __rcvr1<_Receiver, __env2_t>; + using __rcvr2_t = __rcvr2<_Receiver, __env2_t>; + using __opstate1_t = connect_result_t<_CvSender1, __rcvr1_t>; + using __opstate2_t = connect_result_t<_Sender2, __rcvr2_t>; + + static constexpr bool __nothrow_constructible = __nothrow_move_constructible<_Sender2> + && __nothrow_connectable<_CvSender1, __rcvr1_t>; + + constexpr explicit __opstate(_CvSender1 &__sndr1, + _Sender2 &__sndr2, + _Receiver &__rcvr, + __env2_t __env2) noexcept(__nothrow_constructible) + : __state<_Receiver, __env2_t>{std::move(__rcvr), std::move(__env2)} + , __sndr2_(std::move(__sndr2)) { - if (sizeof...(_Senders) != 0) - { - this->__start_next_ = &__start_next; - } - STDEXEC::start(STDEXEC::__var::__get<0>(__ops_)); + __opstate_.__emplace_from(STDEXEC::connect, + static_cast<_CvSender1 &&>(__sndr1), + __rcvr1_t{this}); } - __senders_t __sndrs_; - __ops_variant_t __ops_{STDEXEC::__no_init}; + _Sender2 __sndr2_; + __variant<__opstate1_t, __opstate2_t> __opstate_{__no_init}; }; - template - concept __has_eptr_completion = - STDEXEC::sender_in<_Sender> - && __transform_completion_signatures(STDEXEC::get_completion_signatures<_Sender>(), - __ignore_completion(), - __decay_arguments(), - __ignore_completion()) - .__contains(STDEXEC::__fn_ptr_t()); - - template - struct __sndr<_Sender0, _Senders...> + template + struct __eat_value_signatures { - using sender_concept = STDEXEC::sender_t; - using __senders_t = STDEXEC::__tuple<_Sender0, _Senders...>; - - // 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 - requires(sizeof...(_Env) > 0) - || __has_eptr_completion> - || (__has_eptr_completion<_Senders> || ...) - STDEXEC_ATTRIBUTE(host, device) - static consteval auto get_completion_signatures() - { - if constexpr (!STDEXEC::__decay_copyable<_Self>) - { - return STDEXEC::__throw_compile_time_error< - STDEXEC::_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_, - STDEXEC::_WITH_PRETTY_SENDER_<__sndr<_Sender0, _Senders...>>>(); - } - else - { - using __env_t = STDEXEC::__mfront<_Env..., STDEXEC::env<>>; - using __rcvr_t = STDEXEC::__receiver_archetype<__env_t>; - constexpr bool __is_nothrow = (STDEXEC::__nothrow_connectable<_Senders, __rcvr_t> && ...); - - // 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 __concat_completion_signatures( - __transform_completion_signatures( - STDEXEC::get_completion_signatures, - _Env...>(), - __ignore_completion()), - __transform_completion_signatures( - STDEXEC::get_completion_signatures<_Senders, _Env...>(), - __ignore_completion())..., - STDEXEC::get_completion_signatures, _Env...>(), - STDEXEC::__eptr_completion_unless_t<__mbool<__is_nothrow>>()); - } - } + template + using __error_t = __mexception<_WHAT_(_INVALID_ARGUMENT_), + _WHERE_(_IN_ALGORITHM_, __sequence_t), + _WHY_(_ALL_SENDERS_BUT_THE_LAST_MUST_BE_SENDERS_OF_VOID_), + _WITH_PRETTY_SENDER_<_Self>>; - template - STDEXEC_ATTRIBUTE(host, device) - constexpr STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this _Self&& __self, _Receiver __rcvr) - noexcept(STDEXEC::__nothrow_constructible_from< - __opstate<_Receiver, STDEXEC::__copy_cvref_t<_Self, _Sender0>, _Senders...>, - _Receiver, - __copy_cvref_t<_Self, __senders_t>>) + template + constexpr auto operator()() const noexcept { - return __opstate<_Receiver, STDEXEC::__copy_cvref_t<_Self, _Sender0>, _Senders...>{ - static_cast<_Receiver&&>(__rcvr), - static_cast<_Self&&>(__self).__sndrs_}; + // This fold over the comma operator returns completion_signatures{} if _Args is + // empty, and otherwise results in a compile-time error. The predecessor sender + // should be a sender of void, so it's an error if _Args is not empty. + return (completion_signatures{}, + ..., + STDEXEC::__throw_compile_time_error(__error_t<_Args>())); } - STDEXEC_EXPLICIT_THIS_END(connect) + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // Default implementation of the __sequence sender algorithm. + template + struct __sndr<_Sender1, _Sender2> + { + using sender_concept = sender_t; - template - STDEXEC_ATTRIBUTE(always_inline, host, device) - static constexpr auto&& __static_get(_Self&& __self) noexcept + template + using __opstate_t = __opstate<__copy_cvref_t<_Self, _Sender1>, _Sender2, _Receiver>; + + template + using __env2_t = __join_env_t<__seq::__env2_t<__copy_cvref_t<_Self, _Sender1>, _Env> const &, + __fwd_env_t<_Env>>; + + using __attrs_t = __attrs<_Sender1 const &, _Sender2 const &>; + + template + requires(sizeof...(_Env) != 0) // + || __has_eptr_completion<__copy_cvref_t<_Self, _Sender1>> + || __has_eptr_completion<_Sender2> + static consteval auto get_completion_signatures() { - if constexpr (_Index == 0) + using __cv_sender1_t = __copy_cvref_t<_Self, _Sender1>; + + if constexpr (!__decay_copyable<_Self>) { - return static_cast<_Self&&>(__self).__tag_; + return STDEXEC::__throw_compile_time_error<_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_, + _WITH_PRETTY_SENDER_<_Self>>(); } - else if constexpr (_Index == 1) + else if constexpr (!__sends...>) { - return static_cast<_Self&&>(__self).__ign_; + // If the first sender has no set_value completions, then the second sender will + // never be started, so just return the (error and stopped) completions of the + // first sender. + return STDEXEC::get_completion_signatures<__cv_sender1_t, __fwd_env_t<_Env>...>(); } else { - return STDEXEC::__get<_Index - 2>(static_cast<_Self&&>(__self).__sndrs_); + constexpr bool __nothrow_connect2 = + (__nothrow_connectable<_Sender2, __receiver_archetype<__env2_t<_Self, _Env>>> && ...); + + auto __completions1 = // + STDEXEC::__transform_completion_signatures( + STDEXEC::get_completion_signatures<__cv_sender1_t, __fwd_env_t<_Env>...>(), + __eat_value_signatures<_Self>{}); + + auto __completions2 = + STDEXEC::get_completion_signatures<_Sender2, __env2_t<_Self, _Env>...>(); + + auto __eptr_completions = + STDEXEC::__eptr_completion_unless_t<__mbool<__nothrow_connect2>>(); + + return STDEXEC::__concat_completion_signatures(__completions1, + __completions2, + __eptr_completions); } } - template - STDEXEC_ATTRIBUTE(always_inline, host, device) - constexpr auto&& get() && noexcept + template + constexpr STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this _Self &&__self, _Receiver __rcvr) + noexcept(__nothrow_constructible_from<__opstate_t<_Self, _Receiver>, + __copy_cvref_t<_Self, _Sender1>, + _Sender2, + _Receiver>) { - return __static_get<_Index>(static_cast<__sndr&&>(*this)); + return __opstate_t<_Self, _Receiver>(static_cast<_Self &&>(__self).__sndr1_, + static_cast<_Self &&>(__self).__sndr2_, + static_cast<_Receiver &&>(__rcvr)); } + STDEXEC_EXPLICIT_THIS_END(connect) - template - STDEXEC_ATTRIBUTE(always_inline, host, device) - constexpr auto&& get() & noexcept + [[nodiscard]] + constexpr auto get_env() const noexcept -> __attrs_t { - return __static_get<_Index>(*this); + return __attrs_t{__sndr1_, __sndr2_}; } - template - STDEXEC_ATTRIBUTE(always_inline, host, device) - constexpr auto&& get() const & noexcept + _Sender1 __sndr1_; + _Sender2 __sndr2_; + }; + + template + struct __sndr<_Sender1, _Sender2, _Senders...> // + : __sndr<__sndr<_Sender1, _Sender2>, _Senders...> + {}; + + struct __impls : __sexpr_defaults + { + static constexpr auto __get_attrs = // + [](auto, auto, _Child const &...__child) noexcept // + -> __attrs<_Child const &...> { - return __static_get<_Index>(*this); - } + return __attrs<_Child const &...>{__child...}; + }; - STDEXEC_ATTRIBUTE(no_unique_address, maybe_unused) __sequence_t __tag_; - STDEXEC_ATTRIBUTE(no_unique_address, maybe_unused) STDEXEC::__ __ign_; - __senders_t __sndrs_; + template + static consteval auto __get_completion_signatures() + { + using __sndr_t = transform_sender_result_t<_Self, env<>>; + return STDEXEC::get_completion_signatures<__sndr_t>(); + } }; } // namespace __seq - template - STDEXEC_ATTRIBUTE(host, device) - constexpr auto __sequence_t::operator()(_Sender __sndr) const - noexcept(STDEXEC::__nothrow_move_constructible<_Sender>) -> _Sender + struct __sequence_t { - return __sndr; - } + template + [[nodiscard]] + constexpr auto operator()(_Senders &&...__sndrs) const // + noexcept(__nothrow_decay_copyable<_Senders...>) -> __well_formed_sender auto + { + return __make_sexpr<__sequence_t>({}, static_cast<_Senders &&>(__sndrs)...); + } - template - requires(sizeof...(_Senders) > 1) - STDEXEC_ATTRIBUTE(host, device) - constexpr auto __sequence_t::operator()(_Senders... sndrs) const - noexcept(STDEXEC::__nothrow_move_constructible<_Senders...>) -> __seq::__sndr<_Senders...> - { - return __seq::__sndr<_Senders...>{{}, {}, {static_cast<_Senders&&>(sndrs)...}}; - } + template + static constexpr auto transform_sender(set_value_t, _Self &&__self, __ignore) // + -> decltype(auto) + { + constexpr std::size_t __nbr_sndrs = __nbr_children_of<_Self>; + if constexpr (__nbr_sndrs == 0) + { + return just(); + } + else if constexpr (__nbr_sndrs == 1) + { + return static_cast<__tuple_element_t<2, _Self>>( + STDEXEC::__get<2>(static_cast<_Self &&>(__self))); + } + else + { + return __apply(__mk_sndr, static_cast<_Self &&>(__self)); + } + } + + private: + static constexpr auto __mk_sndr = [](__ignore, __ignore, _Child &&...__child) + { + return __seq::__sndr<__decay_t<_Child>...>{static_cast<_Child &&>(__child)...}; + }; + }; inline constexpr __sequence_t __sequence{}; -} // namespace STDEXEC -namespace exec = experimental::execution; + template <> + struct __sexpr_impl<__sequence_t> : __seq::__impls + {}; -namespace std -{ + // __seq::__sndr is the result of a sender transform. It should not be transformed + // further. template - struct tuple_size> - : std::integral_constant - {}; + inline constexpr auto __structured_binding_size_v<__seq::__sndr<_Senders...>> = -1; - template - struct tuple_element> - { - using type = STDEXEC::__m_at_c; - }; -} // namespace std +} // namespace STDEXEC STDEXEC_PRAGMA_POP() diff --git a/include/stdexec/__detail/__starts_on.hpp b/include/stdexec/__detail/__starts_on.hpp index 8ea88d6cf..7cab6d295 100644 --- a/include/stdexec/__detail/__starts_on.hpp +++ b/include/stdexec/__detail/__starts_on.hpp @@ -48,17 +48,13 @@ namespace STDEXEC static constexpr auto transform_sender(set_value_t, _Sender&& __sndr, __ignore) { auto& [__tag, __sched, __child] = __sndr; - // FUTURE: once __sequence updates the environment of the receiver connected to the - // nth+1 sender to contain the completion scheduler of the nth sender, we can remove - // the write_env here. - auto __sndr2 = write_env(STDEXEC::__forward_like<_Sender>(__child), __sched_env(__sched)); // NOT TO SPEC: the specification requires that this be implemented in terms of // let_value(schedule(sch), []{ return child; }), but that implementation // is inefficient on the GPU. We could customize starts_on for the GPU to use this // implementation, but this is a good change to make for all platforms since it // avoids unnecessarily making the child sender dependent on the completion of the // schedule operation. - return __sequence(continues_on(just(), __sched), std::move(__sndr2)); + return __sequence(continues_on(just(), __sched), STDEXEC::__forward_like<_Sender>(__child)); } template diff --git a/include/stdexec/__detail/__task_scheduler.hpp b/include/stdexec/__detail/__task_scheduler.hpp index e9f732c86..82e283088 100644 --- a/include/stdexec/__detail/__task_scheduler.hpp +++ b/include/stdexec/__detail/__task_scheduler.hpp @@ -777,12 +777,5 @@ namespace STDEXEC } } } - - [[nodiscard]] - inline auto __proxy_env::query(get_scheduler_t) const noexcept -> task_scheduler - { - auto __sched = __rcvr_.template try_query(get_scheduler); - return __sched ? *__sched : task_scheduler(inline_scheduler{}); - } } // namespace __detail } // namespace STDEXEC diff --git a/include/stdexec/__detail/__transform_completion_signatures.hpp b/include/stdexec/__detail/__transform_completion_signatures.hpp index cd510f55d..9d40df652 100644 --- a/include/stdexec/__detail/__transform_completion_signatures.hpp +++ b/include/stdexec/__detail/__transform_completion_signatures.hpp @@ -277,14 +277,14 @@ namespace STDEXEC template [[nodiscard]] consteval auto - __transform_expr(_Fn const &__fn, int) -> __transform_result_t<_Fn const &, _Args...> + __transform_expr(_Fn const &__fn, long) -> __transform_result_t<_Fn const &, _Args...> { return __fn.template operator()<_Args...>(); } template [[nodiscard]] - consteval auto __transform_expr(_Fn const &__fn, long) -> __call_result_t<_Fn const &> + consteval auto __transform_expr(_Fn const &__fn, int) -> __call_result_t<_Fn const &> { return __fn(); } diff --git a/include/stdexec/__detail/__tuple.hpp b/include/stdexec/__detail/__tuple.hpp index 6eb4db0c0..4bd47669f 100644 --- a/include/stdexec/__detail/__tuple.hpp +++ b/include/stdexec/__detail/__tuple.hpp @@ -483,6 +483,51 @@ namespace STDEXEC }; inline constexpr __mktuple_t __mktuple{}; + + // + // __fold_left(tuple, init, fn) + // + namespace __tup + { + struct __fold_left_fn + { + template + STDEXEC_ATTRIBUTE(host, device) + constexpr auto operator()(_State&& __state, __ignore) const + noexcept(__nothrow_move_constructible<_State>) -> _State + { + return static_cast<_State&&>(__state); + } + + template + requires __callable<__fold_left_fn, _State, _Fn, _Rest...> + && __callable<_Fn&, __call_result_t<__fold_left_fn, _State, _Fn, _Rest...>, _First> + STDEXEC_ATTRIBUTE(host, device) + constexpr auto + operator()(_State&& __state, _Fn& __fn, _First&& __first, _Rest&&... __rest) const noexcept( + __nothrow_callable<__fold_left_fn, _State, _Fn, _Rest...> + && __nothrow_callable<_Fn&, __call_result_t<__fold_left_fn, _State, _Fn, _Rest...>, _First>) + -> decltype(auto) + { + return __fn((*this)(static_cast<_State&&>(__state), __fn, static_cast<_Rest&&>(__rest)...), + static_cast<_First&&>(__first)); + } + }; + + template + requires __applicable<__fold_left_fn, _Tuple, _Init, _Fn&> + STDEXEC_ATTRIBUTE(host, device) + constexpr auto __fold_left(_Tuple&& __tupl, _Init&& __init, _Fn&& __fn) + noexcept(__nothrow_applicable<__fold_left_fn, _Tuple, _Init, _Fn&>) + -> __apply_result_t<__fold_left_fn, _Tuple, _Init, _Fn&> + { + return __apply(__fold_left_fn{}, + static_cast<_Tuple&&>(__tupl), + static_cast<_Init&&>(__init), + __fn); + } + } // namespace __tup + } // namespace STDEXEC STDEXEC_PRAGMA_POP() diff --git a/include/stdexec/__detail/__utility.hpp b/include/stdexec/__detail/__utility.hpp index 1be46dfb2..c3ff206d8 100644 --- a/include/stdexec/__detail/__utility.hpp +++ b/include/stdexec/__detail/__utility.hpp @@ -80,18 +80,8 @@ namespace STDEXEC template STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE __overload(_Fns...) -> __overload<_Fns...>; -#if STDEXEC_EDG() - // nvc++ doesn't cache the results of alias template specializations. - // To avoid repeated computation of the same function return type, - // cache the result ourselves in a class template specialization. - template - using __call_result_i = decltype(__declval<_Fun>()(__declval<_As>()...)); - template - using __call_result_t = __mmemoize_q<__call_result_i, _Fun, _As...>; -#else template using __call_result_t = decltype(__declval<_Fun>()(__declval<_As>()...)); -#endif template using __call_result_or_t = diff --git a/include/stdexec/__detail/__variant.hpp b/include/stdexec/__detail/__variant.hpp index c2cd610aa..81dc1ca83 100644 --- a/include/stdexec/__detail/__variant.hpp +++ b/include/stdexec/__detail/__variant.hpp @@ -75,16 +75,20 @@ namespace STDEXEC return __scope_guard{[&__index]() noexcept { __index = __variant_npos; }}; } + template + using __variant_alternative_t = + __copy_cvref_t<_Variant, typename std::remove_reference_t<_Variant>::template __at_t<_Ny>>; + template STDEXEC_ATTRIBUTE(host, device) - constexpr auto __get(_Variant &&__var) noexcept -> decltype(auto) + constexpr auto __get(_Variant &&__var) noexcept -> __variant_alternative_t<_Ny, _Variant> && { return __var.template __get<_Ny>(static_cast<_Variant &&>(__var)); } - template + template STDEXEC_ATTRIBUTE(host, device) - static constexpr auto __visit_alt(_Fn &&__fn, _Self &&__self, _Us &&...__us) -> decltype(auto) + static constexpr auto __visit_alt(_Fn &&__fn, _Self &&__self, _Us &&...__us) -> _Result { return static_cast<_Fn &&>(__fn)(static_cast<_Us &&>(__us)..., __var::__get<_Ny>(static_cast<_Self &&>(__self))); @@ -125,9 +129,6 @@ namespace STDEXEC { static constexpr std::size_t __max_size = STDEXEC::__umax({sizeof(_Ts)...}); - template - using __at_t = __m_at_c<_Ny, _Ts...>; - struct __move_visitor { template @@ -163,6 +164,9 @@ namespace STDEXEC alignas(_Ts...) std::byte __storage_[__max_size]; public: + template + using __at_t = __m_at_c<_Ny, _Ts...>; + // Construct into the valueless state: STDEXEC_ATTRIBUTE(host, device) constexpr explicit __variant(__no_init_t) noexcept {} @@ -340,10 +344,12 @@ namespace STDEXEC STDEXEC_ATTRIBUTE(host, device) static constexpr auto __visit(_Fn &&__fn, _Self &&__self, _Us &&...__us) noexcept((__nothrow_callable<_Fn, _Us..., __copy_cvref_t<_Self, _Ts>> && ...)) - -> decltype(auto) + -> __call_result_t<_Fn, _Us..., __copy_cvref_t<_Self, __at_t<0>>> { - STDEXEC_STATIC_CONSTEXPR_LOCAL auto __vtable = std::array{ - &__var::__visit_alt<_Is, _Fn, _Self, _Us...>...}; + using __result_t = __call_result_t<_Fn, _Us..., __copy_cvref_t<_Self, __at_t<0>>>; + + STDEXEC_CONSTEXPR_LOCAL auto __vtable = std::array{ + &__var::__visit_alt<_Is, __result_t, _Fn, _Self, _Us...>...}; STDEXEC_ASSERT(__self.__index_ != __variant_npos); return (*__vtable[__self.__index_])(static_cast<_Fn &&>(__fn), static_cast<_Self &&>(__self), @@ -357,7 +363,7 @@ namespace STDEXEC template STDEXEC_ATTRIBUTE(nodiscard, host, device, always_inline) - static constexpr auto __get(_Self &&__self) noexcept -> decltype(auto) + static constexpr auto __get(_Self &&__self) noexcept -> __copy_cvref_t<_Self, __at_t<_Ny>> && { using __value_t = __at_t<_Ny>; STDEXEC_ASSERT(_Ny == __self.__index_); diff --git a/test/stdexec/algos/adaptors/test_sequence.cpp b/test/stdexec/algos/adaptors/test_sequence.cpp index 758f25b5a..ebd76ee6a 100644 --- a/test/stdexec/algos/adaptors/test_sequence.cpp +++ b/test/stdexec/algos/adaptors/test_sequence.cpp @@ -70,8 +70,8 @@ namespace TEST_CASE("sequence produces a sender", "[sequence]") { - // The sequence algorithm requires at least one sender. - STATIC_REQUIRE(!ex::__callable); + // The sequence algorithm with no arguments is equivalent to just(). + STATIC_REQUIRE(ex::sender_of, ex::set_value_t()>); auto s0 = exec::sequence(ex::just(42)); static_assert(ex::__nothrow_connectable>>); @@ -88,7 +88,7 @@ TEST_CASE("sequence produces a sender", "[sequence]") STATIC_REQUIRE(ex::dependent_sender); STATIC_REQUIRE(!ex::sender_in); STATIC_REQUIRE(ex::sender_in); - check_val_types const &>>, env_t>(s1); + check_val_types, env_t>(s1); check_err_types, env_t>(s1); check_sends_stopped(s1); @@ -103,10 +103,7 @@ TEST_CASE("sequence produces a sender", "[sequence]") 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); + STATIC_REQUIRE(!ex::sender_in>); } TEST_CASE("sequence with one argument works", "[sequence]") @@ -135,7 +132,7 @@ TEST_CASE("sequence with two arguments works", "[sequence]") { SECTION("value completion") { - auto sndr = exec::sequence(ex::just(big{}), ex::just(big{}, 4, 6, 8)); + auto sndr = exec::sequence(ex::just(), ex::just(big{}, 4, 6, 8)); auto op = ex::connect(std::move(sndr), expect_value_receiver{big{}, 4, 6, 8}); ex::start(op); } @@ -147,7 +144,7 @@ TEST_CASE("sequence with two arguments works", "[sequence]") } SECTION("error completion 2") { - auto sndr = exec::sequence(ex::just(big{}, 4, 6, 8), ex::just_error(big{})); + auto sndr = exec::sequence(ex::just(), ex::just_error(big{})); auto op = ex::connect(std::move(sndr), expect_error_receiver{big{}}); ex::start(op); } @@ -161,7 +158,7 @@ TEST_CASE("sequence with two arguments works", "[sequence]") SECTION("stopped completion 2") { auto stop = ex::just(big{}) | ex::let_value([](auto &) { return ex::just_stopped(); }); - auto sndr = exec::sequence(ex::just(big{}, 4, 6, 8), std::move(stop)); + auto sndr = exec::sequence(ex::just(), std::move(stop)); auto op = ex::connect(std::move(sndr), expect_stopped_receiver{}); ex::start(op); } @@ -171,7 +168,7 @@ TEST_CASE("sequence with two arguments works", "[sequence]") 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 sndr = exec::sequence(ex::just(), throwing_connect{}, ex::just(big{}, 42)); check_err_types, ex::env<>>(std::move(sndr)); auto op = ex::connect(std::move(sndr), expect_error_receiver{err}); ex::start(op);