From 85aaed454719e31c5a272d6929748a1c8fe4002f Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 25 Feb 2026 16:16:03 -0800 Subject: [PATCH] `__completion_signatures_of_t` requires that `get_completion_signatures()` is a constant expression --- include/exec/sequence_senders.hpp | 26 ++++----- include/stdexec/__detail/__concepts.hpp | 16 ++++++ include/stdexec/__detail/__diagnostics.hpp | 57 +++++++------------ .../__detail/__get_completion_signatures.hpp | 38 +++++++++++++ 4 files changed, 84 insertions(+), 53 deletions(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 86f101f54..f090e6f4f 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -124,7 +124,7 @@ namespace experimental::execution { template concept __has_set_next_member = requires(_Receiver& __rcvr, _Item&& __item) { - __rcvr.set_next(static_cast<_Item&&>(__item)); + __rcvr.set_next(static_cast<_Item &&>(__item)); }; // This is a sequence-receiver CPO that is used to apply algorithms on an input sender and it @@ -426,8 +426,8 @@ namespace experimental::execution template concept has_sequence_item_types = STDEXEC::sender_in<_Sequence, _Env...> // && requires(_Sequence&& __sequence, _Env&&... __env) { - get_item_types(static_cast<_Sequence&&>(__sequence), - static_cast<_Env&&>(__env)...); + get_item_types(static_cast<_Sequence &&>(__sequence), + static_cast<_Env &&>(__env)...); }; template @@ -518,8 +518,8 @@ namespace experimental::execution concept sequence_sender_in = sequence_sender<_Sequence, _Env...> && requires(_Sequence&& __sequence, _Env&&... __env) { { - get_item_types(static_cast<_Sequence&&>(__sequence), - static_cast<_Env&&>(__env)...) + get_item_types(static_cast<_Sequence &&>(__sequence), + static_cast<_Env &&>(__env)...) } -> __well_formed_item_types<_Sequence>; }; @@ -557,8 +557,7 @@ namespace experimental::execution #endif template - struct __sequence_type_check_failure // - : STDEXEC::__compile_time_error<__sequence_type_check_failure<_Data, _What...>> + struct __sequence_type_check_failure : STDEXEC::__compile_time_error { static_assert(std::is_nothrow_move_constructible_v<_Data>, "The data member of sender_type_check_failure must be nothrow move " @@ -570,16 +569,14 @@ namespace experimental::execution : __data_(static_cast<_Data&&>(data)) {} - private: - friend struct STDEXEC::__compile_time_error<__sequence_type_check_failure>; - [[nodiscard]] - constexpr auto what() const noexcept -> char const * + constexpr auto what() const noexcept -> char const * // NOLINT(modernize-use-override) { return "This sequence sender is not well-formed. It does not meet the requirements of a " "sequence sender type."; } + // public so that __sequence_type_check_failure is a structural type _Data __data_{}; }; @@ -845,10 +842,9 @@ namespace experimental::execution return STDEXEC::connect(static_cast&&>(__next), __stopped_means_break<_Receiver>{ static_cast<_Receiver&&>(__rcvr)}); - // NOLINTNEXTLINE(bugprone-branch-clone) } else if constexpr (__subscribable_with_static_member<__tfx_seq_t, _Receiver>) - { + { // NOLINT(bugprone-branch-clone) return __tfx_seq.subscribe(static_cast<__tfx_seq_t&&>(__tfx_seq), static_cast<_Receiver&&>(__rcvr)); } @@ -929,8 +925,8 @@ namespace experimental::execution template concept sequence_sender_to = sequence_receiver_from<_Receiver, _Sequence> && requires(_Sequence&& __sequence, _Receiver&& __rcvr) { - subscribe(static_cast<_Sequence&&>(__sequence), - static_cast<_Receiver&&>(__rcvr)); + subscribe(static_cast<_Sequence &&>(__sequence), + static_cast<_Receiver &&>(__rcvr)); }; template diff --git a/include/stdexec/__detail/__concepts.hpp b/include/stdexec/__detail/__concepts.hpp index ca54c325f..7b918478a 100644 --- a/include/stdexec/__detail/__concepts.hpp +++ b/include/stdexec/__detail/__concepts.hpp @@ -97,6 +97,22 @@ namespace STDEXEC template class _Ty> concept __is_not_instance_of = !__is_instance_of<_Ay, _Ty>; + template + concept __constant = true; + + namespace __detail + { + template + using __is_nttp = void; + template class> + using __nttp_test = void; + } // namespace __detail + + template + concept __structural = requires { typename __detail::__nttp_test<_Ty, __detail::__is_nttp>; }; + + static_assert(__structural); + namespace __std { diff --git a/include/stdexec/__detail/__diagnostics.hpp b/include/stdexec/__detail/__diagnostics.hpp index 5ac7b9971..1c222c612 100644 --- a/include/stdexec/__detail/__diagnostics.hpp +++ b/include/stdexec/__detail/__diagnostics.hpp @@ -144,13 +144,14 @@ namespace STDEXEC _WITH_PRETTY_SENDER_<_Sender>, _WITH_ENVIRONMENT_(_Env)...>; -#if __cpp_lib_constexpr_exceptions \ - >= 2025'02L // constexpr exception types, https://wg21.link/p3378 +#if __cpp_lib_constexpr_exceptions >= 2025'02L + // constexpr stdlib exception types, https://wg21.link/p3378 using __exception = ::std::exception; -#elif __cpp_constexpr >= 2024'11L // constexpr virtual functions +#elif __cpp_constexpr >= 2019'07L + // constexpr virtual functions struct __exception { constexpr __exception() noexcept = default; @@ -163,8 +164,9 @@ namespace STDEXEC } }; -#else // no constexpr virtual functions: +#else + // no constexpr virtual functions struct __exception { constexpr __exception() noexcept = default; @@ -176,23 +178,13 @@ namespace STDEXEC } }; -#endif // __cpp_lib_constexpr_exceptions >= 2025'02L +#endif - template struct __compile_time_error : __exception - { - constexpr __compile_time_error() = default; // NOLINT (bugprone-crtp-constructor-accessibility) - - [[nodiscard]] - constexpr auto what() const noexcept -> char const * - { - return static_cast<_Derived const *>(this)->what(); - } - }; + {}; template - struct __sender_type_check_failure // - : __compile_time_error<__sender_type_check_failure<_Data, _What...>> + struct __sender_type_check_failure : __compile_time_error { static_assert(std::is_nothrow_move_constructible_v<_Data>, "The data member of sender_type_check_failure must be nothrow move " @@ -204,34 +196,24 @@ namespace STDEXEC : __data_(static_cast<_Data &&>(data)) {} - private: - friend struct __compile_time_error<__sender_type_check_failure>; - [[nodiscard]] - constexpr auto what() const noexcept -> char const * + constexpr auto what() const noexcept -> char const * // NOLINT(modernize-use-override) { return "This sender is not well-formed. It does not meet the requirements of a sender type."; } + // public so that __sender_type_check_failure is a structural type _Data __data_{}; }; - struct dependent_sender_error : __compile_time_error + struct dependent_sender_error : __compile_time_error { - constexpr explicit dependent_sender_error(char const *what) noexcept - : what_(what) - {} - - private: - friend struct __compile_time_error; - [[nodiscard]] - constexpr auto what() const noexcept -> char const * + constexpr auto what() const noexcept -> char const * // NOLINT(modernize-use-override) { - return what_; + return "This sender needs to know its execution environment before it can " + "know how it will complete."; } - - char const *what_; }; // A specialization of _ERROR_ to be used to report dependent sender. It inherits @@ -257,11 +239,8 @@ namespace STDEXEC using __errors = _ERROR_; using __all = _ERROR_; - constexpr _ERROR_() noexcept - : dependent_sender_error{"This sender needs to know its execution environment before it can " - "know how it will " - "complete."} - {} + constexpr _ERROR_() = default; + constexpr ~_ERROR_() = default; STDEXEC_ATTRIBUTE(host, device) constexpr auto operator+() const -> _ERROR_; @@ -274,6 +253,8 @@ namespace STDEXEC constexpr auto operator,(const _ERROR_ &) const -> _ERROR_; }; + static_assert(__structural<_ERROR_>); + // By making __dependent_sender_error_t an alias for _ERROR_<...>, we ensure that // it will get propagated correctly through various metafunctions. template diff --git a/include/stdexec/__detail/__get_completion_signatures.hpp b/include/stdexec/__detail/__get_completion_signatures.hpp index 6138e5c77..f09303da2 100644 --- a/include/stdexec/__detail/__get_completion_signatures.hpp +++ b/include/stdexec/__detail/__get_completion_signatures.hpp @@ -302,11 +302,49 @@ namespace STDEXEC /////////////////////////////////////////////////////////////////////////////////////////////////// // An minimally constrained alias for the result of get_completion_signatures: +#if STDEXEC_GCC() template requires enable_sender<__decay_t<_Sender>> + && __constant()> using __completion_signatures_of_t = decltype(STDEXEC::get_completion_signatures<_Sender, _Env...>()); +#elif STDEXEC_EDG() + + namespace __detail + { + template + using __cmplsigs_of_t = + std::integral_constant()), + STDEXEC::get_completion_signatures<_Sender, _Env...>()>::value_type; + } // namespace __detail + + template + requires enable_sender<__decay_t<_Sender>> + && __minvocable_q<__detail::__cmplsigs_of_t, _Sender, _Env...> + using __completion_signatures_of_t = + decltype(STDEXEC::get_completion_signatures<_Sender, _Env...>()); + +#elif STDEXEC_MSVC() + + // MSVC cannot handle a __completion_signatures_of_t alias template that requires + // get_completion_signatures to be a constant expression, even if we wrap the call to + // get_completion_signatures in an integral_constant like we do for EDG. So we skip + // checking the requirement. + + template + requires enable_sender<__decay_t<_Sender>> + using __completion_signatures_of_t = + decltype(STDEXEC::get_completion_signatures<_Sender, _Env...>()); + +#else + + template + requires enable_sender<__decay_t<_Sender>> + using __completion_signatures_of_t = + __mtypeof()>; +#endif + /////////////////////////////////////////////////////////////////////////////////////////////////// // __get_child_completion_signatures template