From f5d7bccfa25d401535dfa70ed1dc08d8a3a53fd3 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Fri, 6 Mar 2026 23:07:01 -0800 Subject: [PATCH 1/2] really fix the perf regression in the maxwell_gpu_s example --- examples/nvexec/maxwell/snr.cuh | 77 ++++--- include/exec/detail/basic_sequence.hpp | 3 + include/exec/static_thread_pool.hpp | 9 + include/nvexec/stream/common.cuh | 20 +- include/nvexec/stream/schedule_from.cuh | 6 +- include/stdexec/__detail/__config.hpp | 10 +- include/stdexec/__detail/__preprocessor.hpp | 12 +- .../__detail/__sender_introspection.hpp | 201 +++++++++++++----- 8 files changed, 228 insertions(+), 110 deletions(-) diff --git a/examples/nvexec/maxwell/snr.cuh b/examples/nvexec/maxwell/snr.cuh index 5b2aec3d6..dc4e13808 100644 --- a/examples/nvexec/maxwell/snr.cuh +++ b/examples/nvexec/maxwell/snr.cuh @@ -60,19 +60,19 @@ STDEXEC_PRAGMA_IGNORE_GNU("-Wmissing-braces") namespace ex = stdexec; -namespace repeat_n_detail +namespace _repeat_n { template - struct repeat_n_sender_t; -} // namespace repeat_n_detail + struct sender; +} // namespace _repeat_n struct repeat_n_t { template auto operator()(Sender __sndr, std::size_t n, Closure closure) const noexcept - -> repeat_n_detail::repeat_n_sender_t + -> _repeat_n::sender { - return repeat_n_detail::repeat_n_sender_t{ + return _repeat_n::sender{ {}, {closure, n}, std::move(__sndr) @@ -88,9 +88,8 @@ struct repeat_n_t inline constexpr repeat_n_t repeat_n{}; -namespace repeat_n_detail +namespace _repeat_n { - template class receiver_2_t { @@ -245,11 +244,11 @@ namespace repeat_n_detail }; template - struct repeat_n_sender_t + struct sender { using sender_concept = ex::sender_t; - template + template static consteval auto get_completion_signatures() noexcept { return ex::completion_signatures< @@ -260,11 +259,11 @@ namespace repeat_n_detail >(); } - template Self, ex::receiver Receiver> + template Self, ex::receiver Receiver> STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this Self&& self, Receiver r) - -> repeat_n_detail::operation_state_t + -> _repeat_n::operation_state_t { - return repeat_n_detail::operation_state_t( + return _repeat_n::operation_state_t( static_cast(self).sender_, static_cast(self).data_.first, static_cast(r), @@ -283,26 +282,25 @@ namespace repeat_n_detail std::pair data_; Sender sender_; }; -} // namespace repeat_n_detail +} // namespace _repeat_n namespace STDEXEC { template - inline constexpr std::size_t - __structured_binding_size_v> = 3; + inline constexpr std::size_t __structured_binding_size_v<_repeat_n::sender> = 3; } // namespace STDEXEC #if STDEXEC_CUDA_COMPILATION() // A CUDA stream implementation of repeat_n namespace nv::execution::_strm { - namespace repeat_n + namespace _repeat_n { template class receiver_2_t : public stream_receiver_base { - using Sender = OpT::PredSender; - using Receiver = OpT::Receiver; + using Sender = OpT::child_t; + using Receiver = OpT::receiver_t; OpT& op_state_; @@ -353,11 +351,15 @@ namespace nv::execution::_strm template class receiver_1_t : public stream_receiver_base { - using Receiver = OpT::Receiver; + using Receiver = OpT::receiver_t; OpT& op_state_; public: + explicit receiver_1_t(OpT& op_state) + : op_state_(op_state) + {} + void set_value() noexcept { using inner_op_state_t = OpT::inner_op_state_t; @@ -394,15 +396,13 @@ namespace nv::execution::_strm { return op_state_.make_env(); } - - explicit receiver_1_t(OpT& op_state) - : op_state_(op_state) - {} }; template struct operation_state_t : _strm::opstate_base { + using receiver_t = Receiver; + using child_t = PredSender; using Scheduler = std::invoke_result_t, ex::env_of_t, ex::env_of_t>; @@ -445,7 +445,7 @@ namespace nv::execution::_strm static_cast(rcvr), ex::get_completion_scheduler(ex::get_env(pred_sender), ex::get_env(rcvr)) - .context_state_) + .ctx_) , scheduler_(ex::get_completion_scheduler(ex::get_env(pred_sender), ex::get_env(rcvr))) , closure_(closure) @@ -458,7 +458,7 @@ namespace nv::execution::_strm }; template - struct sender_t + struct sender { using sender_concept = ex::sender_t; @@ -467,12 +467,12 @@ namespace nv::execution::_strm ex::set_error_t(std::exception_ptr), ex::set_error_t(cudaError_t)>; - template Self, ex::receiver Receiver> + template Self, ex::receiver Receiver> requires(ex::sender_to) STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this Self&& self, Receiver r) - -> nvexec::_strm::repeat_n::operation_state_t + -> nvexec::_strm::_repeat_n::operation_state_t { - return nvexec::_strm::repeat_n::operation_state_t( + return nvexec::_strm::_repeat_n::operation_state_t( static_cast(self).sender_, static_cast(self).closure_, static_cast(r), @@ -490,21 +490,20 @@ namespace nv::execution::_strm Closure closure_; std::size_t n_{}; }; - } // namespace repeat_n + } // namespace _repeat_n template <> - struct transform_sender_for + struct transform_sender_for<::repeat_n_t> { template - auto operator()(Env const &, ex::__ignore, Data&& data, Sender sndr) const + auto operator()(Env const &, ::repeat_n_t, Data&& data, Sender sndr) const { - static_assert(sizeof(Env) == 0); - auto& [closure, n] = data; - using closure_t = decltype(closure); + auto& [closure, count] = data; + using closure_t = decltype(closure); - return repeat_n::sender_t(static_cast(sndr), - ex::__forward_like(closure), - n); + return _strm::_repeat_n::sender(static_cast(sndr), + ex::__forward_like(closure), + count); } }; } // namespace nv::execution::_strm @@ -528,8 +527,8 @@ auto maxwell_eqs_snr(float dt, ex::scheduler auto&& computer) { return ex::just() - | repeat_n(n_iterations, - ex::on(computer, + | ex::on(computer, + repeat_n(n_iterations, ex::bulk(ex::par, accessor.cells, update_h(accessor)) | ex::bulk(ex::par, accessor.cells, update_e(time, dt, accessor)))) | ex::then(dump_vtk(write_results, accessor)); diff --git a/include/exec/detail/basic_sequence.hpp b/include/exec/detail/basic_sequence.hpp index 1054bac03..cb0c06503 100644 --- a/include/exec/detail/basic_sequence.hpp +++ b/include/exec/detail/basic_sequence.hpp @@ -113,6 +113,9 @@ namespace exec = experimental::execution; namespace STDEXEC::__detail { + template + extern __result_of<_DescriptorFn> __desc_of_v>; + template extern __declfn_t<__minvoke<__result_of<_DescriptorFn>, __q>> __demangle_v>; diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index 9bc475370..ab288afb8 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -1281,6 +1281,14 @@ namespace experimental::execution using _bulk_opstate_t = _static_thread_pool::_bulk_opstate; + explicit _bulk_sender(_static_thread_pool& pool, Sender sndr, Shape shape, Fun fun) + noexcept(__nothrow_move_constructible) + : pool_(pool) + , sndr_(static_cast(sndr)) + , shape_(shape) + , fun_(static_cast(fun)) + {} + template <__decays_to<_bulk_sender> Self, receiver Receiver> requires receiver_of>> STDEXEC_EXPLICIT_THIS_BEGIN(auto connect)(this Self&& self, Receiver rcvr) @@ -1310,6 +1318,7 @@ namespace experimental::execution return STDEXEC::get_env(sndr_); } + private: _static_thread_pool& pool_; Sender sndr_; Shape shape_; diff --git a/include/nvexec/stream/common.cuh b/include/nvexec/stream/common.cuh index eb3b04425..ed014a478 100644 --- a/include/nvexec/stream/common.cuh +++ b/include/nvexec/stream/common.cuh @@ -98,10 +98,20 @@ namespace nv::execution struct stream_domain : STDEXEC::default_domain { template <::exec::sender_for Sender, class Tag = STDEXEC::tag_of_t, class Env> - requires STDEXEC::__applicable<_strm::transform_sender_for, Sender, Env const &> + requires STDEXEC::__callable, + Sender, + Env const &> static auto transform_sender(STDEXEC::set_value_t, Sender&& sndr, Env const & env) + noexcept(STDEXEC::__nothrow_callable, + Sender, + Env const &>) + { - return STDEXEC::__apply(_strm::transform_sender_for{}, static_cast(sndr), env); + return STDEXEC::__structured_apply(_strm::transform_sender_for{}, + static_cast(sndr), + env); } template @@ -115,7 +125,6 @@ namespace nv::execution namespace _strm { - #if STDEXEC_HAS_BUILTIN(__is_reference) template concept trivially_copyable = ((STDEXEC_IS_TRIVIALLY_COPYABLE(Ts) || __is_reference(Ts)) && ...); @@ -701,8 +710,9 @@ namespace nv::execution else { // pass a cudaError_t by value: - continuation_kernel - <<<1, 1, 0, get_stream()>>>(static_cast(rcvr_), set_error_t(), status); + continuation_kernel<<<1, 1, 0, get_stream()>>>(static_cast(rcvr_), + set_error_t(), + cudaError_t(status)); } } diff --git a/include/nvexec/stream/schedule_from.cuh b/include/nvexec/stream/schedule_from.cuh index d189e25e4..10e23a29e 100644 --- a/include/nvexec/stream/schedule_from.cuh +++ b/include/nvexec/stream/schedule_from.cuh @@ -38,8 +38,8 @@ namespace nv::execution template struct opstate : _strm::opstate_base { - using env_t = _strm::opstate_base::env_t; struct receiver; + using env_t = _strm::opstate_base::env_t; using variant_t = variant_storage_t; using task_t = continuation_task; using enqueue_receiver_t = stream_enqueue_receiver; @@ -75,7 +75,7 @@ namespace nv::execution opstate& opstate_; }; - opstate(Sender&& sender, Receiver&& rcvr, context ctx) + opstate(Sender&& sndr, Receiver&& rcvr, context ctx) : _strm::opstate_base(static_cast(rcvr), ctx) , ctx_(ctx) , storage_(host_allocate(this->status_, ctx.pinned_resource_)) @@ -88,7 +88,7 @@ namespace nv::execution .release()) , env_(host_allocate(this->status_, ctx_.pinned_resource_, this->make_env())) , inner_op_{ - connect(static_cast(sender), + connect(static_cast(sndr), enqueue_receiver_t{env_.get(), storage_.get(), task_, ctx_.hub_->producer()})} { if (this->status_ == cudaSuccess) diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 11be7aafc..ecc6f8848 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -801,16 +801,16 @@ namespace STDEXEC _NAME)) # define STDEXEC_EXPLICIT_THIS_EAT_this -# define STDEXEC_EXPLICIT_THIS_MANGLE_auto auto STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN -# define STDEXEC_EXPLICIT_THIS_MANGLE_void void STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN -# define STDEXEC_EXPLICIT_THIS_MANGLE_bool bool STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN +# define STDEXEC_EXPLICIT_THIS_MANGLE_auto auto STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN() +# define STDEXEC_EXPLICIT_THIS_MANGLE_void void STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN() +# define STDEXEC_EXPLICIT_THIS_MANGLE_bool bool STDEXEC_EXPLICIT_THIS_MANGLE STDEXEC_PP_LPAREN() # define STDEXEC_EXPLICIT_THIS_ARGS(...) \ - STDEXEC_PP_CAT(STDEXEC_EXPLICIT_THIS_EAT_, __VA_ARGS__) STDEXEC_PP_RPAREN + STDEXEC_PP_CAT(STDEXEC_EXPLICIT_THIS_EAT_, __VA_ARGS__) STDEXEC_PP_RPAREN() # define STDEXEC_EXPLICIT_THIS_BEGIN(...) \ static STDEXEC_PP_EXPAND(STDEXEC_PP_CAT(STDEXEC_EXPLICIT_THIS_MANGLE_, __VA_ARGS__) \ - STDEXEC_PP_RPAREN) STDEXEC_PP_LPAREN STDEXEC_EXPLICIT_THIS_ARGS + STDEXEC_PP_RPAREN()) STDEXEC_PP_LPAREN() STDEXEC_EXPLICIT_THIS_ARGS # define STDEXEC_EXPLICIT_THIS_END(_NAME) \ template \ diff --git a/include/stdexec/__detail/__preprocessor.hpp b/include/stdexec/__detail/__preprocessor.hpp index fb19d2c87..04f4b7802 100644 --- a/include/stdexec/__detail/__preprocessor.hpp +++ b/include/stdexec/__detail/__preprocessor.hpp @@ -18,10 +18,10 @@ #define STDEXEC_PP_STRINGIZE_I(...) #__VA_ARGS__ #define STDEXEC_PP_STRINGIZE(...) STDEXEC_PP_STRINGIZE_I(__VA_ARGS__) -#define STDEXEC_PP_LPAREN ( -#define STDEXEC_PP_RPAREN ) -#define STDEXEC_PP_PARENS () -#define STDEXEC_PP_COMMA , +#define STDEXEC_PP_LPAREN() ( +#define STDEXEC_PP_RPAREN() ) +#define STDEXEC_PP_PARENS() () +#define STDEXEC_PP_COMMA() , #define STDEXEC_PP_CAT_I(_XP, ...) _XP##__VA_ARGS__ #define STDEXEC_PP_CAT(_XP, ...) STDEXEC_PP_CAT_I(_XP, __VA_ARGS__) @@ -83,7 +83,7 @@ #define STDEXEC_PP_FOR_EACH_AGAIN() STDEXEC_PP_FOR_EACH_HELPER #define STDEXEC_PP_FOR_EACH_HELPER(_MACRO, _A1, ...) \ - _MACRO(_A1) __VA_OPT__(STDEXEC_PP_FOR_EACH_AGAIN STDEXEC_PP_PARENS(_MACRO, __VA_ARGS__)) /**/ + _MACRO(_A1) __VA_OPT__(STDEXEC_PP_FOR_EACH_AGAIN STDEXEC_PP_PARENS()(_MACRO, __VA_ARGS__)) /**/ #define STDEXEC_PP_FOR_EACH(_MACRO, ...) \ __VA_OPT__(STDEXEC_PP_EXPAND_R(STDEXEC_PP_FOR_EACH_HELPER(_MACRO, __VA_ARGS__))) @@ -94,7 +94,7 @@ #define STDEXEC_PP_BACK_AGAIN() STDEXEC_PP_BACK_I #define STDEXEC_PP_BACK_I(_A1, ...) \ STDEXEC_PP_FRONT(__VA_OPT__(, ) _A1, ) \ - __VA_OPT__(STDEXEC_PP_BACK_AGAIN STDEXEC_PP_PARENS(__VA_ARGS__)) + __VA_OPT__(STDEXEC_PP_BACK_AGAIN STDEXEC_PP_PARENS()(__VA_ARGS__)) #define STDEXEC_PP_BACK(...) __VA_OPT__(STDEXEC_PP_EXPAND_R(STDEXEC_PP_BACK_I(__VA_ARGS__))) #define STDEXEC_PP_TAIL(_IGN, ...) __VA_ARGS__ diff --git a/include/stdexec/__detail/__sender_introspection.hpp b/include/stdexec/__detail/__sender_introspection.hpp index 93b02572c..be4afa86c 100644 --- a/include/stdexec/__detail/__sender_introspection.hpp +++ b/include/stdexec/__detail/__sender_introspection.hpp @@ -18,12 +18,15 @@ #include "__execution_fwd.hpp" #include "__meta.hpp" #include "__sender_concepts.hpp" -#include "__tuple.hpp" +#include "__tuple.hpp" // IWYU pragma: keep for __is_tuple and __tuple_size_v #include "__type_traits.hpp" #include #include // IWYU pragma: keep for std::terminate +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wc++26-extensions") + namespace STDEXEC { namespace @@ -50,6 +53,15 @@ namespace STDEXEC using __f = __minvoke<_Fn, _Args..., _Tag, _Data, _Child...>; }; + namespace __detail + { + template + auto __std_tuple_sizer(std::tuple<_Ts...> const &) -> __msize_t; + } // namespace __detail + + template + concept __is_std_tuple = requires(_Tuple &&__arg) { __detail::__std_tuple_sizer(__arg); }; + template inline constexpr int __structured_binding_size_v = -1; @@ -60,71 +72,148 @@ namespace STDEXEC #else template <__is_tuple _Ty> inline constexpr int __structured_binding_size_v<_Ty> = __tuple_size_v<_Ty>; + + template <__is_std_tuple _Ty> + inline constexpr int __structured_binding_size_v<_Ty> = decltype(__detail::__std_tuple_sizer( + __declval<_Ty>()))::value; + // For types that are *not* tuples, __structured_binding_size_v must be specialized // explicitly. #endif namespace __detail { - template - auto __get_desc_impl(__tuple<_Tag, _Data, _Child...> &&) -> __desc<_Tag, _Data, _Child...>; + template + struct __static_const + { + using type = decltype(_Apply); + static constexpr type value = _Apply; + }; -// Can structured bindings introduce a pack? #if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 2024'11L - STDEXEC_PRAGMA_PUSH() - STDEXEC_PRAGMA_IGNORE_GNU("-Wc++26-extensions") - template - requires(!__is_tuple<_Sender>) - auto __get_desc_impl(_Sender &&__sndr) + // Structured bindings can introduce a pack, so the implementation of + // __structured_apply is simple. + template + inline constexpr auto __structured_apply_impl = + [](_Fn &&__fn, _Type &&__obj, _Us &&...__us) // + noexcept(_Nothrow) -> decltype(auto) { - auto &&[__tag, __data, ... __child] = __sndr; - return __desc{}; - } + using __cpcv = __copy_cvref_fn<_Type>; + auto &[... __as] = __obj; + return static_cast<_Fn &&>(__fn)(static_cast<_Us &&>(__us)..., + static_cast<__mcall1<__cpcv, decltype(__as)> &&>(__as)...); + }; + + template + inline constexpr auto const &__structured_apply_v = __structured_apply_impl<_Nothrow>; - STDEXEC_PRAGMA_POP() #else - template - extern __undefined<__msize_t<_Arity>> &__get_desc_impl_v; - -# define STDEXEC_GET_DESC_IMPL_CHILD(_NY) , __child##_NY -# define STDEXEC_GET_DESC_IMPL_CHILD_TYPE(_NY) , decltype(__child##_NY) -# define STDEXEC_GET_DESC_IMPL_ITERATE(_IDX) \ - template <> \ - inline constexpr auto __get_desc_impl_v<_IDX> = [](_Sender &&__sndr) { \ - auto &&[__tag, __data STDEXEC_PP_REPEAT(_IDX, STDEXEC_GET_DESC_IMPL_CHILD)] = __sndr; \ - return __desc< \ - decltype(__tag), \ - decltype(__data) STDEXEC_PP_REPEAT(_IDX, STDEXEC_GET_DESC_IMPL_CHILD_TYPE) \ - >{}; \ - } - STDEXEC_GET_DESC_IMPL_ITERATE(0); - STDEXEC_GET_DESC_IMPL_ITERATE(1); - STDEXEC_GET_DESC_IMPL_ITERATE(2); - STDEXEC_GET_DESC_IMPL_ITERATE(3); - STDEXEC_GET_DESC_IMPL_ITERATE(4); - STDEXEC_GET_DESC_IMPL_ITERATE(5); - STDEXEC_GET_DESC_IMPL_ITERATE(6); - STDEXEC_GET_DESC_IMPL_ITERATE(7); - STDEXEC_GET_DESC_IMPL_ITERATE(8); - STDEXEC_GET_DESC_IMPL_ITERATE(9); - STDEXEC_GET_DESC_IMPL_ITERATE(10); -# undef STDEXEC_GET_DESC_IMPL_CHILD -# undef STDEXEC_GET_DESC_IMPL_CHILD_TYPE -# undef STDEXEC_GET_DESC_IMPL_ITERATE - template - requires(!__is_tuple<_Sender>) - auto __get_desc_impl(_Sender &&__sndr) + // Structured bindings *cannot* introduce a pack, so we explicitly handle structures + // with up to 10 members. + template + extern __undefined<__msize_t<_Ny>> __structured_apply_v; + + template + inline constexpr auto const &__structured_apply_v<0, _Nothrow> = __static_const<( // + [](_Fn &&__fn, __ignore, _Us &&...__us) noexcept(_Nothrow) // + -> decltype(auto) // + { // + return static_cast<_Fn &&>(__fn)(static_cast<_Us &&>(__us)...); // + })>::value; // + +# define STDEXEC_STRUCTURED_APPLY_ELEM_ID(_NY) STDEXEC_PP_IF(_NY, STDEXEC_PP_COMMA, STDEXEC_PP_EAT)() __a##_NY +# define STDEXEC_STRUCTURED_APPLY_ELEM(_NY) , static_cast<__mcall1<__cpcv, decltype(__a##_NY)> &&>(__a##_NY) +# define STDEXEC_STRUCTURED_APPLY_ITERATE(_IDX) \ + template \ + inline constexpr auto const& __structured_apply_v<_IDX, _Nothrow> = __static_const<( \ + [](_Fn &&__fn, _Type &&__obj, _Us &&...__us) \ + noexcept(_Nothrow) -> decltype(auto) \ + { \ + using __cpcv = __copy_cvref_fn<_Type>; \ + auto &[STDEXEC_PP_REPEAT(_IDX, STDEXEC_STRUCTURED_APPLY_ELEM_ID)] = __obj; \ + return static_cast<_Fn &&>(__fn)( \ + static_cast<_Us &&>(__us)... STDEXEC_PP_REPEAT(_IDX, STDEXEC_STRUCTURED_APPLY_ELEM)); \ + })>::value + + STDEXEC_STRUCTURED_APPLY_ITERATE(1); + STDEXEC_STRUCTURED_APPLY_ITERATE(2); + STDEXEC_STRUCTURED_APPLY_ITERATE(3); + STDEXEC_STRUCTURED_APPLY_ITERATE(4); + STDEXEC_STRUCTURED_APPLY_ITERATE(5); + STDEXEC_STRUCTURED_APPLY_ITERATE(6); + STDEXEC_STRUCTURED_APPLY_ITERATE(7); + STDEXEC_STRUCTURED_APPLY_ITERATE(8); + STDEXEC_STRUCTURED_APPLY_ITERATE(9); + STDEXEC_STRUCTURED_APPLY_ITERATE(10); +# undef STDEXEC_STRUCTURED_APPLY_ELEM +# undef STDEXEC_STRUCTURED_APPLY_ELEM_ID +# undef STDEXEC_STRUCTURED_APPLY_ITERATE + +#endif + + struct __structured_apply { - using __desc_t = decltype(__get_desc_impl_v<__structured_binding_size_v<_Sender> - 2>( - __sndr)); - return __desc_t{}; - } + private: + template + struct __get_declfn + { + template + constexpr auto operator()(_As &&...) const noexcept + { + if constexpr (__callable<_Fn, _As...>) + { + return __declfn<__call_result_t<_Fn, _As...>, __nothrow_callable<_Fn, _As...>>(); + } + } + }; + +#if STDEXEC_EDG() + template + static auto __declfn_fn() // + -> decltype((__detail::__structured_apply_v<__structured_binding_size_v<_Type>>) ( // + __get_declfn<_Fn>{}, + __declval<_Type>(), + __declval<_Us>()...)); + + template + requires(__structured_binding_size_v<_Type> >= 0) + using __declfn_t = decltype(__declfn_fn<_Fn, _Type, _Us...>()); +#else + template + requires(__structured_binding_size_v<_Type> >= 0) + using __declfn_t = __result_of<__structured_apply_v<__structured_binding_size_v<_Type>>, + __get_declfn<_Fn>, + _Type, + _Us...>; #endif + public: + template > + requires __callable<_DeclFn> + constexpr auto operator()(_Fn &&__fn, _Type &&__obj, _Us &&...__us) const + noexcept(__nothrow_callable<_DeclFn>) -> __call_result_t<_DeclFn> + { + return (__structured_apply_v<__structured_binding_size_v<_Type>, + __nothrow_callable<_DeclFn>>) (static_cast<_Fn &&>(__fn), + static_cast<_Type &&>(__obj), + static_cast<_Us &&>(__us)...); + } + }; + + struct __get_desc + { + template + constexpr auto operator()(_Tag, _Data const &, _Child const &...) const noexcept + -> __desc<_Tag, _Data, _Child...> + { + return __desc<_Tag, _Data, _Child...>{}; + } + }; + template - using __desc_of_t = decltype(__detail::__get_desc_impl(__declval<_Sender>())); + using __desc_of_t = __call_result_t<__structured_apply, __get_desc, _Sender>; template extern __undefined<_Sender> &__desc_of_v; @@ -137,9 +226,14 @@ namespace STDEXEC extern decltype(_Descriptor()) __desc_of_v<__sexpr<_Descriptor>>; } // namespace __detail + using __structured_apply_t = __detail::__structured_apply; + inline constexpr __structured_apply_t __structured_apply{}; + template using __desc_of_t = decltype(__detail::__desc_of_v<__decay_t<_Sender>>()); + // NOT TO SPEC: in the specification, the tparam of tag_of_t is constrained with the + // sender concept template requires enable_sender<__decay_t<_Sender>> using tag_of_t = __desc_of_t<_Sender>::__tag; @@ -177,11 +271,14 @@ namespace STDEXEC && (__std::same_as, _Tag> && ...); template - concept sender_expr STDEXEC_DEPRECATE_CONCEPT("Please use sender_for from " - "instead") = __sender_for<_Sender>; + concept sender_expr + STDEXEC_DEPRECATE_CONCEPT("Please use exec::sender_for from " + " instead") = __sender_for<_Sender>; template concept sender_expr_for - STDEXEC_DEPRECATE_CONCEPT("Please use sender_for from " + STDEXEC_DEPRECATE_CONCEPT("Please use exec::sender_for from " " instead") = __sender_for<_Sender, _Tag>; } // namespace STDEXEC + +STDEXEC_PRAGMA_POP() From 95821d9d045a59e1dd75e89c142cf10ad7098713 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 7 Mar 2026 12:41:31 -0800 Subject: [PATCH 2/2] work around gcc bug --- include/exec/detail/basic_sequence.hpp | 4 ++-- include/stdexec/__detail/__basic_sender.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/exec/detail/basic_sequence.hpp b/include/exec/detail/basic_sequence.hpp index cb0c06503..ee6a8b12e 100644 --- a/include/exec/detail/basic_sequence.hpp +++ b/include/exec/detail/basic_sequence.hpp @@ -114,9 +114,9 @@ namespace exec = experimental::execution; namespace STDEXEC::__detail { template - extern __result_of<_DescriptorFn> __desc_of_v>; + extern decltype(_DescriptorFn()) __desc_of_v>; template - extern __declfn_t<__minvoke<__result_of<_DescriptorFn>, __q>> + extern __declfn_t<__minvoke>> __demangle_v>; } // namespace STDEXEC::__detail diff --git a/include/stdexec/__detail/__basic_sender.hpp b/include/stdexec/__detail/__basic_sender.hpp index e6c2fb2bb..109839535 100644 --- a/include/stdexec/__detail/__basic_sender.hpp +++ b/include/stdexec/__detail/__basic_sender.hpp @@ -447,7 +447,7 @@ namespace STDEXEC using __basic_sender_t = __basic_sender<_Tag, _Data, __demangle_t<_Child>...>::type; template - extern __declfn_t<__minvoke<__result_of<_Descriptor>, __q<__basic_sender_t>>> + extern __declfn_t<__minvoke>> __demangle_v<__sexpr<_Descriptor>>; } // namespace __detail } // namespace STDEXEC