diff --git a/include/stdexec/__detail/__completion_behavior.hpp b/include/stdexec/__detail/__completion_behavior.hpp index c20f6ddd9..608fc11c2 100644 --- a/include/stdexec/__detail/__completion_behavior.hpp +++ b/include/stdexec/__detail/__completion_behavior.hpp @@ -34,9 +34,10 @@ namespace STDEXEC // __get_completion_behavior struct __completion_behavior { - //private: + // private: template <__completion_tag _Tag> friend struct __get_completion_behavior_t; + friend struct __completion_info; enum __flag : std::uint8_t { @@ -63,6 +64,50 @@ namespace STDEXEC __unknown = __not_affine_ | __async_ | __inline_ }; + // For use when computing the completion behavior of two senders when run in parallel. + // + // __asynchronous | __asynchronous_affine => __async_ (aka __asynchronous) + // __asynchronous | __inline_completion => __async_ | __inline_ | __not_affine (aka __unknown) + // __asynchronous_affine | __inline_completion => __async_ | __inline_ + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + friend constexpr auto operator|(__behavior __left, __behavior __right) noexcept // + -> __behavior + { + return __behavior(std::uint8_t(__left) | std::uint8_t(__right)); + } + + STDEXEC_ATTRIBUTE(always_inline, host, device) + friend constexpr auto operator|=(__behavior &__left, __behavior __right) noexcept // + -> __behavior & + { + return (__left = (__left | __right)); + } + + // For use when computing the completion behavior of two senders when run in sequence. + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + friend constexpr auto operator&(__behavior __left, __behavior __right) noexcept // + -> __behavior + { + // Two senders in sequence can only complete inline if both senders can complete + // inline. + auto const __possibly_inline = std::uint8_t(__left) & std::uint8_t(__right) & __inline_; + // Two senders in sequence can complete in a different context if either sender can + // complete in a different context. + auto const __not_affine = (std::uint8_t(__left) | std::uint8_t(__right)) & __not_affine_; + // Two senders in sequence can complete asynchronously if either sender can complete + // asynchronously. + auto const __possibly_async = (std::uint8_t(__left) | std::uint8_t(__right)) & __async_; + + return __behavior(__possibly_inline | __not_affine | __possibly_async); + } + + STDEXEC_ATTRIBUTE(always_inline, host, device) + friend constexpr auto operator&=(__behavior &__left, __behavior __right) noexcept // + -> __behavior & + { + return (__left = (__left & __right)); + } + template <__behavior _CB> using __constant_t = std::integral_constant<__behavior, _CB>; @@ -81,15 +126,37 @@ namespace STDEXEC static constexpr __asynchronous_affine_t __asynchronous_affine{}; static constexpr __inline_completion_t __inline_completion{}; - // __asynchronous | __asynchronous_affine => __async_ (aka __asynchronous) - // __asynchronous | __inline_completion => __async_ | __inline_ | __not_affine (aka __unknown) - // __asynchronous_affine | __inline_completion => __async_ | __inline_ + //private: + template <__behavior _CB> + static constexpr auto __reify() noexcept + { + if constexpr (_CB == __behavior::__asynchronous) + return __asynchronous; + else if constexpr (_CB == __behavior::__asynchronous_affine) + return __asynchronous_affine; + else if constexpr (_CB == __behavior::__inline_completion) + return __inline_completion; + else if constexpr (_CB == __behavior::__unknown) + return __unknown; + else + return __constant_t<_CB>{}; + } + + public: + // For use when computing the completion behavior of two senders when run in parallel. template <__behavior _Left, __behavior _Right> STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) friend constexpr auto operator|(__constant_t<_Left>, __constant_t<_Right>) noexcept { - return __constant_t(static_cast(_Left) - | static_cast(_Right))>(); + return __reify<_Left | _Right>(); + } + + // For use when computing the completion behavior of two senders when run in sequence. + template <__behavior _Left, __behavior _Right> + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + friend constexpr auto operator&(__constant_t<_Left>, __constant_t<_Right>) noexcept + { + return __reify<_Left & _Right>(); } template <__behavior _Left, __behavior _Right> @@ -123,11 +190,10 @@ namespace STDEXEC struct __common_t { template <__behavior... _CSs> - requires(sizeof...(_CSs) > 0) STDEXEC_ATTRIBUTE(nodiscard, host, device) constexpr auto operator()(__constant_t<_CSs>... __cbs) const noexcept { - return (__cbs | ...); + return (__cbs | ... | __constant_t<__behavior(0)>()); } }; diff --git a/include/stdexec/__detail/__completion_info.hpp b/include/stdexec/__detail/__completion_info.hpp index e846b7963..b5c475101 100644 --- a/include/stdexec/__detail/__completion_info.hpp +++ b/include/stdexec/__detail/__completion_info.hpp @@ -21,6 +21,7 @@ #include "__completion_behavior.hpp" #include "__completion_signatures.hpp" #include "__meta.hpp" +#include "__ranges.hpp" #include "__static_vector.hpp" #include "__typeinfo.hpp" @@ -45,14 +46,14 @@ namespace STDEXEC STDEXEC::__disposition __disposition = __invalid_disposition; __type_index __signature = __mtypeid; __type_index __domain = __mtypeid; - __behavior_t __behavior = __completion_behavior::__unknown; + __behavior_t __behavior = __behavior_t(0); __completion_info() = default; template <__completion_tag _Tag, class... _Args> constexpr __completion_info(_Tag (*)(_Args...), __type_index __domain = __mtypeid, - __behavior_t __behavior = __completion_behavior::__unknown) noexcept + __behavior_t __behavior = __behavior_t(0)) noexcept : __disposition(_Tag::__disposition) , __signature(__mtypeid<_Tag(_Args...)>) , __domain(__domain) @@ -126,6 +127,17 @@ namespace STDEXEC } }(); + template + constexpr auto __range_apply(_Fn &&__fn, _Args &&...args) noexcept + { + auto __impl = [&](__indices<_Is...>) + { + return static_cast<_Fn &&>(__fn).template + operator()<(*std::ranges::next(_Range.begin(), _Is))...>(std::forward<_Args>(args)...); + }; + return __impl(__make_indices()); + } + template consteval auto __completion_sigs_from(_GetComplInfo) noexcept { @@ -142,13 +154,31 @@ namespace STDEXEC template [[nodiscard]] - consteval auto __to_array(completion_signatures<_Sigs...>) noexcept + consteval auto __reflect(completion_signatures<_Sigs...>) noexcept { using __array_t = __static_vector<__completion_info, sizeof...(_Sigs)>; auto __compls = __array_t{__completion_info(__signature<_Sigs>)...}; std::ranges::sort(__compls); return __compls; } + + template + extern int __splice_v; + + template + using __splice_t = decltype(__splice_v<_Info>); + + template <__same_as<__completion_info> auto _Info> + extern __fn_ptr_t<__tuple<__msplice<_Info.__signature>, + __msplice<_Info.__domain>, + __mconstant<_Info.__behavior>>> + __splice_v<_Info>; + + template + requires __same_as, __completion_info> + constexpr auto __splice_v<_Info> = __with_indices<_Info.size()>()( + []() { return __fn_ptr_t<__tuple<__splice_t<_Info[_Is]>...>>(); }); + } // namespace __cmplsigs } // namespace STDEXEC diff --git a/include/stdexec/__detail/__completion_signatures.hpp b/include/stdexec/__detail/__completion_signatures.hpp index 055b58344..fc4cdd589 100644 --- a/include/stdexec/__detail/__completion_signatures.hpp +++ b/include/stdexec/__detail/__completion_signatures.hpp @@ -390,7 +390,7 @@ namespace STDEXEC //! tag. template <__completion_tag _Tag> [[nodiscard]] - static consteval auto __select(_Tag) noexcept + static consteval auto __select(_Tag = _Tag()) noexcept { if constexpr (_Tag{} == set_value) { @@ -560,6 +560,12 @@ namespace STDEXEC return _ID; \ } else +# define STDEXEC_IF_OK_OR(_ID, ...) \ + if constexpr (STDEXEC::__merror) \ + { \ + return __VA_ARGS__; \ + } else + template [[nodiscard]] consteval auto __throw_dependent_sender_error_r() noexcept -> __dependent_sender_error_t<_Sndr> @@ -577,6 +583,7 @@ namespace STDEXEC #else // ^^^ no constexpr exceptions ^^^ / vvv constexpr exceptions vvv # define STDEXEC_IF_OK(_ID) +# define STDEXEC_IF_OK_OR(_ID, ...) template [[noreturn, nodiscard]] diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index ed768a5f9..66b568eeb 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -658,38 +658,6 @@ namespace STDEXEC # define STDEXEC_IF_NOT_CONSTEVAL STDEXEC_IF_CONSTEVAL {} else #endif -#if defined(STDEXEC_ASSERT) -// nothing to do, user has provided their own assertion macro -#elif defined(STDEXEC_ASSERT_FN) -// legacy way to customize assertions, still supported for backward compatibility -# define STDEXEC_ASSERT(_XP) STDEXEC_ASSERT_FN(_XP) -#else -# define STDEXEC_ASSERT(_XP) \ - do \ - { \ - STDEXEC_IF_CONSTEVAL \ - { \ - if (!(_XP)) \ - STDEXEC::__throw_assertion_failure(); \ - } \ - else \ - { \ - assert(_XP); \ - } \ - } while (false) -#endif - -namespace STDEXEC -{ - struct __assertion_failure - {}; - - inline void __throw_assertion_failure() - { - throw __assertion_failure{}; - } -} // namespace STDEXEC - #define STDEXEC_AUTO_RETURN(...) \ noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { \ return __VA_ARGS__; \ @@ -807,6 +775,39 @@ namespace STDEXEC } } // namespace STDEXEC +#if defined(STDEXEC_ASSERT) +// nothing to do, user has provided their own assertion macro +#elif defined(STDEXEC_ASSERT_FN) +// legacy way to customize assertions, still supported for backward compatibility +# define STDEXEC_ASSERT(_XP) STDEXEC_ASSERT_FN(_XP) +#else +# define STDEXEC_ASSERT(_XP) \ + do \ + { \ + STDEXEC_IF_CONSTEVAL \ + { \ + if (!(_XP)) \ + STDEXEC::__throw_assertion_failure(); \ + } \ + else \ + { \ + assert(_XP); \ + } \ + } while (false) +#endif + +namespace STDEXEC +{ + struct __assertion_failure + {}; + + STDEXEC_ATTRIBUTE(host, device) + inline void __throw_assertion_failure() + { + STDEXEC_THROW(__assertion_failure{}); + } +} // namespace STDEXEC + /////////////////////////////////////////////////////////////////////////////// /// To hook a customization point like STDEXEC::connect, define a member /// function like this: diff --git a/include/stdexec/__detail/__domain.hpp b/include/stdexec/__detail/__domain.hpp index d1f8a056d..3610f9db9 100644 --- a/include/stdexec/__detail/__domain.hpp +++ b/include/stdexec/__detail/__domain.hpp @@ -319,8 +319,8 @@ namespace STDEXEC { using __domain_t = __call_result_t<__read_query_t, _Attrs const &, _Env const &...>; return __check_domain<_Attrs, _Env...>(__domain_t{}); - // Otherwise, if _Tag is void, fall back to querying for the set_value_t completion domain: } + // Otherwise, if _Tag is void, fall back to querying for the set_value_t completion domain: else if constexpr (__same_as<_Tag, void>) { if constexpr (__callable, @@ -344,7 +344,7 @@ namespace STDEXEC { using __sch_t = __call_result_t, _Attrs const &, _Env const &...>; - using __read_query_t = typename get_completion_domain_t::__read_query_t; + using __read_query_t = get_completion_domain_t::__read_query_t; if constexpr (__callable<__read_query_t, __sch_t, _Env const &...>) { diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 6e56e6e94..a4d9a4d42 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -35,11 +35,11 @@ STDEXEC_PRAGMA_IGNORE_GNU("-Wmissing-braces") namespace STDEXEC { - ////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////// // [exec.envs] namespace __env { - ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////// // cprop template struct cprop @@ -155,6 +155,33 @@ namespace STDEXEC return __join(__root_env{}, static_cast &&>(__env)); } }; + + // A query result is valid if it is not void and does not indicate a failure. + template + concept __valid_query_result = __not_same_as<_Ty, void> && __ok<_Ty>; + + template + concept __valid_queryable_with = requires(_Env const &__env, _Args &&...__args) { + { __env.__query(_Query(), static_cast<_Args &&>(__args)...) } -> __valid_query_result; + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // A CRTP base for an environment whose derived class provides (possibly + // void-returning) __query member functions, and that implements query() by forwarding + // to those member functions that do not return void. + template <__class _Derived> + struct __facade + { + // clang-format off + template + requires __valid_queryable_with<_Env, _Query, _Args...> + STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) + constexpr auto query(_Query, _Args &&...__args) const STDEXEC_AUTO_RETURN + ( + static_cast<_Env const &>(*this).__query(_Query(), static_cast<_Args &&>(__args)...) + ) + // clang-format on + }; } // namespace __env using __env::__join_env_t; @@ -170,7 +197,7 @@ namespace STDEXEC template concept __is_root_env = requires(_Env &&__env) { - { __root_t{}(__env) } -> __std::same_as; + { __root_t()(__env) } -> __same_as; }; ////////////////////////////////////////////////////////////////////// diff --git a/include/stdexec/__detail/__execution_fwd.hpp b/include/stdexec/__detail/__execution_fwd.hpp index d7852d2bf..65e254189 100644 --- a/include/stdexec/__detail/__execution_fwd.hpp +++ b/include/stdexec/__detail/__execution_fwd.hpp @@ -174,6 +174,8 @@ namespace STDEXEC using scheduler_t = scheduler_tag; using receiver_t = receiver_tag; + struct __completion_info; + template STDEXEC_ATTRIBUTE(nodiscard, always_inline, host, device) constexpr auto __get_completion_behavior() noexcept; diff --git a/include/stdexec/__detail/__get_completion_signatures.hpp b/include/stdexec/__detail/__get_completion_signatures.hpp index 1d0fa839e..bbeb427a4 100644 --- a/include/stdexec/__detail/__get_completion_signatures.hpp +++ b/include/stdexec/__detail/__get_completion_signatures.hpp @@ -431,7 +431,7 @@ namespace STDEXEC auto __cmplsigs = STDEXEC::get_completion_signatures<_Sender, _Env...>(); STDEXEC_IF_OK(__cmplsigs) { - auto __cmplinfo = STDEXEC::__cmplsigs::__to_array(__cmplsigs); + auto __cmplinfo = STDEXEC::__cmplsigs::__reflect(__cmplsigs); std::ranges::for_each(__cmplinfo, &__completion_info::__populate<_Sender, _Env...>); return __cmplinfo; } diff --git a/include/stdexec/__detail/__let.hpp b/include/stdexec/__detail/__let.hpp index 201c44dea..492349d3b 100644 --- a/include/stdexec/__detail/__let.hpp +++ b/include/stdexec/__detail/__let.hpp @@ -24,6 +24,7 @@ #include "__domain.hpp" #include "__env.hpp" #include "__meta.hpp" +#include "__ranges.hpp" #include "__schedulers.hpp" #include "__sender_adaptor_closure.hpp" #include "__senders.hpp" @@ -34,6 +35,8 @@ #include "../functional.hpp" #include +#include +#include #include "__prologue.hpp" @@ -43,14 +46,41 @@ namespace STDEXEC // [exec.let] namespace __let { - template - struct __let_t; + struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_; - template - using __env2_t = __secondary_env_t<_Sender, _Env, _SetTag>; + template + struct _NESTED_ERROR_; + + // BUGBUG: when get_completion_signatures is using constexpr exceptions, this + // __try_completion_signatures_of_t machinery hides the nested error from trying to + // compute the result senders. + template + using __try_completion_signatures_of_t = + __minvoke_or_q<__completion_signatures_of_t, + __unrecognized_sender_error_t<_Sender, _Env...>, + _Sender, + _Env...>; + + template + using __bad_result_sender = __mexception< + _WHAT_(_FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_), + _WHERE_(_IN_ALGORITHM_, _LetTag), + _WITH_PRETTY_SENDER_<_Sender>, + __fn_t<_WITH_ENVIRONMENT_, _JoinEnv2>..., + __mapply_q<_NESTED_ERROR_, __try_completion_signatures_of_t<_Sender, _JoinEnv2...>>>; + + template + using __not_decay_copyable_error_t = + __mexception<_WHAT_(_SENDER_RESULTS_ARE_NOT_DECAY_COPYABLE_), + _WHERE_(_IN_ALGORITHM_, _LetTag), + _WITH_ARGUMENTS_(_Args...)>; + + template + concept __potentially_valid_sender_in = sender_in<_Sender, _JoinEnv2...> + || (sender<_Sender> && (sizeof...(_JoinEnv2) == 0)); template - using __result_env_t = __join_env_t<__env2_t<_SetTag, _Sender, _Env>, _Env>; + using __env2_t = __secondary_env_t<_Sender, _Env, _SetTag>; template struct __rcvr_env @@ -85,41 +115,8 @@ namespace STDEXEC } }; - struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_; - - template - struct _NESTED_ERROR_; - - // BUGBUG: when get_completion_signatures is using constexpr exceptions, this - // __try_completion_signatures_of_t machinery hides the nested error from trying to - // compute the result senders. - template - using __try_completion_signatures_of_t = - __minvoke_or_q<__completion_signatures_of_t, - __unrecognized_sender_error_t<_Sender, _Env...>, - _Sender, - _Env...>; - - template - using __bad_result_sender = __mexception< - _WHAT_(_FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_), - _WHERE_(_IN_ALGORITHM_, _LetTag), - _WITH_PRETTY_SENDER_<_Sender>, - __fn_t<_WITH_ENVIRONMENT_, _JoinEnv2>..., - __mapply_q<_NESTED_ERROR_, __try_completion_signatures_of_t<_Sender, _JoinEnv2...>>>; - - template - using __not_decay_copyable_error_t = - __mexception<_WHAT_(_SENDER_RESULTS_ARE_NOT_DECAY_COPYABLE_), - _WHERE_(_IN_ALGORITHM_, _LetTag), - _WITH_ARGUMENTS_(_Args...)>; - - template - concept __potentially_valid_sender_in = sender_in<_Sender, _JoinEnv2...> - || (sender<_Sender> && (sizeof...(_JoinEnv2) == 0)); - //! Metafunction creating the operation state needed to connect the result of calling - //! the sender factory function, `_Fun`, and passing its result to a receiver. + //! the sender factory function, \c _Fun, and passing its result to a receiver. template struct __submit_datum_for { @@ -130,30 +127,8 @@ namespace STDEXEC submit_result<__invoke_result_t<_Fun, __decay_t<_Args>&...>, __rcvr_env<_Receiver, _Env2>>; }; - // A metafunction to check whether the predecessor's completion results are nothrow - // decay-copyable and whether connecting the secondary sender is nothrow. - template - struct __has_nothrow_completions_fn - { - template - using __sndr2_t = __invoke_result_t<_Fn, __decay_t<_Ts>&...>; - using __rcvr2_t = __receiver_archetype<_Env2>; - - template - using __f = __mbool<__nothrow_decay_copyable<_Ts...> // - && __nothrow_invocable<_Fn, __decay_t<_Ts>&...> // - && __nothrow_connectable<__sndr2_t<_Ts...>, __rcvr2_t>>; - }; - - template - using __has_nothrow_completions_t = __gather_completions_t< - completion_signatures_of_t<_Child, _Env>, - _SetTag, - __has_nothrow_completions_fn<_Fn, __result_env_t<_SetTag, _Child, _Env>>, - __qq<__mand_t>>; - - //! The core of the operation state for `let_*`. - //! This gets bundled up into a larger operation state (`__detail::__op_state<...>`). + //! The core of the operation state for \c let_*. + //! This gets bundled up into a larger operation state (\c __detail::__op_state<...>). template struct __opstate_base { @@ -179,7 +154,7 @@ namespace STDEXEC template constexpr void __impl(_Tag, _Args&&... __args) noexcept { - if constexpr (__same_as<_SetTag, _Tag>) + if constexpr (_SetTag() == _Tag()) { using __sender_t = __invoke_result_t<_Fun, __decay_t<_Args>&...>; using __submit_t = submit_result<__sender_t, __rcvr_env<_Receiver, _Env2>>; @@ -248,7 +223,7 @@ namespace STDEXEC __opstate_base<_SetTag, _Fun, _Receiver, _Env2, _Tuples...>* __state_; }; - constexpr auto __mk_result_sndr = + inline constexpr auto __mk_result_sndr = [](_Fun& __fn, _Args&... __args) noexcept( __nothrow_invocable<_Fun, _Args&...>) -> decltype(auto) { @@ -274,8 +249,8 @@ namespace STDEXEC __op.submit(static_cast<__sender_t&&>(__sndr), static_cast<__second_rcvr_t&&>(__rcvr2)); }; - //! The core of the operation state for `let_*`. - //! This gets bundled up into a larger operation state (`__detail::__op_state<...>`). + //! The core of the operation state for \c let_*. + //! This gets bundled up into a larger operation state (\c __detail::__op_state<...>). template struct __opstate final : __opstate_base<_SetTag, @@ -330,330 +305,303 @@ namespace STDEXEC __op_state_variant_t __storage_{__no_init}; }; - // The set_value completions of: - // - // * a let_value sender are: - // * the value completions of the secondary senders - // - // * a let_error sender are: - // * the value completions of the predecessor sender - // * the value completions of the secondary senders - // - // * a let_stopped sender are: - // * the value completions of the predecessor sender - // * the value completions of the secondary sender - // - // The set_error completions of: - // - // * a let_value sender are: - // * the error completions of the predecessor sender - // * the error completions of the secondary senders - // * the value completions of the predecessor sender if decay copying the arguments can throw - // - // * a let_error sender are: - // * the error completions of the secondary senders - // * the error completions of the predecessor sender if decay copying the errors can throw - // - // * a let_stopped sender are: - // * the error completions of the predecessor sender - // * the error completions of the secondary senders - // - // The set_stopped completions of: - // - // * a let_value sender are: - // * the stopped completions of the predecessor sender - // * the stopped completions of the secondary senders - // - // * a let_error sender are: - // * the stopped completions of the predecessor sender - // * the stopped completions of the secondary senders - // - // * a let_stopped sender are: - // * the stopped completions of the secondary sender - // - template - struct __result_completion_behavior_fn - { - template - [[nodiscard]] - static constexpr auto __impl() noexcept - { - using __sndr_t = - __minvoke_or_q<__invoke_result_t, __not_a_sender<>, _Fun, __decay_t<_Ts>&...>; - return STDEXEC::__get_completion_behavior<_SetTag, __sndr_t, _JoinEnv2...>(); - } + using __eptr_sig_t = set_error_t(std::exception_ptr); + inline constexpr auto __eptr_sig_id = __mtypeid<__eptr_sig_t>; - template - using __f = decltype(__impl<_Ts...>()); - }; + template + using __fn_t = __decay_t<__data_of<_CvSender>>; - template - struct __domain_transform_fn + inline constexpr auto __mk_join_bhvr(__completion_behavior::__behavior __b0) noexcept { - template - using __f = __completion_domain_of_t<_SetTag, - __invoke_result_t<_Fun, __decay_t<_As>&...>, - __result_env_t<_SetTag, _Sender, _Env>...>; - }; - - //! @tparam _LetTag The tag type for the let_ operation. - //! @tparam _SetTag The completion signal of the let_ sender itself that is being - //! queried. For example, you may be querying a let_value sender for its set_error - //! completion domain. - template - [[nodiscard]] - consteval auto __get_completion_domain() noexcept - { - if constexpr (sender_in<_Sndr, _Env...>) - { - using __domain_transform_fn = __let::__domain_transform_fn<_SetTag, _Fun, _Sndr, _Env...>; - return __minvoke_or_q<__gather_completions_t, - indeterminate_domain<>, - __t<_LetTag>, - __completion_signatures_of_t<_Sndr, _Env...>, - __domain_transform_fn, - __qq<__common_domain_t>>(); - } - else + using __behavior_t = __completion_behavior::__behavior; + return [=](__behavior_t& __b1) noexcept -> __behavior_t& { - return indeterminate_domain<>{}; - } + return (__b1 &= __b0); + }; } - template - using __let_completion_domain_t = __unless_one_of_t< - __result_of<__let::__get_completion_domain<_LetTag, _SetTag, _Sndr, _Fun, _Env...>>, - indeterminate_domain<>>; - - template - struct __attrs + inline constexpr auto __mk_disp_eq(__disposition __disposition_) noexcept { - using __set_tag_t = STDEXEC::__t<_LetTag>; - - template - constexpr auto query(get_completion_scheduler_t<_Tag>) const = delete; - - template - [[nodiscard]] - constexpr auto query(get_completion_domain_t<__set_tag_t>, _Env const &...) const noexcept - -> __ensure_valid_domain_t< - __let_completion_domain_t<_LetTag, __set_tag_t, _Sndr, _Fun, _Env...>> + return [=](__completion_info const & __info) noexcept -> bool { - return {}; - } - - template <__one_of _Tag, class... _Env> - requires(__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fun, _Env>::value && ...) - [[nodiscard]] - constexpr auto query(get_completion_domain_t<_Tag>, _Env const &...) const noexcept - -> __ensure_valid_domain_t< - __common_domain_t<__completion_domain_of_t<_Tag, _Sndr, __fwd_env_t<_Env>...>, - __let_completion_domain_t<_LetTag, _Tag, _Sndr, _Fun, _Env...>>> - { - return {}; - } - - template - requires(!__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fun, _Env>::value) - [[nodiscard]] - constexpr auto query(get_completion_domain_t, _Env const &) const noexcept - -> __ensure_valid_domain_t< - __common_domain_t<__completion_domain_of_t<__set_tag_t, _Sndr, __fwd_env_t<_Env>>, - __completion_domain_of_t>, - __let_completion_domain_t<_LetTag, set_error_t, _Sndr, _Fun, _Env>>> - { - return {}; - } - - template - [[nodiscard]] - constexpr auto query(__get_completion_behavior_t<__set_tag_t>, _Env const &...) const noexcept - { - if constexpr (sender_in<_Sndr, __fwd_env_t<_Env>...>) - { - // The completion behavior of let_value(sndr, fn) is the union of the completion - // behavior of sndr and all the senders that fn can potentially produce. (MSVC - // needs the constexpr computation broken up, hence the local variables.) - using __transform_fn = - __result_completion_behavior_fn<__set_tag_t, - _Fun, - __result_env_t<__set_tag_t, _Sndr, _Env>...>; - using __completions_t = __completion_signatures_of_t<_Sndr, __fwd_env_t<_Env>...>; - - constexpr auto __pred_behavior = - STDEXEC::__get_completion_behavior<__set_tag_t, _Sndr, __fwd_env_t<_Env>...>(); - constexpr auto __result_behaviors = __gather_completions_t< - __set_tag_t, - __completions_t, - __transform_fn, - __mbind_front_q<__call_result_t, __completion_behavior::__common_t>>(); - - return __pred_behavior | __result_behaviors; - } - else - { - return __completion_behavior::__unknown; - } - } - }; + return __info.__disposition == __disposition_; + }; + } - //! Implementation of the `let_*_t` types, where `_SetTag` is, e.g., `set_value_t` for `let_value`. - template - struct __let_t + inline constexpr auto __mk_common_domain() noexcept { - template - constexpr auto operator()(_Sender&& __sndr, _Fun __fn) const -> __well_formed_sender auto - { - return __make_sexpr<_LetTag>(static_cast<_Fun&&>(__fn), static_cast<_Sender&&>(__sndr)); - } - - template - STDEXEC_ATTRIBUTE(always_inline) - constexpr auto operator()(_Fun __fn) const + return []() noexcept { - return __closure(*this, static_cast<_Fun&&>(__fn)); - } - - private: - friend _LetTag; - __let_t() = default; - }; + return __common_domain_t<__msplice<_DomainIds>...>(); + }; + } template - struct __impls : __sexpr_defaults + struct __impls_for : __sexpr_defaults { private: - using __set_t = __t<_LetTag>; - using __eptr_sig_t = set_error_t(std::exception_ptr); - - template - using __fn_t = __decay_t<__data_of<_CvSender>>; + using __set_tag_t = __t<_LetTag>; template - using __env2_t = __let::__result_env_t<__set_t, _CvSender, _Env>; + using __env2_t = __join_env_t<__let::__env2_t<__set_tag_t, _CvSender, _Env>, _Env>; template using __rcvr2_t = __receiver_archetype<__env2_t<_CvSender, _Env>>; template using __opstate_t = __gather_completions_of_t< - __set_t, + __set_tag_t, __child_of<_CvSender>, __fwd_env_t>, __q<__decayed_tuple>, - __mbind_front_q<__opstate, __set_t, __child_of<_CvSender>, __fn_t<_CvSender>, _Receiver>>; + __mbind_front_q<__opstate, __set_tag_t, __child_of<_CvSender>, __fn_t<_CvSender>, _Receiver>>; template - static constexpr auto __transform_cmplsig = // - [](__set_t (*)(_As...), __completion_info __info) // - -> decltype(auto) + static constexpr auto __mk_cmplsig_transform() noexcept { - if constexpr (!__decay_copyable<_As...>) - { - using __what_t = __not_decay_copyable_error_t<_LetTag, _As...>; - return STDEXEC::__throw_compile_time_error(__what_t()); - } - else if constexpr (!__invocable<_Fun, __decay_t<_As>&...>) + //! \param __sig The signature of this completion of the child sender. + //! \param __child_info The descriptor of this completion of the child sender. + //! + //! \returns a \c __static_vector of \c __completion_info objects representing the + //! completions of the sender returned by \c _Fun. + //! + //! \throws a compile-time error if the arguments are not decay-copyable, if + //! \c _Fun is not invocable with the arguments, or if the result of invoking + //! \c _Fun is not a valid sender in the current environment. + return []([[maybe_unused]] + __set_tag_t (*__sig)(_As...), + __completion_info const __child_info) { - using __what_t = __callable_error_t<_LetTag, _Fun, __decay_t<_As>&...>; - return STDEXEC::__throw_compile_time_error(__what_t()); - } - else if constexpr (!__potentially_valid_sender_in< - __invoke_result_t<_Fun, __decay_t<_As>&...>, - __env2_t<_Child, _Env>...>) - { - using __sndr_t = __invoke_result_t<_Fun, __decay_t<_As>&...>; - using __what_t = __bad_result_sender<__sndr_t, _LetTag, __env2_t<_Child, _Env>...>; - return STDEXEC::__throw_compile_time_error(__what_t()); - } - else - { - using __sndr2_t = __invoke_result_t<_Fun, __decay_t<_As>&...>; - auto __cmpls = STDEXEC::__get_completion_info<__sndr2_t, __env2_t<_Child, _Env>...>(); - STDEXEC_IF_OK(__cmpls) + if constexpr (!__decay_copyable<_As...>) { - if constexpr (!__nothrow_decay_copyable<_As...> - || !__nothrow_invocable<_Fun, __decay_t<_As>&...> - || (!__nothrow_connectable<__sndr2_t, __rcvr2_t<_Child, _Env>> || ...)) - { - __completion_info const __eptr_info(__signature<__eptr_sig_t>, - __info.__domain, - __info.__behavior); - return __cmpls + STDEXEC::__make_static_vector(__eptr_info); - } - else + using __what_t = __not_decay_copyable_error_t<_LetTag, _As...>; + return STDEXEC::__throw_compile_time_error(__what_t()); + } + else if constexpr (!__invocable<_Fun, __decay_t<_As>&...>) + { + using __what_t = __callable_error_t<_LetTag, _Fun, __decay_t<_As>&...>; + return STDEXEC::__throw_compile_time_error(__what_t()); + } + else if constexpr (!__potentially_valid_sender_in< + __invoke_result_t<_Fun, __decay_t<_As>&...>, + __env2_t<_Child, _Env>...>) + { + using __sndr2_t = __invoke_result_t<_Fun, __decay_t<_As>&...>; + using __what_t = __bad_result_sender<__sndr2_t, _LetTag, __env2_t<_Child, _Env>...>; + return STDEXEC::__throw_compile_time_error(__what_t()); + } + else + { + using __sndr2_t = __invoke_result_t<_Fun, __decay_t<_As>&...>; + auto __cmpls = STDEXEC::__get_completion_info<__sndr2_t, __env2_t<_Child, _Env>...>(); + + STDEXEC_IF_OK(__cmpls) { - return __cmpls; + constexpr bool __is_nothrow = // + __nothrow_decay_copyable<_As...> + && __nothrow_invocable<_Fun, __decay_t<_As>&...> + // false if the pack _Env is empty: + && (__nothrow_connectable<__sndr2_t, __rcvr2_t<_Child, _Env>> || ...); + + // The completion behavior of the let_ sender should include both the + // completion behavior of the child sender and the completion behavior of + // the result sender. + constexpr auto __get_bhvr = &__completion_info::__behavior; + std::ranges::for_each(__cmpls, __mk_join_bhvr(__child_info.__behavior), __get_bhvr); + + if constexpr (__is_nothrow) + return __cmpls; + else + return __cmpls + + __static_vector{__completion_info(__signature<__eptr_sig_t>, + __child_info.__domain, + __child_info.__behavior)}; } } - } - }; + }; + } - template <__completion_info _Info> - static constexpr auto __maybe_transform_cmplsig = [](auto __transform) -> decltype(auto) + template <__completion_info _Info, class _Transform> + static constexpr auto __maybe_transform_cmplsig(_Transform __transform) { - if constexpr (_Info.__disposition != __set_t::__disposition) - return STDEXEC::__make_static_vector(_Info); + //! If \c _Info is not a \c __set_tag_t completion, then pass it through + //! unmodified. Otherwise, transform it using \c __transform. + if constexpr (_Info.__disposition != __set_tag_t::__disposition) + return __static_vector{_Info}; else return __transform(__signature<__msplice<_Info.__signature>>, _Info); }; - //! @tparam _Info A `__static_vector` of `__completion_info` objects representing - //! the completions of the predecessor sender. - template - static constexpr auto __get_cmpl_info_i = [](__indices<_Is...>) + static constexpr auto __mk_get_cmpl_info_impl() noexcept { - return [] + //! \tparam _Info A pack of \c __completion_info objects representing the + //! completions of the predecessor sender. + return [](auto __transform) { - __static_vector<__completion_info, 0> __result; - // NB: this fold uses an overloaded addition operator that propagates - // __mexception objects when constexpr exceptions are not available. - return (__maybe_transform_cmplsig<_Info[_Is]>(_Transform) + ... + __result); + constexpr __static_vector<__completion_info, 0> __init{}; + //! \note this fold uses an overloaded addition operator that propagates + //! \c __mexception objects when constexpr exceptions are not available. + return (__maybe_transform_cmplsig<_Info>(__transform) + ... + __init); }; - }; + } template - struct __get_cmpl_info + static constexpr auto __mk_get_cmpl_info() noexcept { - constexpr auto operator()() const + return [] + { + constexpr auto __get_sig = &__completion_info::__signature; + constexpr auto __mk_completions = [] + { + constexpr auto __cmpls = STDEXEC::__get_completion_info<_Child, __fwd_env_t<_Env>...>(); + STDEXEC_IF_OK(__cmpls) + { + constexpr auto __transform = __mk_cmplsig_transform<_Fun, _Child, _Env...>(); + return __cmplsigs::__range_apply<__cmpls>(__mk_get_cmpl_info_impl(), __transform); + } + }; + + constexpr auto __cmpls2 = __cmplsigs::__completion_info_from(__mk_completions); + STDEXEC_IF_OK(__cmpls2) + { + if constexpr (!__ranges::contains(__cmpls2, __eptr_sig_id, __get_sig) + && sizeof...(_Env) == 0) + return STDEXEC::__throw_dependent_sender_error<_Child>(); + else + return __cmpls2; + } + }; + } + + template + struct __attrs : __env::__facade<__attrs<_Child, _Fun>> + { + using __behavior_t = __completion_behavior::__behavior; + + template + requires sender_in<_Child, _Env...> + [[nodiscard]] + constexpr auto __query(get_completion_domain_t<_SetTag>, _Env const &...) const noexcept { - constexpr auto __transform = __transform_cmplsig<_Fun, _Child, _Env...>; - constexpr auto __get_sig = &__completion_info::__signature; - constexpr auto __eptr_sig_id = __mtypeid<__eptr_sig_t>; - constexpr auto __cmpls = STDEXEC::__get_completion_info<_Child, _Env...>(); + // BUGBUG: this is to-spec but it is arguably wrong. let_error(sndr, fn) should + // be using the set_error_t completion domain to find customizations, not the + // set_value_t completion domain. + // using __tag_t = __if_c<__same_as<_SetTag, void>, __set_tag_t, _SetTag>; + using __tag_t = __if_c<__same_as<_SetTag, void>, set_value_t, _SetTag>; + constexpr auto __get_cmpl_info = __impls_for::__mk_get_cmpl_info<_Fun, _Child, _Env...>(); + constexpr auto __cmpl_info = __cmplsigs::__completion_info_from(__get_cmpl_info); + STDEXEC_IF_OK(__cmpl_info) + { + using __domains_t = __static_vector<__type_index, __cmpl_info.size()>; + constexpr __domains_t __doms = __cmpl_info + | std::views::filter(__mk_disp_eq(__tag_t::__disposition)) + | std::views::transform(&__completion_info::__domain); + return __cmplsigs::__range_apply<__doms>(__mk_common_domain()); + } + } - STDEXEC_IF_OK(__cmpls) + template + requires sender_in<_Child, _Env...> + [[nodiscard]] + constexpr auto __query(__get_completion_behavior_t<_Tag>, _Env const &...) const noexcept + { + constexpr auto __get_cmpl_info = __impls_for::__mk_get_cmpl_info<_Fun, _Child, _Env...>(); + constexpr auto __cmpl_info = __cmplsigs::__completion_info_from(__get_cmpl_info); + STDEXEC_IF_OK(__cmpl_info) { - constexpr auto __idx = __make_indices<__cmpls.size()>(); - constexpr auto __get_cmpls2 = __get_cmpl_info_i<__cmpls, __transform>(__idx); - constexpr auto __cmpls2 = __cmplsigs::__completion_info_from(__get_cmpls2); + using __behaviors_t = __static_vector<__behavior_t, __cmpl_info.size()>; + constexpr __behaviors_t __bs = __cmpl_info + | std::views::filter(__mk_disp_eq(_Tag::__disposition)) + | std::views::transform(&__completion_info::__behavior); + constexpr auto __behavior = + std::accumulate(__bs.begin(), __bs.end(), __behavior_t(), std::bit_or()); + return __completion_behavior::__reify<__behavior>(); + } + } - STDEXEC_IF_OK(__cmpls2) + template + static consteval bool __use_predecessor_completion_scheduler() + { + auto __impl = [](completion_signatures<_Sigs...>) + { + // If we are querying for the, e.g., set_value completion scheduler of the + // let_value sender, then we check the set_value completions of all the + // secondary senders and return true if they all complete inline. Otherwise, + // we return false if any of the secondary senders have a _SetTag completion. + constexpr auto __tform = __mk_cmplsig_transform<_Fun, _Child, _Env...>(); + constexpr auto __info = // + (__tform(__signature<_Sigs>, + __completion_info{__signature<_Sigs>, + __mtypeid, + __behavior_t::__inline_completion}) + + ... + __static_vector<__completion_info, 0>()); + STDEXEC_IF_OK(__info) { - if constexpr (std::ranges::find(__cmpls2, __eptr_sig_id, __get_sig) == __cmpls2.end() - && sizeof...(_Env) == 0) - return STDEXEC::__throw_dependent_sender_error<_Child>(); + // We only care about the completions of the secondary senders that match + // the _SetTag we're querying for. + constexpr __static_vector<__completion_info, __info.size()> __compls = // + __info // + | std::views::filter(__mk_disp_eq(_SetTag::__disposition)); + + if constexpr (__same_as<_SetTag, __set_tag_t>) + { + // If we get here, we are querying for the, e.g., set_value completion + // scheduler of the let_value sender. We check the set_value completions + // of all the secondary senders and return true if they all complete + // inline. + constexpr __static_vector<__behavior_t, __info.size()> __bs = + __compls | std::views::transform(&__completion_info::__behavior); + constexpr auto __behavior = + std::accumulate(__bs.begin(), __bs.end(), __behavior_t(), std::bit_or()); + return __behavior == __behavior_t::__inline_completion; + } else - return __cmpls2; + { + // If we get here, we are querying for, e.g., the set_error completion + // scheduler of the let_value sender. We return true if there are no + // set_error completions of the secondary senders. + return __compls.empty(); + } } + }; + + auto __cmplsigs = get_completion_signatures<_Child, __fwd_env_t<_Env>...>(); + auto __result = __impl(__cmplsigs.__select(__set_tag_t())); + STDEXEC_IF_OK_OR(__result, false) + { + return true; } } + + // If all the secondary senders complete inline and the primary sender knows its + // completion scheduler, then we know the completion scheduler for the let sender + // without calling the function. + template + requires __has_completion_scheduler_for<_SetTag, _Child, __fwd_env_t<_Env>...> + && (__use_predecessor_completion_scheduler<_SetTag, _Env...>()) + [[nodiscard]] + constexpr auto + __query(get_completion_scheduler_t<_SetTag>, _Env const &... __env) const noexcept + { + return STDEXEC::get_completion_scheduler<_SetTag>(get_env(__child_), __fwd_env(__env)...); + } + + _Child const & __child_; }; public: static constexpr auto __get_attrs = - [](__ignore, __ignore, _Child const & __child) noexcept -> decltype(auto) + [](__ignore, _Fun const &, _Child const & __child) noexcept { - // TODO(ericniebler): this needs a proper implementation - return __fwd_env(STDEXEC::get_env(__child)); + return __attrs<_Child, _Fun>{{}, __child}; }; template static consteval auto __get_completion_signatures() { static_assert(__sender_for<_CvSender, _LetTag>); - constexpr auto __get_cmpl_info = - __impls::__get_cmpl_info<__fn_t<_CvSender>, __child_of<_CvSender>, _Env...>(); + auto __get_cmpl_info = + __impls_for::__mk_get_cmpl_info<__fn_t<_CvSender>, __child_of<_CvSender>, _Env...>(); if constexpr (!__decay_copyable<_CvSender>) return STDEXEC::__throw_compile_time_error<_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_, @@ -676,6 +624,28 @@ namespace STDEXEC static_cast<_Receiver&&>(__rcvr)); }; }; + + //! CRTP base of the \c let_*_t types + template + struct __let_t + { + template + constexpr auto operator()(_Sender&& __sndr, _Fun __fn) const -> __well_formed_sender auto + { + return __make_sexpr<_LetTag>(static_cast<_Fun&&>(__fn), static_cast<_Sender&&>(__sndr)); + } + + template + STDEXEC_ATTRIBUTE(always_inline) + constexpr auto operator()(_Fun __fn) const + { + return __closure(*this, static_cast<_Fun&&>(__fn)); + } + + private: + friend _LetTag; + __let_t() = default; + }; } // namespace __let struct let_value_t : __let::__let_t @@ -683,11 +653,13 @@ namespace STDEXEC using __t = set_value_t; let_value_t() = default; }; + struct let_error_t : __let::__let_t { using __t = set_error_t; let_error_t() = default; }; + struct let_stopped_t : __let::__let_t { using __t = set_stopped_t; @@ -699,15 +671,15 @@ namespace STDEXEC inline constexpr let_stopped_t let_stopped{}; template <> - struct __sexpr_impl : __let::__impls + struct __sexpr_impl : __let::__impls_for {}; template <> - struct __sexpr_impl : __let::__impls + struct __sexpr_impl : __let::__impls_for {}; template <> - struct __sexpr_impl : __let::__impls + struct __sexpr_impl : __let::__impls_for {}; } // namespace STDEXEC diff --git a/include/stdexec/__detail/__prologue.hpp b/include/stdexec/__detail/__prologue.hpp index a6e582ebe..cc9008b2e 100644 --- a/include/stdexec/__detail/__prologue.hpp +++ b/include/stdexec/__detail/__prologue.hpp @@ -26,6 +26,7 @@ #include STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_EDG(is_constant_evaluated_in_nonconstexpr_context) // Windows.h macros: diff --git a/include/stdexec/__detail/__ranges.hpp b/include/stdexec/__detail/__ranges.hpp index fc85e66a1..0066caac2 100644 --- a/include/stdexec/__detail/__ranges.hpp +++ b/include/stdexec/__detail/__ranges.hpp @@ -17,13 +17,14 @@ #include "__config.hpp" +#include #include #include "__prologue.hpp" namespace STDEXEC { - namespace [[deprecated("use std::ranges directly")]] ranges + namespace [[deprecated("Use std::ranges instead")]] ranges { using std::ranges::begin; using std::ranges::end; @@ -33,6 +34,36 @@ namespace STDEXEC using std::ranges::iterator_t; using std::ranges::sentinel_t; } // namespace ranges + + namespace __ranges + { + // Define `std::from_range` if the standard library doesn't provide it. +#if __cpp_lib_containers_ranges >= 202202L + using std::from_range_t; + using std::from_range; +#else + struct from_range_t + { + explicit from_range_t() = default; + }; + inline constexpr from_range_t from_range{}; +#endif + + // Define `ranges::contains` if the standard library doesn't provide it. +#if __cpp_lib_ranges_contains >= 202207L + using std::ranges::contains; +#else + inline constexpr auto contains = + []>(_Range&& __rng, + _Value const & __value, + _Proj __proj = {}) -> bool + { + return std::ranges::find(__rng, __value, __proj) != std::ranges::end(__rng); + }; +#endif + } // namespace __ranges } // namespace STDEXEC #include "__epilogue.hpp" diff --git a/include/stdexec/__detail/__senders.hpp b/include/stdexec/__detail/__senders.hpp index 4cac52f9e..f71863681 100644 --- a/include/stdexec/__detail/__senders.hpp +++ b/include/stdexec/__detail/__senders.hpp @@ -62,7 +62,7 @@ namespace STDEXEC ///////////////////////////////////////////////////////////////////////////// // early sender type-checking template - concept __well_formed_sender = sender_in<_Sender> || dependent_sender<_Sender>; + concept __well_formed_sender = dependent_sender<_Sender> || sender_in<_Sender>; template <__well_formed_sender _Sender> constexpr void __ensure_well_formed_sender() noexcept diff --git a/include/stdexec/__detail/__static_vector.hpp b/include/stdexec/__detail/__static_vector.hpp index ae81bc198..cfca14f31 100644 --- a/include/stdexec/__detail/__static_vector.hpp +++ b/include/stdexec/__detail/__static_vector.hpp @@ -76,23 +76,38 @@ namespace STDEXEC __static_vector() = default; +#if STDEXEC_CLANG() && STDEXEC_CLANG_VERSION < 2000 + // Before Clang 20, CTAD with initializer lists was broken in constant evaluation. + template <__same_as<_Tp>... _Values> + requires(sizeof...(_Values) <= _Capacity) + constexpr __static_vector(_Values... __values) + : __size_(sizeof...(_Values)) + , __data_{__values...} + {} +#else constexpr __static_vector(std::initializer_list __init) - noexcept(__nothrow_copy_constructible) - { - auto const __count = (std::min) (__init.size(), _Capacity); - auto const __end = std::ranges::copy_n(__init.begin(), __count, __data_).out; - __size_ = __end - __data_; - } + : __static_vector(__init.begin(), __init.size(), __make_indices<_Capacity>()) + {} +#endif + + template + constexpr __static_vector(_Range &&__rng) + : __static_vector(std::ranges::begin(__rng), + std::size_t(std::ranges::distance(__rng)), + __make_indices<_Capacity>()) + {} [[nodiscard]] constexpr auto operator[](std::size_t __i) noexcept -> value_type & { + STDEXEC_ASSERT(__i < size()); return __data_[__i]; } [[nodiscard]] constexpr auto operator[](std::size_t __i) const noexcept -> value_type const & { + STDEXEC_ASSERT(__i < size()); return __data_[__i]; } @@ -126,6 +141,12 @@ namespace STDEXEC return __size_; } + [[nodiscard]] + constexpr auto empty() const noexcept -> bool + { + return __size_ == 0; + } + [[nodiscard]] static constexpr auto capacity() noexcept -> std::size_t { @@ -134,6 +155,7 @@ namespace STDEXEC constexpr void resize(std::size_t __new_size) noexcept { + STDEXEC_ASSERT(__new_size <= capacity()); __size_ = __new_size; } @@ -144,8 +166,18 @@ namespace STDEXEC return end(); } + // These must be public so that __static_vector is a structual type. std::size_t __size_ = 0; value_type __data_[_Capacity]; + + private: + template + constexpr explicit __static_vector(_Iterator __it, std::size_t const __size, __indices<_Is...>) + : __size_(std::min(__size, _Capacity)) + , __data_{(_Is < __size_ ? *__it++ : value_type{})...} + { + STDEXEC_ASSERT(__size <= _Capacity); + } }; // Specialization of __static_vector for zero capacity that doesn't require default @@ -159,6 +191,13 @@ namespace STDEXEC __static_vector() = default; + template + requires __std::constructible_from> + constexpr __static_vector(_Range &&__rng) + { + STDEXEC_ASSERT(std::ranges::distance(__rng) == 0); + } + [[nodiscard]] constexpr auto operator[](std::size_t) noexcept -> value_type & { @@ -201,6 +240,12 @@ namespace STDEXEC return 0; } + [[nodiscard]] + static constexpr auto empty() noexcept -> bool + { + return true; + } + [[nodiscard]] static constexpr auto capacity() noexcept -> std::size_t { @@ -219,12 +264,6 @@ namespace STDEXEC template ... _Rest> STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE __static_vector(_First, _Rest...) -> __static_vector<_First, 1 + sizeof...(_Rest)>; - - template ... _Rest> - constexpr auto __make_static_vector(_First __first, _Rest... __rest) noexcept - { - return __static_vector<_First, 1 + sizeof...(_Rest)>{__first, __rest...}; - } } // namespace STDEXEC #include "__epilogue.hpp" diff --git a/include/stdexec/functional.hpp b/include/stdexec/functional.hpp index c23fbc7d5..875cdaf86 100644 --- a/include/stdexec/functional.hpp +++ b/include/stdexec/functional.hpp @@ -273,15 +273,53 @@ namespace STDEXEC struct __construct_from { template - requires __initializable_from<_Ty, _As...> + requires __std::constructible_from<_Ty, _As...> STDEXEC_ATTRIBUTE(host, device, always_inline) - constexpr auto - operator()(_As &&...__as) const noexcept(__nothrow_initializable_from<_Ty, _As...>) -> _Ty + constexpr auto operator()(_As &&...__as) const // + noexcept(__nothrow_constructible_from<_Ty, _As...>) -> _Ty + { + return _Ty(static_cast<_As &&>(__as)...); + } + + template + requires __std::constructible_from<_Ty, _As...> || __initializable_from<_Ty, _As...> + STDEXEC_ATTRIBUTE(host, device, always_inline) + constexpr auto operator()(_As &&...__as) const // + noexcept(__nothrow_initializable_from<_Ty, _As...>) -> _Ty { return _Ty{static_cast<_As &&>(__as)...}; } }; + // usage: __with_indices<3>()([](args...){...}, args...) + template + struct __with_indices + { + struct __impl_fn + { + // clang-format off + template + STDEXEC_ATTRIBUTE(host, device, always_inline) + constexpr auto operator()(__indices<_Is...>, _Fn &&__fn, _As &&...__as) const + STDEXEC_AUTO_RETURN + ( + static_cast<_Fn &&>(__fn).template operator()<_Is...>(static_cast<_As &&>(__as)...) + ) + // clang-format on + }; + + template + requires __callable<__impl_fn, __make_indices<_Count>, _Fn, _As...> + STDEXEC_ATTRIBUTE(host, device, always_inline) + constexpr auto operator()(_Fn &&__fn, _As &&...__as) const + noexcept(__nothrow_callable<__impl_fn, __make_indices<_Count>, _Fn, _As...>) -> decltype(auto) + { + return __impl_fn()(__make_indices<_Count>{}, + static_cast<_Fn &&>(__fn), + static_cast<_As &&>(__as)...); + } + }; + //! \brief Helper to combine multiple function objects into one overload set template struct __overload : _Fns... @@ -370,6 +408,12 @@ namespace STDEXEC noexcept(__nothrow_move_constructible<_BoundArgs...> && __nothrow_decay_copyable<_Fn>) { using __binder_t = __back_binder<__decay_t<_Fn>, _BoundArgs...>; - return __binder_t{static_cast<_Fn &&>(__fn), static_cast<_BoundArgs &&>(__bound_args)...}; + return __binder_t{static_cast<_Fn &&>(__fn), {static_cast<_BoundArgs &&>(__bound_args)...}}; + }; + + template + constexpr auto __bind_back(_Fn &&__fn) noexcept(__nothrow_move_constructible<_Fn>) -> _Fn + { + return static_cast<_Fn &&>(__fn); }; } // namespace STDEXEC diff --git a/test/stdexec/algos/adaptors/test_let_stopped.cpp b/test/stdexec/algos/adaptors/test_let_stopped.cpp index 5ef07ea31..5105135a1 100644 --- a/test/stdexec/algos/adaptors/test_let_stopped.cpp +++ b/test/stdexec/algos/adaptors/test_let_stopped.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include "stdexec/__detail/__sender_introspection.hpp" #include #include #include diff --git a/test/stdexec/algos/adaptors/test_let_value.cpp b/test/stdexec/algos/adaptors/test_let_value.cpp index dec4733ef..ebc3415bd 100644 --- a/test/stdexec/algos/adaptors/test_let_value.cpp +++ b/test/stdexec/algos/adaptors/test_let_value.cpp @@ -75,7 +75,7 @@ namespace TEST_CASE("let_value returning void can we waited on", "[adaptors][let_value]") { ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(); }); - STDEXEC::sync_wait(std::move(snd)); + ex::sync_wait(std::move(snd)); } TEST_CASE("let_value can be used to produce values", "[adaptors][let_value]") @@ -164,9 +164,9 @@ namespace }; auto snd = ex::just(13) | ex::let_value(invocable{}); static_assert( - set_equivalent<::STDEXEC::completion_signatures<::STDEXEC::set_value_t(int), - ::STDEXEC::set_error_t(std::exception_ptr)>, - ::STDEXEC::completion_signatures_of_t>>); + set_equivalent< + ::ex::completion_signatures<::ex::set_value_t(int), ::ex::set_error_t(std::exception_ptr)>, + ::ex::completion_signatures_of_t>>); auto op = ex::connect(std::move(snd), expect_error_receiver{}); ex::start(op); } @@ -363,7 +363,7 @@ namespace struct let_value_test_domain { template Sender> - static auto transform_sender(STDEXEC::set_value_t, Sender&&, auto&&...) + static auto transform_sender(ex::set_value_t, Sender&&, auto&&...) { return ex::just(std::string{"hallo"}); } @@ -416,17 +416,16 @@ namespace struct throws_on_connect { - using sender_concept = ::STDEXEC::sender_tag; + using sender_concept = ::ex::sender_tag; template static consteval auto get_completion_signatures() noexcept { - return ::STDEXEC::completion_signatures<::STDEXEC::set_value_t()>{}; + return ::ex::completion_signatures<::ex::set_value_t()>{}; } template - auto - connect(Receiver) const -> ::STDEXEC::connect_result_t + auto connect(Receiver) const -> ::ex::connect_result_t { throw std::logic_error("TEST"); } @@ -439,7 +438,7 @@ namespace { struct receiver { - using receiver_concept = ::STDEXEC::receiver_tag; + using receiver_concept = ::ex::receiver_tag; std::shared_ptr ptr; void set_value() noexcept { @@ -452,9 +451,9 @@ namespace *ptr = 5; } }; - auto const ptr = std::make_shared(0); - auto sender = ex::let_value(::STDEXEC::just(), []() noexcept { return throws_on_connect{}; }); - auto op = ex::connect(std::move(sender), receiver{ptr}); + auto const ptr = std::make_shared(0); + auto sender = ex::let_value(::ex::just(), []() noexcept { return throws_on_connect{}; }); + auto op = ex::connect(std::move(sender), receiver{ptr}); ex::start(op); CHECK(*ptr == 5); } @@ -481,18 +480,18 @@ namespace struct immovable_sender { - using sender_concept = ::STDEXEC::sender_tag; + using sender_concept = ::ex::sender_tag; template static consteval auto get_completion_signatures() noexcept { - return ::STDEXEC::completion_signatures_of_t{}; + return ::ex::completion_signatures_of_t{}; } template auto connect(Receiver r) const & noexcept { - return ::STDEXEC::connect(::STDEXEC::just(), std::move(r)); + return ::ex::connect(::ex::just(), std::move(r)); } immovable_sender() = default; @@ -501,10 +500,10 @@ namespace throw std::logic_error("Unexpected copy"); } }; - static_assert(::STDEXEC::sender); - static_assert(::STDEXEC::sender); - static_assert(::STDEXEC::sender_in>); - static_assert(::STDEXEC::sender_in>); + static_assert(::ex::sender); + static_assert(::ex::sender); + static_assert(::ex::sender_in>); + static_assert(::ex::sender_in>); TEST_CASE("If the sender factory returns a reference to a sender that reference is passed to " "connect", @@ -516,4 +515,20 @@ namespace auto op = ex::connect(sender, expect_void_receiver{}); ex::start(op); } + + TEST_CASE("let_value does not complete inline if the successor doesn't complete inline", + "[adaptors][let_value]") + { + auto sndr = ex::read_env(ex::get_scheduler) + | ex::let_value( + [](auto sched) noexcept + { + return ex::schedule(std::move(sched)) | ex::then([]() noexcept { return 0; }); + }); + + using sender_t = decltype(sndr); + using env_t = ex::prop; + + STATIC_REQUIRE_FALSE(ex::__completes_inline); + } } // namespace diff --git a/test/stdexec/queries/test_get_completion_behavior.cpp b/test/stdexec/queries/test_get_completion_behavior.cpp index 0bc0c87f7..51cbd93ec 100644 --- a/test/stdexec/queries/test_get_completion_behavior.cpp +++ b/test/stdexec/queries/test_get_completion_behavior.cpp @@ -36,7 +36,7 @@ static_assert((cb::__inline_completion | cb::__inline_completion) == cb::__inlin static_assert((cb::__asynchronous | cb::__asynchronous_affine) == cb::__asynchronous); static_assert((cb::__asynchronous | cb::__inline_completion) == cb::__unknown); static_assert(int((cb::__asynchronous_affine | cb::__inline_completion).value) - == (cb::__async_ | cb::__inline_)); + == (int(cb::__asynchronous_affine.value) | int(cb::__inline_completion.value))); static_assert(!cb::__is_affine(cb::__asynchronous)); static_assert(cb::__is_affine(cb::__inline_completion));