diff --git a/include/exec/repeat_until.hpp b/include/exec/repeat_until.hpp index 189f76108..497ad8f7f 100644 --- a/include/exec/repeat_until.hpp +++ b/include/exec/repeat_until.hpp @@ -21,14 +21,21 @@ #include "../stdexec/__detail/__optional.hpp" #include "../stdexec/execution.hpp" +#include "completion_signatures.hpp" #include "sequence.hpp" #include "trampoline_scheduler.hpp" #include #include +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_EDG(not_used_in_template_function_params) + namespace experimental::execution { + struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_; + struct _EXPECTING_A_SENDER_OF_VOID_; + namespace __repeat { using namespace STDEXEC; @@ -81,8 +88,7 @@ namespace experimental::execution else { // Mixed results: - constexpr bool __is_nothrow = noexcept( - (static_cast(static_cast<_Booleans &&>(__bools)) && ...)); + constexpr bool __is_nothrow = (std::is_nothrow_convertible_v<_Booleans, bool> && ...); STDEXEC_TRY { // If the child sender completed with true, we're done @@ -110,11 +116,11 @@ namespace experimental::execution template constexpr void set_error(_Error &&__err) noexcept - { // intentionally pass-by-value + { STDEXEC_TRY { auto __err_copy = static_cast<_Error &&>(__err); // make a local copy of the error... - __state_->__cleanup(); // because this could potentially invalidate it. + __state_->__cleanup(); // ... because this could potentially invalidate it. STDEXEC::set_error(std::move(__state_->__rcvr_), static_cast<_Error &&>(__err_copy)); } STDEXEC_CATCH_ALL @@ -134,9 +140,9 @@ namespace experimental::execution } [[nodiscard]] - constexpr auto get_env() const noexcept -> env_of_t<_Receiver> + constexpr auto get_env() const noexcept -> __fwd_env_t> { - return STDEXEC::get_env(__state_->__rcvr_); + return __fwd_env(STDEXEC::get_env(__state_->__rcvr_)); } __opstate_base<_Receiver> *__state_; @@ -207,69 +213,97 @@ namespace experimental::execution STDEXEC_PRAGMA_POP() - struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_ - {}; - struct _EXPECTING_A_SENDER_OF_VOID_ - {}; - - template - using __values_t = - // There's something funny going on with __if_c here. Use std::conditional_t instead. :-( - std::conditional_t< - ((sizeof...(_Args) == 1) && (__std::convertible_to<_Args, bool> && ...)), - std::conditional_t<(__bool_constant<_Args, false> && ...), - completion_signatures<>, - completion_signatures>, - __mexception<_WHAT_(_INVALID_ARGUMENT_), - _WHERE_(_IN_ALGORITHM_, repeat_until_t), - _WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_), - _WITH_PRETTY_SENDER_<_Child>>>; - - template - using __values_overload_nothrow_bool_convertible_t = - __mand...>; - - template - using __values_nothrow_bool_convertible_t = - __value_types_t<__completion_signatures_of_t<_Sender, _Env...>, // sigs - __qq<__values_overload_nothrow_bool_convertible_t>, // tuple - __qq<__mand> // variant - >; - - template - using __with_eptr_completion_t = __eptr_completion_unless< - __values_nothrow_bool_convertible_t<_Sender, _Env...>::value - && __cmplsigs::__partitions_of_t< - __completion_signatures_of_t<_Sender, _Env...>>::__nothrow_decay_copyable::__errors::value - && (__nothrow_connectable<_Sender, __receiver_archetype<_Env>> && ...)>; - - template - using __delete_set_value_t = completion_signatures<>; - - template - using __completions_t = STDEXEC::transform_completion_signatures< - __completion_signatures_of_t<__decay_t<_Child> &, _Env...>, - STDEXEC::transform_completion_signatures< - __completion_signatures_of_t, _Env...>, - __with_eptr_completion_t<_Child, _Env...>, - __delete_set_value_t>, - __mbind_front_q<__values_t, _Child>::template __f>; - struct __repeat_until_impl : __sexpr_defaults { + template + static constexpr auto __transform_values = []() + { + if constexpr (sizeof...(_Args) != 1 || (!__std::convertible_to<_Args, bool> || ...)) + { + return exec::throw_compile_time_error< + _WHAT_(_INVALID_ARGUMENT_), + _WHERE_(_IN_ALGORITHM_, repeat_until_t), + _WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_), + _WITH_PRETTY_SENDER_<_Child>>(); + } + else if constexpr ((__bool_constant<_Args, false> && ...)) + { + return STDEXEC::completion_signatures{}; + } + else if constexpr ((std::is_nothrow_convertible_v<_Args, bool> && ...)) + { + return STDEXEC::completion_signatures(); + } + else + { + return STDEXEC::completion_signatures(); + } + }; + + static constexpr auto __transform_errors = []() noexcept + { + if constexpr (__nothrow_decay_copyable<_Error> || __decays_to<_Error, std::exception_ptr>) + { + return STDEXEC::completion_signatures(); + } + else + { + return STDEXEC::completion_signatures(); + } + }; + template static consteval auto __get_completion_signatures() { - // TODO: port this to use constant evaluation - return __completions_t<__child_of<_Sender>, _Env...>{}; + using __child_t = __child_of<_Sender>; + using __bouncer_t = schedule_result_t; + using __eptr_completion_t = set_error_t(std::exception_ptr); + constexpr auto __eptr_completion = (__eptr_completion_t *) nullptr; + + STDEXEC_COMPLSIGS_LET( + __sigs, + exec::transform_completion_signatures(get_completion_signatures<__child_t, _Env...>(), + __transform_values<__child_t>, + __transform_errors)) + { + // The repeat_until sender is a dependent sender if one of the following is + // true: + // - the child sender is a dependent sender, or + // - the trampoline scheduler's sender is a dependent sender, or + // - sizeof...(_Env) == 0 and the child sender does not have a + // set_error(exception_ptr) completion. + constexpr bool __is_dependent = (sizeof...(_Env) == 0) + && (dependent_sender<__bouncer_t> + || !__sigs.__contains(__eptr_completion)); + if constexpr (__is_dependent) + { + return exec::throw_compile_time_error>(); + } + else + { + constexpr bool __has_nothrow_connect = + (__nothrow_connectable<__child_t, __receiver_archetype<_Env>> || ...); + constexpr auto __eptr_sigs = __eptr_completion_unless<__has_nothrow_connect>(); + constexpr auto __bouncer_sigs = exec::transform_completion_signatures( + get_completion_signatures<__bouncer_t, _Env...>(), + exec::ignore_completion()); // drop the set_value_t() completion from the + // trampoline scheduler. + + return exec::concat_completion_signatures(__sigs, __eptr_sigs, __bouncer_sigs); + } + } }; static constexpr auto __connect = [](_Sender &&__sndr, _Receiver __rcvr) noexcept( - noexcept(__opstate(STDEXEC::__get<2>(__declval<_Sender>()), __declval<_Receiver>()))) + __nothrow_constructible_from<__opstate<__child_of<_Sender>, _Receiver>, + __child_of<_Sender>, + _Receiver>) { - return __opstate(STDEXEC::__get<2>(static_cast<_Sender &&>(__sndr)), - static_cast<_Receiver &&>(__rcvr)); + auto &[__tag, __ign, __child] = __sndr; + return __opstate(STDEXEC::__forward_like<_Sender>(__child), std::move(__rcvr)); }; }; @@ -362,3 +396,5 @@ namespace STDEXEC struct __sexpr_impl : exec::__repeat::__repeat_until_impl {}; } // namespace STDEXEC + +STDEXEC_PRAGMA_POP() diff --git a/include/exec/sequence.hpp b/include/exec/sequence.hpp index 925b679ab..75c4c5fb9 100644 --- a/include/exec/sequence.hpp +++ b/include/exec/sequence.hpp @@ -37,12 +37,14 @@ namespace experimental::execution { template STDEXEC_ATTRIBUTE(nodiscard, host, device) - constexpr auto operator()(Sender sndr) const -> Sender; + constexpr auto operator()(Sender sndr) const + noexcept(STDEXEC::__nothrow_move_constructible) -> Sender; template requires(sizeof...(Senders) > 1) STDEXEC_ATTRIBUTE(nodiscard, host, device) - constexpr auto operator()(Senders... sndrs) const -> _sndr; + constexpr auto operator()(Senders... sndrs) const + noexcept(STDEXEC::__nothrow_move_constructible) -> _sndr; }; template @@ -334,7 +336,8 @@ namespace experimental::execution template STDEXEC_ATTRIBUTE(host, device) - constexpr auto sequence_t::operator()(Sender sndr) const -> Sender + constexpr auto sequence_t::operator()(Sender sndr) const + noexcept(STDEXEC::__nothrow_move_constructible) -> Sender { return sndr; } @@ -342,7 +345,8 @@ namespace experimental::execution template requires(sizeof...(Senders) > 1) STDEXEC_ATTRIBUTE(host, device) - constexpr auto sequence_t::operator()(Senders... sndrs) const -> _sndr + constexpr auto sequence_t::operator()(Senders... sndrs) const + noexcept(STDEXEC::__nothrow_move_constructible) -> _sndr { return _sndr{{}, {}, {static_cast(sndrs)...}}; } diff --git a/include/stdexec/__detail/__receivers.hpp b/include/stdexec/__detail/__receivers.hpp index 8781277fc..167021c6c 100644 --- a/include/stdexec/__detail/__receivers.hpp +++ b/include/stdexec/__detail/__receivers.hpp @@ -250,8 +250,10 @@ namespace STDEXEC }; } - template - struct __receiver_archetype + // Used to test whether a sender has a nothrow connect to a receiver whose environment + // is _Env..., or if _Env... is empty (indicating that the sender is non-dependent), to + // a receiver with an arbitrary environment. + struct __receiver_archetype_base { using receiver_concept = receiver_t; @@ -267,9 +269,13 @@ namespace STDEXEC STDEXEC_ATTRIBUTE(host, device) constexpr void set_stopped() noexcept {} + }; + template + struct __receiver_archetype : __receiver_archetype_base + { STDEXEC_ATTRIBUTE(nodiscard, noreturn, host, device) - _Env get_env() const noexcept + auto get_env() const noexcept -> _Env { STDEXEC_ASSERT(false); STDEXEC_TERMINATE(); diff --git a/include/stdexec/__detail/__transform_completion_signatures.hpp b/include/stdexec/__detail/__transform_completion_signatures.hpp index 002741dcf..3860a5e95 100644 --- a/include/stdexec/__detail/__transform_completion_signatures.hpp +++ b/include/stdexec/__detail/__transform_completion_signatures.hpp @@ -325,24 +325,24 @@ namespace STDEXEC template using __transform_result_t = decltype(__declval<_Fn>().template operator()<_Args...>()); - template + template [[nodiscard]] consteval auto - __transform_expr(_Fn const &__fn) -> __transform_result_t<_Fn const &, _SetTag, _Args...> + __transform_expr(_Fn const &__fn, int) -> __transform_result_t<_Fn const &, _Args...> { - return __fn.template operator()<_SetTag, _Args...>(); + return __fn.template operator()<_Args...>(); } template [[nodiscard]] - consteval auto __transform_expr(_Fn const &__fn) -> __call_result_t<_Fn const &> + consteval auto __transform_expr(_Fn const &__fn, long) -> __call_result_t<_Fn const &> { return __fn(); } template - using __transform_expr_t = decltype(__cmplsigs::__transform_expr<_Args...>( - __declval<_Fn const &>())); + using __transform_expr_t = + decltype(__cmplsigs::__transform_expr<_Args...>(__declval<_Fn const &>(), 0)); // transform_completion_signatures: template @@ -354,11 +354,11 @@ namespace STDEXEC using __completions_t = __transform_expr_t<_Fn, _Args...>; if constexpr (__well_formed_completions<__completions_t>) { - return __cmplsigs::__transform_expr<_Args...>(__fn); + return __cmplsigs::__transform_expr<_Args...>(__fn, 0); } else { - (void) __cmplsigs::__transform_expr<_Args...>(__fn); // potentially throwing + (void) __cmplsigs::__transform_expr<_Args...>(__fn, 0); // potentially throwing return STDEXEC::__throw_compile_time_error< _IN_TRANSFORM_COMPLETION_SIGNATURES_, _A_TRANSFORM_FUNCTION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION_, diff --git a/test/exec/test_repeat_until.cpp b/test/exec/test_repeat_until.cpp index 0435bc33c..d47273841 100644 --- a/test/exec/test_repeat_until.cpp +++ b/test/exec/test_repeat_until.cpp @@ -28,9 +28,11 @@ #include #include +#include #include #include #include +#include #include namespace ex = STDEXEC; @@ -315,7 +317,7 @@ namespace static_assert( std::same_as, ex::__detail::__not_a_variant>, "Expect no value completions"); - static_assert(ex::sender_of, + static_assert(ex::sender_of>, "Missing set_stopped_t() from upstream"); // operator| and sync_wait require valid completion signatures @@ -465,4 +467,21 @@ namespace "Missing added set_error_t(std::exception_ptr)"); } } + + TEST_CASE("repeat_until works with a dependent sender", "[adaptors][repeat_until]") + { + std::size_t invoked = 0; + auto snd = exec::repeat_until(ex::read_env(ex::get_stop_token) + | ex::then( + [&](auto token) noexcept + { + ++invoked; + return std::is_same_v; + })); + static_assert(ex::dependent_sender); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + CHECK(!invoked); + ex::start(op); + CHECK(invoked == 1); + } } // namespace