From 4a2dfa419f2e6024575e3ceb35f3390356a70565 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 15 Apr 2026 09:52:57 -0700 Subject: [PATCH 1/4] fixes for `get_completion_signatures` with constexpr exceptions --- include/exec/any_sender_of.hpp | 4 +- .../system_context_replaceability_api.hpp | 14 +- include/exec/sequence/any_sequence_of.hpp | 5 + include/exec/sequence/merge.hpp | 2 +- include/exec/sequence/transform_each.hpp | 6 +- include/exec/sequence_senders.hpp | 87 ++--- include/stdexec/__detail/__basic_sender.hpp | 3 +- .../__detail/__completion_signatures.hpp | 142 ++++---- include/stdexec/__detail/__let.hpp | 343 ++++++++---------- include/stdexec/__detail/__meta.hpp | 25 +- include/stdexec/__detail/__schedulers.hpp | 14 +- .../stdexec/__detail/__sender_concepts.hpp | 5 +- include/stdexec/__detail/__submit.hpp | 2 +- include/stdexec/__detail/__task.hpp | 2 +- test/exec/test_fork_join.cpp | 6 +- 15 files changed, 315 insertions(+), 345 deletions(-) diff --git a/include/exec/any_sender_of.hpp b/include/exec/any_sender_of.hpp index bd6ceee22..93dbe7805 100644 --- a/include/exec/any_sender_of.hpp +++ b/include/exec/any_sender_of.hpp @@ -81,7 +81,7 @@ namespace experimental::execution template inline constexpr auto _check_query_v = std::conditional_t, + __mexception, _no_query_error_t>{}; template @@ -477,7 +477,7 @@ namespace experimental::execution using _base_t::_base_t; template <__std::derived_from<_interface_> Self, class... Env> - static consteval auto get_completion_signatures() noexcept + static consteval auto get_completion_signatures() { // throw if Env does not contain the queries needed to type-erase the receiver: using _check_queries_t = __mfind_error<_check_query_t...>; diff --git a/include/exec/detail/system_context_replaceability_api.hpp b/include/exec/detail/system_context_replaceability_api.hpp index 00d836ea5..d73b18776 100644 --- a/include/exec/detail/system_context_replaceability_api.hpp +++ b/include/exec/detail/system_context_replaceability_api.hpp @@ -22,6 +22,12 @@ #include +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wdeprecated-declarations") +STDEXEC_PRAGMA_IGNORE_MSVC(4996) // warning C4996: 'function': was declared deprecated +STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity) +STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity_with_custom_message) + namespace experimental::execution { namespace [[deprecated("Use the " STDEXEC_PP_STRINGIZE(STDEXEC) // @@ -47,11 +53,6 @@ namespace experimental::execution return STDEXEC::parallel_scheduler_replacement::query_parallel_scheduler_backend(); } - STDEXEC_PRAGMA_PUSH() - STDEXEC_PRAGMA_IGNORE_GNU("-Wdeprecated-declarations") - STDEXEC_PRAGMA_IGNORE_MSVC(4996) // warning C4996: 'function': was declared deprecated - STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity) - STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity_with_custom_message) /// Set a factory for the parallel scheduler backend. /// Can be used to replace the parallel scheduler at runtime. /// Out of spec. @@ -63,7 +64,6 @@ namespace experimental::execution { return STDEXEC::parallel_scheduler_replacement::set_parallel_scheduler_backend(__new_factory); } - STDEXEC_PRAGMA_POP() /// Interface for completing a sender operation. Backend will call frontend though this interface /// for completing the `schedule` and `schedule_bulk` operations. @@ -79,6 +79,8 @@ namespace experimental::execution } // namespace system_context_replaceability } // namespace experimental::execution +STDEXEC_PRAGMA_POP() + namespace exec = experimental::execution; #endif diff --git a/include/exec/sequence/any_sequence_of.hpp b/include/exec/sequence/any_sequence_of.hpp index 9b1acde0f..e0b2d0350 100644 --- a/include/exec/sequence/any_sequence_of.hpp +++ b/include/exec/sequence/any_sequence_of.hpp @@ -21,6 +21,9 @@ #include "../any_sender_of.hpp" #include "../sequence_senders.hpp" +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Woverloaded-virtual") + namespace experimental::execution { template > @@ -225,4 +228,6 @@ namespace experimental::execution }; } // namespace experimental::execution +STDEXEC_PRAGMA_POP() + namespace exec = experimental::execution; diff --git a/include/exec/sequence/merge.hpp b/include/exec/sequence/merge.hpp index d34940a02..ddf6fa05a 100644 --- a/include/exec/sequence/merge.hpp +++ b/include/exec/sequence/merge.hpp @@ -174,7 +174,7 @@ namespace experimental::execution } template - static consteval auto get_completion_signatures() noexcept + static consteval auto get_completion_signatures() { static_assert(sender_for<_Self, merge_t>); auto __items = STDEXEC::__children_of<_Self, STDEXEC::__qq>(); diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index 9f8c9da45..74a5d4962 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -125,7 +125,7 @@ namespace experimental::execution template auto operator()(__ignore, _Adaptor __adaptor, _Sequence&& __sequence) - noexcept(__nothrow_decay_copyable<_Adaptor> && __nothrow_decay_copyable<_Sequence>) + noexcept(__nothrow_decay_copyable<_Adaptor, _Sequence>) -> __operation<_Sequence, _Receiver, _Adaptor> { return {static_cast<_Sequence&&>(__sequence), @@ -138,8 +138,8 @@ namespace experimental::execution { template auto operator()(_Sequence&& __sndr, _Adaptor&& __adaptor) const - noexcept(__nothrow_decay_copyable<_Sequence> && __nothrow_decay_copyable<_Adaptor>) - -> __well_formed_sequence_sender auto + noexcept(__nothrow_decay_copyable<_Sequence, _Adaptor>) // + -> __well_formed_sequence_sender auto { return make_sequence_expr(static_cast<_Adaptor&&>(__adaptor), static_cast<_Sequence&&>(__sndr)); diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index d8b0e163d..9d1d5ef61 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -230,10 +230,10 @@ namespace experimental::execution template struct item_types { - template > - static constexpr auto __transform(_Fn __fn, _Continuation __continuation = {}) + template + static constexpr auto __transform([[maybe_unused]] _Transform _transform, _Reduce _reduce) { - return __continuation(__fn.template operator()<_Senders>()...); + return _reduce(_transform.template operator()<_Senders>()...); } }; @@ -253,42 +253,6 @@ namespace experimental::execution struct _FAILED_TO_COMPUTE_SEQUENCE_ITEM_TYPES_ {}; -#if STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() - - template - [[nodiscard]] - consteval auto __invalid_item_types(_Values...) - { - return STDEXEC::__mexception<_What...>(); - } - -#else // ^^^ no constexpr exceptions ^^^ / vvv constexpr exceptions vvv - - // C++26, https://wg21.link/p3068 - template - [[noreturn, nodiscard]] - consteval auto __invalid_item_types([[maybe_unused]] _Values... __values) -> item_types<> - { - if constexpr (sizeof...(_Values) == 1) - { - throw __sequence_type_check_failure<_Values..., _What, _More...>(__values...); - } - else - { - throw __sequence_type_check_failure, _What, _More...>( - STDEXEC::__tuple{__values...}); - } - } - -#endif // ^^^ constexpr exceptions ^^^ - - template - [[nodiscard]] - consteval auto __invalid_item_types(STDEXEC::__mexception<_What...>) - { - return exec::__invalid_item_types<_What...>(); - } - template using __unrecognized_sequence_error_t = STDEXEC::__mexception(); + return STDEXEC::__dependent_sender_r, _Sequence>(); } else { - return __unrecognized_sequence_error_t<_Sequence, _Env...>(); + return STDEXEC::__throw_compile_time_error_r>( + __unrecognized_sequence_error_t<_Sequence, _Env...>()); } } @@ -591,6 +556,46 @@ namespace experimental::execution _Data __data_{}; }; +#if STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() + + template + [[nodiscard]] + consteval auto __invalid_item_types(_Values...) + { + return STDEXEC::__mexception<_What...>(); + } + +#else // ^^^ no constexpr exceptions ^^^ / vvv constexpr exceptions vvv + + // C++26, https://wg21.link/p3068 + template + [[noreturn, nodiscard]] + consteval auto __invalid_item_types([[maybe_unused]] _Values... __values) -> item_types<> + { + if constexpr (STDEXEC::__same_as<_What, STDEXEC::dependent_sender_error>) + { + throw STDEXEC::__mexception(); + } + else if constexpr (sizeof...(_Values) == 1) + { + throw __sequence_type_check_failure<_Values..., _What, _More...>(__values...); + } + else + { + throw __sequence_type_check_failure, _What, _More...>( + STDEXEC::__tuple<_Values...>{__values...}); + } + } + +#endif // ^^^ constexpr exceptions ^^^ + + template + [[nodiscard]] + consteval auto __invalid_item_types(STDEXEC::__mexception<_What...>) + { + return exec::__invalid_item_types<_What...>(); + } + struct _MISSING_SET_NEXT_OVERLOAD_FOR_ITEM_ {}; diff --git a/include/stdexec/__detail/__basic_sender.hpp b/include/stdexec/__detail/__basic_sender.hpp index 5a2de96a9..1f987463a 100644 --- a/include/stdexec/__detail/__basic_sender.hpp +++ b/include/stdexec/__detail/__basic_sender.hpp @@ -369,7 +369,8 @@ namespace STDEXEC } else { - return __throw_compile_time_error(__unrecognized_sender_error_t<_Self, _Env...>()); + return STDEXEC::__throw_compile_time_error( + __unrecognized_sender_error_t<_Self, _Env...>()); } } diff --git a/include/stdexec/__detail/__completion_signatures.hpp b/include/stdexec/__detail/__completion_signatures.hpp index d35a42e1a..2d6fd54b9 100644 --- a/include/stdexec/__detail/__completion_signatures.hpp +++ b/include/stdexec/__detail/__completion_signatures.hpp @@ -78,66 +78,6 @@ namespace STDEXEC using __completion_signature_ptrs_t = decltype(__cmplsigs::__repack_completions( static_cast<_SigPtrs>(nullptr)...)); -#if STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() - - template - [[nodiscard]] - consteval auto __throw_compile_time_error_r(_Values...) -> __mexception<_What...> - { - return {}; - } - - template - [[nodiscard]] - consteval auto __throw_compile_time_error(_Values...) -> __mexception<_What...> - { - return {}; - } - -#else // ^^^ no constexpr exceptions ^^^ / vvv constexpr exceptions vvv - - // C++26, https://wg21.link/p3068 - template - [[noreturn, nodiscard]] - consteval auto __throw_compile_time_error_r([[maybe_unused]] _Values... __values) -> _Return - { - if constexpr (__same_as<_What, dependent_sender_error>) - { - throw __mexception(); - } - else if constexpr (sizeof...(_Values) == 1) - { - throw __sender_type_check_failure<_Values..., _What, _More...>(__values...); - } - else - { - throw __sender_type_check_failure<__tuple<_Values...>, _What, _More...>(__tuple{__values...}); - } - } - - template - [[noreturn, nodiscard]] - consteval auto - __throw_compile_time_error([[maybe_unused]] _Values... __values) -> completion_signatures<> - { - return; - } -#endif // ^^^ constexpr exceptions ^^^ - - template - [[nodiscard]] - consteval auto __throw_compile_time_error_r(__mexception<_What...>) - { - return STDEXEC::__throw_compile_time_error_r<_Return, _What...>(); - } - - template - [[nodiscard]] - consteval auto __throw_compile_time_error(__mexception<_What...>) - { - return STDEXEC::__throw_compile_time_error<_What...>(); - } - namespace __cmplsigs { // __partitions is a cache of completion signatures for fast access. The @@ -490,6 +430,67 @@ namespace STDEXEC } }; +#if STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() + + template + [[nodiscard]] + consteval auto __throw_compile_time_error_r(_Values...) -> __mexception<_What...> + { + return {}; + } + + template + [[nodiscard]] + consteval auto __throw_compile_time_error(_Values...) -> __mexception<_What...> + { + return {}; + } + +#else // ^^^ no constexpr exceptions ^^^ / vvv constexpr exceptions vvv + + // C++26, https://wg21.link/p3068 + template + [[noreturn, nodiscard]] + consteval auto __throw_compile_time_error_r([[maybe_unused]] _Values... __values) -> _Return + { + if constexpr (__same_as<_What, dependent_sender_error>) + { + throw __mexception(); + } + else if constexpr (sizeof...(_Values) == 1) + { + throw __sender_type_check_failure<_Values..., _What, _More...>(__values...); + } + else + { + throw __sender_type_check_failure<__tuple<_Values...>, _What, _More...>(__tuple{__values...}); + } + } + + template + [[noreturn, nodiscard]] + consteval auto __throw_compile_time_error(_Values... __values) -> completion_signatures<> + { + (void) STDEXEC::__throw_compile_time_error_r, _What, _More...>( + static_cast<_Values>(__values)...); + } + +#endif // ^^^ constexpr exceptions ^^^ + + template + [[nodiscard]] + consteval auto __throw_compile_time_error_r(__mexception<_What...>) + { + return STDEXEC::__throw_compile_time_error_r<_Return, _What...>(); + } + + template + [[nodiscard]] + consteval auto __throw_compile_time_error(__mexception<_What...>) + { + return STDEXEC::__throw_compile_time_error<_What...>(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////// // __gather_completion_signatures_t namespace __detail @@ -569,12 +570,18 @@ namespace STDEXEC #if STDEXEC_NO_STDCPP_CONSTEXPR_EXCEPTIONS() # define STDEXEC_COMPLSIGS_LET(_ID, ...) \ - if constexpr ([[maybe_unused]] \ - auto _ID = __VA_ARGS__; \ + if constexpr ([[maybe_unused]] auto _ID = __VA_ARGS__; \ !STDEXEC::__valid_completion_signatures) { \ return _ID; \ } else + template + [[nodiscard]] + consteval auto __dependent_sender_r() noexcept -> __dependent_sender_error_t<_Sndr> + { + return {}; + } + template [[nodiscard]] consteval auto __dependent_sender() noexcept -> __dependent_sender_error_t<_Sndr> @@ -585,11 +592,16 @@ namespace STDEXEC #else // ^^^ no constexpr exceptions ^^^ / vvv constexpr exceptions vvv # define STDEXEC_COMPLSIGS_LET(_ID, ...) \ - if constexpr ([[maybe_unused]] \ - auto _ID = __VA_ARGS__; \ - false) { \ + if constexpr ([[maybe_unused]] auto _ID = __VA_ARGS__; false) { \ } else + template + [[noreturn, nodiscard]] + consteval auto __dependent_sender_r() -> _Result + { + throw __dependent_sender_error_t<_Sndr>{}; + } + template [[noreturn, nodiscard]] consteval auto __dependent_sender() -> completion_signatures<> diff --git a/include/stdexec/__detail/__let.hpp b/include/stdexec/__detail/__let.hpp index 78e237a32..1c1d89fe2 100644 --- a/include/stdexec/__detail/__let.hpp +++ b/include/stdexec/__detail/__let.hpp @@ -31,6 +31,8 @@ #include "__utility.hpp" #include "__variant.hpp" +#include "../functional.hpp" + #include namespace STDEXEC @@ -42,28 +44,6 @@ namespace STDEXEC template struct __let_t; - template - struct __let_tag - { - using __t = _SetTag; - }; - - template - extern __undefined<_SetTag> __let_from_set; - - template - extern let_value_t __let_from_set; - - template - extern let_error_t __let_from_set; - - template - extern let_stopped_t __let_from_set; - - template - using __on_not_callable = - __mbind_front_q<__callable_error_t, decltype(__let_from_set<_SetTag>)>; - template using __env2_t = __secondary_env_t<_Sender, _Env, _SetTag>; @@ -74,6 +54,10 @@ namespace STDEXEC struct __rcvr_env { using receiver_concept = receiver_tag; + + _Receiver& __rcvr_; + _Env2 const & __env_; + template constexpr void set_value(_As&&... __as) noexcept { @@ -92,15 +76,11 @@ namespace STDEXEC } [[nodiscard]] - constexpr decltype(__env::__join(__declval<_Env2 const &>(), - __declval>())) - get_env() const noexcept + constexpr auto get_env() const noexcept // + -> decltype(__env::__join(__env_, STDEXEC::get_env(__rcvr_))) { return __env::__join(__env_, STDEXEC::get_env(__rcvr_)); } - - _Receiver& __rcvr_; - _Env2 const & __env_; }; struct _FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_; @@ -108,6 +88,9 @@ namespace STDEXEC 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, @@ -115,10 +98,10 @@ namespace STDEXEC _Sender, _Env...>; - template - using __bad_result_sender = __not_a_sender< + template + using __bad_result_sender = __mexception< _WHAT_(_FUNCTION_MUST_RETURN_A_VALID_SENDER_IN_THE_CURRENT_ENVIRONMENT_), - _WHERE_(_IN_ALGORITHM_, decltype(__let_from_set<_SetTag>)), + _WHERE_(_IN_ALGORITHM_, _LetTag), _WITH_PRETTY_SENDER_<_Sender>, __fn_t<_WITH_ENVIRONMENT_, _JoinEnv2>..., __mapply_q<_NESTED_ERROR_, __try_completion_signatures_of_t<_Sender, _JoinEnv2...>>>; @@ -127,96 +110,6 @@ namespace STDEXEC concept __potentially_valid_sender_in = sender_in<_Sender, _JoinEnv2...> || (sender<_Sender> && (sizeof...(_JoinEnv2) == 0)); - template - using __ensure_sender_t = - __minvoke_if_c<__potentially_valid_sender_in<_Sender, _JoinEnv2...>, - __q1<__midentity>, - __mbind_back_q<__bad_result_sender, _SetTag, _JoinEnv2...>, - _Sender>; - - // A metafunction that computes the result sender type for a given set of argument types - template - struct __result_sender_fn - { - template - using __f = __minvoke_q<__ensure_sender_t, - _SetTag, - __mcall<__mtry_catch_q<__call_result_t, __on_not_callable<_SetTag>>, - _Fun, - __decay_t<_Args>&...>, - _JoinEnv2...>; - }; - - // The receiver that gets connected to the result sender is the input receiver, - // possibly augmented with the input sender's completion scheduler (which is - // where the result sender will be started). - template - using __result_receiver_t = __rcvr_env<_Receiver, _Env2>; - - template - using __checked_result_receiver_t = __result_receiver_t<_Receiver, _Env2>; - - template - using __submit_result_t = - submit_result<_ResultSender, __checked_result_receiver_t<_ResultSender, _Env2, _Receiver>>; - - template - struct __transform_signal_fn - { - template - using __nothrow_connect_t = __mbool< - __nothrow_decay_copyable<_Args...> && __nothrow_callable<_Fun, __decay_t<_Args>&...> - && (__nothrow_connectable<__mcall<__result_sender_fn<_SetTag, _Fun, _JoinEnv2>, _Args...>, - __receiver_archetype<_JoinEnv2>> - && ...)>; - - template - using __f = __mcall<__mtry_q<__concat_completion_signatures_t>, - __completion_signatures_of_t< - __mcall<__result_sender_fn<_SetTag, _Fun, _JoinEnv2...>, _Args...>, - _JoinEnv2...>, - __eptr_completion_unless_t<__nothrow_connect_t<_Args...>>>; - }; - - template - using __completions_t = __gather_completion_signatures_t< - __completion_signatures_of_t<_CvSender, _Env...>, - __t<_LetTag>, - __transform_signal_fn<__t<_LetTag>, - _Fun, - __result_env_t<__t<_LetTag>, _CvSender, _Env>...>::template __f, - __cmplsigs::__default_completion, - __mtry_q<__concat_completion_signatures_t>::__f>; - - struct _THE_SENDERS_RETURNED_BY_THE_GIVEN_FUNCTION_DO_NOT_SHARE_A_COMMON_DOMAIN_; - - template - struct __try_common_domain_fn - { - struct __error_fn - { - template - using __f = __mexception< - _WHAT_(_DOMAIN_ERROR_), - _WHY_(_THE_SENDERS_RETURNED_BY_THE_GIVEN_FUNCTION_DO_NOT_SHARE_A_COMMON_DOMAIN_), - _WHERE_(_IN_ALGORITHM_, decltype(__let_from_set<_SetTag>)), - _WITH_PRETTY_SENDERS_<_Senders...>>; - }; - - // TODO(ericniebler): this needs to be updated: - template - using __f = __mcall<__mtry_catch_q<__common_domain_t, __error_fn>, - __completion_domain_of_t<_SetTag, _Senders, _Env...>...>; - }; - - // Compute all the domains of all the result senders and make sure they're all the same - template - using __result_domain_t = __gather_completions_t< - _SetTag, - __completion_signatures_of_t<_Child, _Env>, - __result_sender_fn<_SetTag, _Fun, __result_env_t<_SetTag, _Child, _Env>>, - __try_common_domain_fn<_SetTag, _Env>>; - //! Metafunction creating the operation state needed to connect the result of calling //! the sender factory function, `_Fun`, and passing its result to a receiver. template @@ -225,11 +118,8 @@ namespace STDEXEC // compute the result of calling submit with the result of executing _Fun // with _Args. if the result is void, substitute with __ignore. template - using __f = __submit_result_t< - __mcall<__result_sender_fn<_SetTag, _Fun, __join_env_t<_Env2, env_of_t<_Receiver>>>, - _Args...>, - _Env2, - _Receiver>; + using __f = + 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 @@ -237,12 +127,13 @@ namespace STDEXEC 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_connectable<__call_result_t<_Fn, __decay_t<_Ts>&...>, __rcvr2_t>>; + using __f = __mbool<__nothrow_decay_copyable<_Ts...> + && __nothrow_connectable<__sndr2_t<_Ts...>, __rcvr2_t>>; }; template @@ -281,11 +172,11 @@ namespace STDEXEC { if constexpr (__same_as<_SetTag, _Tag>) { - using __sender_t = __call_result_t<_Fun, __decay_t<_Args>&...>; - using __submit_t = __submit_result_t<__sender_t, _Env2, _Receiver>; + using __sender_t = __invoke_result_t<_Fun, __decay_t<_Args>&...>; + using __submit_t = submit_result<__sender_t, __rcvr_env<_Receiver, _Env2>>; constexpr bool __nothrow_store = (__nothrow_decay_copyable<_Args> && ...); - constexpr bool __nothrow_invoke = __nothrow_callable<_Fun, __decay_t<_Args>&...>; + constexpr bool __nothrow_invoke = __nothrow_invocable<_Fun, __decay_t<_Args>&...>; constexpr bool __nothrow_submit = __nothrow_constructible_from<__submit_t, __sender_t, __second_rcvr_t>; @@ -348,6 +239,13 @@ namespace STDEXEC __opstate_base<_SetTag, _Fun, _Receiver, _Env2, _Tuples...>* __state_; }; + constexpr auto __mk_result_sndr = + [](_Fun& __fn, _Args&... __args) noexcept( + __nothrow_invocable<_Fun, _Args&...>) -> decltype(auto) + { + return STDEXEC::__invoke(static_cast<_Fun&&>(__fn), __args...); + }; + constexpr auto __start_next_fn = []( _Fun& __fn, @@ -356,9 +254,9 @@ namespace STDEXEC _Storage& __storage, _Tuple& __tupl) { - using __sender_t = __apply_result_t<_Fun, decltype(__tupl)>; - auto&& __sndr = STDEXEC::__apply(static_cast<_Fun&&>(__fn), __tupl); - using __submit_t = __submit_result_t<__sender_t, _Env2, _Receiver>; + decltype(auto) __sndr = STDEXEC::__apply(__mk_result_sndr, __tupl, __fn); + using __sender_t = decltype(__sndr); + using __submit_t = submit_result<__sender_t, __rcvr_env<_Receiver, _Env2>>; using __second_rcvr_t = __rcvr_env<_Receiver, _Env2>; __second_rcvr_t __rcvr2{__rcvr, static_cast<_Env2&&>(__env2)}; @@ -436,19 +334,6 @@ namespace STDEXEC // * the value completions of the predecessor sender // * the value completions of the secondary sender // - // 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 senders - // // The set_error completions of: // // * a let_value sender are: @@ -463,39 +348,42 @@ namespace STDEXEC // * a let_stopped sender are: // * the error completions of the predecessor sender // * the error completions of the secondary senders - - template + // + // 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 __result_sender_fn = __let::__result_sender_fn<_SetTag, _Fn, _JoinEnv2...>; - if constexpr (__minvocable<__result_sender_fn, _Ts...>) - { - using __sndr2_t = __mcall<__result_sender_fn, _Ts...>; - return STDEXEC::__get_completion_behavior<_SetTag, __sndr2_t, _JoinEnv2...>(); - } - else - { - return __completion_behavior::__unknown; - } + 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...>(); } template using __f = decltype(__impl<_Ts...>()); }; - template + template struct __domain_transform_fn { - using __result_sender_fn = - __let::__result_sender_fn<_SetTag, _Fn, __result_env_t<_SetTag, _Sender, _Env>...>; - template using __f = __completion_domain_of_t<_SetTag, - __mcall<__result_sender_fn, _As...>, + __invoke_result_t<_Fun, __decay_t<_As>&...>, __result_env_t<_SetTag, _Sender, _Env>...>; }; @@ -503,13 +391,13 @@ namespace STDEXEC //! @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 + template [[nodiscard]] consteval auto __get_completion_domain() noexcept { if constexpr (sender_in<_Sndr, _Env...>) { - using __domain_transform_fn = __let::__domain_transform_fn<_SetTag, _Fn, _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>, @@ -523,12 +411,12 @@ namespace STDEXEC } } - template + template using __let_completion_domain_t = __unless_one_of_t< - __result_of<__let::__get_completion_domain<_SetTag, _SetTag2, _Sndr, _Fn, _Env...>>, + __result_of<__let::__get_completion_domain<_LetTag, _SetTag, _Sndr, _Fun, _Env...>>, indeterminate_domain<>>; - template + template struct __attrs { using __set_tag_t = STDEXEC::__t<_LetTag>; @@ -540,30 +428,30 @@ namespace STDEXEC [[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, _Fn, _Env...>> + __let_completion_domain_t<_LetTag, __set_tag_t, _Sndr, _Fun, _Env...>> { return {}; } template <__one_of _Tag, class... _Env> - requires(__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fn, _Env>::value && ...) + 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, _Fn, _Env...>>> + __let_completion_domain_t<_LetTag, _Tag, _Sndr, _Fun, _Env...>>> { return {}; } template - requires(!__has_nothrow_completions_t<__set_tag_t, _Sndr, _Fn, _Env>::value) + 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, _Fn, _Env>>> + __let_completion_domain_t<_LetTag, set_error_t, _Sndr, _Fun, _Env>>> { return {}; } @@ -579,7 +467,7 @@ namespace STDEXEC // needs the constexpr computation broken up, hence the local variables.) using __transform_fn = __result_completion_behavior_fn<__set_tag_t, - _Fn, + _Fun, __result_env_t<__set_tag_t, _Sndr, _Env>...>; using __completions_t = __completion_signatures_of_t<_Sndr, __fwd_env_t<_Env>...>; @@ -600,24 +488,10 @@ namespace STDEXEC } }; - template - extern __undefined<_Tag> const __set_tag_from_let_v; - - template - extern __declfn_t const __set_tag_from_let_v; - - template - extern __declfn_t const __set_tag_from_let_v; - - template - extern __declfn_t const __set_tag_from_let_v; - //! Implementation of the `let_*_t` types, where `_SetTag` is, e.g., `set_value_t` for `let_value`. template struct __let_t { - using __t = decltype(__set_tag_from_let_v<_LetTag>()); - template constexpr auto operator()(_Sender&& __sndr, _Fun __fn) const -> __well_formed_sender auto { @@ -640,6 +514,7 @@ namespace STDEXEC struct __impls : __sexpr_defaults { private: + using __set_t = __t<_LetTag>; template using __fn_t = __decay_t<__data_of<_Sender>>; @@ -650,6 +525,13 @@ namespace STDEXEC __fwd_env_t>, __q<__decayed_tuple>, __mbind_front_q<__opstate, __t<_LetTag>, __child_of<_Sender>, __fn_t<_Sender>, _Receiver>>; + + template + using __not_decay_copyable_error_t = + __mexception<_WHAT_(_SENDER_RESULTS_ARE_NOT_DECAY_COPYABLE_), + _WHERE_(_IN_ALGORITHM_, _LetTag), + _WITH_ARGUMENTS_(_Args...)>; + public: static constexpr auto __get_attrs = [](__ignore, __ignore, _Child const & __child) noexcept -> decltype(auto) @@ -658,28 +540,84 @@ namespace STDEXEC return __fwd_env(STDEXEC::get_env(__child)); }; - template + template static consteval auto __get_completion_signatures() { - static_assert(__sender_for<_Sender, _LetTag>); - if constexpr (__decay_copyable<_Sender>) + static_assert(__sender_for<_CvSndr, _LetTag>); + using __fn_t = __impls::__fn_t<_CvSndr>; + using __child_t = __child_of<_CvSndr>; + auto __completions = STDEXEC::get_completion_signatures<__child_t, _Env...>(); + auto __transform_fn = []() { - // TODO: update this to use constant evaluation - using __result_t = - __completions_t<_LetTag, __fn_t<_Sender>, __child_of<_Sender>, _Env...>; - if constexpr (__ok<__result_t>) + if constexpr (!__invocable<__fn_t, __decay_t<_Args>&...>) { - return __result_t(); + using __what_t = __callable_error_t<_LetTag, __fn_t, __decay_t<_Args>&...>; + return STDEXEC::__throw_compile_time_error(__what_t()); + } + else if constexpr (!__decay_copyable<_Args...>) + { + using __what_t = __not_decay_copyable_error_t<_Args...>; + return STDEXEC::__throw_compile_time_error(__what_t()); + } + else if constexpr (!__potentially_valid_sender_in< + __invoke_result_t<__fn_t, __decay_t<_Args>&...>, + __result_env_t<__set_t, __child_t, _Env>...>) + { + using __sndr_t = __invoke_result_t<__fn_t, __decay_t<_Args>&...>; + using __what_t = + __bad_result_sender<__sndr_t, _LetTag, __result_env_t<__set_t, __child_t, _Env>...>; + return STDEXEC::__throw_compile_time_error(__what_t()); } else { - return STDEXEC::__throw_compile_time_error(__result_t()); + using __sndr_t = __invoke_result_t<__fn_t, __decay_t<_Args>&...>; + using __nothrow_t = __mbool<__nothrow_invocable<__fn_t, __decay_t<_Args>&...> + && __nothrow_decay_copyable<_Args...>>; + + STDEXEC_COMPLSIGS_LET( + __sigs, + STDEXEC::__concat_completion_signatures( + STDEXEC::get_completion_signatures<__sndr_t, + __result_env_t<__set_t, __child_t, _Env>...>(), + __eptr_completion_unless_t<__nothrow_t>())) + { + if constexpr (__sigs.template __contains()) + { + return __sigs; + } + else if constexpr (sizeof...(_Env) == 0) + { + return STDEXEC::__dependent_sender<__sndr_t>(); + } + else + { + using __env_t = __mfront<__result_env_t<__set_t, __child_t, _Env>...>; + using __rcvr_t = __receiver_archetype<__env_t>; + using __nothrow_t = __mbool<__nothrow_connectable<__sndr_t, __rcvr_t>>; + using __eptr_t = __eptr_completion_unless_t<__nothrow_t>; + + return STDEXEC::__concat_completion_signatures(__sigs, __eptr_t()); + } + } } + }; + + if constexpr (!__decay_copyable<_CvSndr>) + { + return STDEXEC::__throw_compile_time_error<_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_, + _WITH_PRETTY_SENDER_<_CvSndr>>(); + } + else if constexpr (__same_as<_LetTag, let_value_t>) + { + return STDEXEC::__transform_completion_signatures(__completions, __transform_fn); + } + else if constexpr (__same_as<_LetTag, let_error_t>) + { + return STDEXEC::__transform_completion_signatures(__completions, {}, __transform_fn); } else { - return STDEXEC::__throw_compile_time_error<_SENDER_TYPE_IS_NOT_DECAY_COPYABLE_, - _WITH_PRETTY_SENDER_<_Sender>>(); + return STDEXEC::__transform_completion_signatures(__completions, {}, {}, __transform_fn); } } @@ -701,14 +639,17 @@ namespace STDEXEC struct let_value_t : __let::__let_t { + 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; let_stopped_t() = default; }; diff --git a/include/stdexec/__detail/__meta.hpp b/include/stdexec/__detail/__meta.hpp index 8f45045ef..9623bfcbb 100644 --- a/include/stdexec/__detail/__meta.hpp +++ b/include/stdexec/__detail/__meta.hpp @@ -139,16 +139,24 @@ namespace STDEXEC #else namespace __pack { + template > + extern int __mk_indices; + template - auto __mk_indices(__indices<0, _Is...>) -> __indices<_Is...>; + extern __indices<_Is...> __mk_indices<0, __indices<_Is...>>; + + template + extern decltype(__mk_indices<_Np - 1, __indices<0, (_Is + 1)...>>) + __mk_indices<_Np, __indices<_Is...>>; - template - auto __mk_indices(__indices<_Np, _Is...>) - -> decltype(__mk_indices(__indices<_Np - 1, 0, (_Is + 1)...>{})); + template + requires(_Np >= 4) + extern decltype(__mk_indices<_Np - 4, __indices<0, 1, 2, 3, (_Is + 4)...>>) + __mk_indices<_Np, __indices<_Is...>>; } // namespace __pack template - using __make_indices = decltype(__pack::__mk_indices(__indices<_Np>{})); + using __make_indices = decltype(__pack::__mk_indices<_Np>); #endif template @@ -1062,7 +1070,7 @@ namespace STDEXEC auto operator+(__inherit<_Set...> &) -> __inherit<_Set...>; template - auto operator%(__inherit<_Set...> &, __mtype<_Ty> &) -> __inherit<_Ty, _Set...> &; + auto operator%(__inherit<_Set...> &, __mtype<_Ty> &) -> __inherit<_Set..., _Ty> &; template requires __mset_contains<__inherit<_Set...>, _Ty> @@ -1095,10 +1103,7 @@ namespace STDEXEC template > struct __munique { -#if STDEXEC_HAS_BUILTIN(__builtin_dedup_pack) && STDEXEC_HAS_BUILTIN(__builtin_sort_pack) - template - using __f = __minvoke<_Continuation, __builtin_sort_pack<__builtin_dedup_pack<_Ts...>...>...>; -#elif STDEXEC_HAS_BUILTIN(__builtin_dedup_pack) +#if STDEXEC_HAS_BUILTIN(__builtin_dedup_pack) template using __f = __minvoke<_Continuation, __builtin_dedup_pack<_Ts...>...>; #else diff --git a/include/stdexec/__detail/__schedulers.hpp b/include/stdexec/__detail/__schedulers.hpp index 9e6d5b9c6..f9a503e8d 100644 --- a/include/stdexec/__detail/__schedulers.hpp +++ b/include/stdexec/__detail/__schedulers.hpp @@ -527,15 +527,17 @@ namespace STDEXEC template struct __mk_secondary_env_t { + template + using __impl_t = + __minvoke<__mremove_if<__mbind_back_q<__never_sends_t, _Sender, __fwd_env_t<_Env const &>>, + __qq<__detail::__mk_secondary_env_impl>>, + _SetTags...>; + template constexpr auto operator()(_CvFn __cv, _Sender const &__sndr, _Env const &__env) const noexcept + -> __call_result_t<__impl_t<_Sender, _Env>, _CvFn, _Sender const &, _Env const &> { - using namespace __detail; - using __env_t = __fwd_env_t<_Env const &>; - 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); + return __impl_t<_Sender, _Env>()(__cv, __sndr, __env); } }; diff --git a/include/stdexec/__detail/__sender_concepts.hpp b/include/stdexec/__detail/__sender_concepts.hpp index d86d7d9b9..d2407831d 100644 --- a/include/stdexec/__detail/__sender_concepts.hpp +++ b/include/stdexec/__detail/__sender_concepts.hpp @@ -55,13 +55,14 @@ namespace STDEXEC && __std::constructible_from<__decay_t<_Sender>, _Sender>; template - concept __constant_completion_signatures = __valid_completion_signatures; + inline constexpr bool __constant_completion_signatures_v = + __valid_completion_signatures; template concept sender_in = (sizeof...(_Env) <= 1) // && sender<_Sender> // - && __constant_completion_signatures()>; + && __constant_completion_signatures_v()>; template concept __receiver_from = diff --git a/include/stdexec/__detail/__submit.hpp b/include/stdexec/__detail/__submit.hpp index c034f5148..d8ea3d80a 100644 --- a/include/stdexec/__detail/__submit.hpp +++ b/include/stdexec/__detail/__submit.hpp @@ -145,7 +145,7 @@ namespace STDEXEC template ()> - // requires sender_to<_Sender, _Receiver> + requires sender_to<_Sender, _Receiver> struct submit_result { using __result_t = connect_result_t<_Sender, _Receiver>; diff --git a/include/stdexec/__detail/__task.hpp b/include/stdexec/__detail/__task.hpp index a200cd999..ac9c1fce8 100644 --- a/include/stdexec/__detail/__task.hpp +++ b/include/stdexec/__detail/__task.hpp @@ -824,7 +824,7 @@ namespace STDEXEC template <__forwarding_query _Query, class... _Args> requires __queryable_with<_TaskEnv, _Query, _Args...> [[nodiscard]] - constexpr auto query(_Query __tag, _Args&&... __args) const + constexpr auto query(_Query, _Args&&... __args) const noexcept(__nothrow_queryable_with<_TaskEnv, _Query, _Args...>) -> __query_result_t<_TaskEnv, _Query, _Args...> { diff --git a/test/exec/test_fork_join.cpp b/test/exec/test_fork_join.cpp index 61e2323d9..391e16ea9 100644 --- a/test/exec/test_fork_join.cpp +++ b/test/exec/test_fork_join.cpp @@ -195,11 +195,7 @@ namespace ::STDEXEC::then([&witness](int const received) noexcept { witness += received; })); - static_assert(requires { - { - ::STDEXEC::get_completion_signatures(sndr) - } -> std::derived_from<::STDEXEC::dependent_sender_error>; - }); + static_assert(::STDEXEC::dependent_sender); ::STDEXEC::sync_wait(std::move(sndr)); From 0e615bc57b8b26b3c0b65153f6f3afa392abb1bb Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 15 Apr 2026 10:36:53 -0700 Subject: [PATCH 2/4] work around gcc-12 bug --- include/stdexec/__detail/__sender_concepts.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/stdexec/__detail/__sender_concepts.hpp b/include/stdexec/__detail/__sender_concepts.hpp index d2407831d..8e1e1644e 100644 --- a/include/stdexec/__detail/__sender_concepts.hpp +++ b/include/stdexec/__detail/__sender_concepts.hpp @@ -54,9 +54,15 @@ namespace STDEXEC && __std::move_constructible<__decay_t<_Sender>> && __std::constructible_from<__decay_t<_Sender>, _Sender>; +#if STDEXEC_GCC() && STDEXEC_GCC_VERSION < 1300 + template + inline constexpr bool __constant_completion_signatures_v = + __valid_completion_signatures>; +#else template inline constexpr bool __constant_completion_signatures_v = __valid_completion_signatures; +#endif template concept sender_in = From 9a0e07e3d2078f37ea097a64e765a807bb7beb63 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 15 Apr 2026 10:43:48 -0700 Subject: [PATCH 3/4] fix CUDA build --- include/nvexec/stream/let_xxx.cuh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/nvexec/stream/let_xxx.cuh b/include/nvexec/stream/let_xxx.cuh index 7bf837f8b..0d31aa6c2 100644 --- a/include/nvexec/stream/let_xxx.cuh +++ b/include/nvexec/stream/let_xxx.cuh @@ -306,34 +306,35 @@ namespace nv::execution::_strm STDEXEC_HOST_DEVICE_DEDUCTION_GUIDE let_sender(Sender, Fun, SetTag) -> let_sender; - template + template struct _transform_let_sender { + using _set_tag = __t; + template auto operator()(Env const &, __ignore, Fun fn, Sender&& sndr) const { if constexpr (stream_completing_sender) { - return let_sender{static_cast(sndr), static_cast(fn), SetTag{}}; + return let_sender{static_cast(sndr), static_cast(fn), _set_tag{}}; } else { - using _let_t = decltype(STDEXEC::__let::__let_from_set); - return _strm::_no_stream_scheduler_in_env<_let_t, Sender, Env>(); + return _strm::_no_stream_scheduler_in_env(); } } }; template <> - struct transform_sender_for : _transform_let_sender + struct transform_sender_for : _transform_let_sender {}; template <> - struct transform_sender_for : _transform_let_sender + struct transform_sender_for : _transform_let_sender {}; template <> - struct transform_sender_for : _transform_let_sender + struct transform_sender_for : _transform_let_sender {}; } // namespace nv::execution::_strm From f67c1a02897ef1746f245714650b167008d0aeba Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 15 Apr 2026 10:45:38 -0700 Subject: [PATCH 4/4] format --- include/nvexec/stream/let_xxx.cuh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/nvexec/stream/let_xxx.cuh b/include/nvexec/stream/let_xxx.cuh index 0d31aa6c2..4d7e3b940 100644 --- a/include/nvexec/stream/let_xxx.cuh +++ b/include/nvexec/stream/let_xxx.cuh @@ -334,7 +334,8 @@ namespace nv::execution::_strm {}; template <> - struct transform_sender_for : _transform_let_sender + struct transform_sender_for + : _transform_let_sender {}; } // namespace nv::execution::_strm