From 86f796d80de0336d2a877d6c40708855542b8a3d Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sat, 23 May 2026 18:30:20 -0700 Subject: [PATCH 1/8] Start supporting get_completion_domain of a function --- include/exec/function.hpp | 93 ++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 22 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index 90375c8dd..7ef802677 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -17,6 +17,7 @@ #include "../stdexec/__detail/__completion_signatures.hpp" #include "../stdexec/__detail/__concepts.hpp" +#include "../stdexec/__detail/__domain.hpp" #include "../stdexec/__detail/__meta.hpp" #include "../stdexec/__detail/__read_env.hpp" #include "../stdexec/__detail/__receivers.hpp" @@ -55,6 +56,11 @@ // queries to pick the frame allocator from the environment without relying on TLS. namespace experimental::execution { + // for specifying required sender attributes in exec::function + template <_query::_query_signature... Sigs> + struct attrs + {}; + namespace __func { using namespace STDEXEC; @@ -192,7 +198,7 @@ namespace experimental::execution } }; - template + template class __function; //! the main implementation of the type-erasing sender function<...> @@ -206,8 +212,8 @@ namespace experimental::execution //! not, as appropriate //! //! \tparam _Args The argument types used to construct the erased sender - template - class __function<_Sigs, queries<_Queries...>, _Args...> + template + class __function<_Sigs, queries<_Queries...>, attrs<_Attrs...>, _Args...> { using __receiver_t = __receiver_wrapper<__any_receiver_ref<_Sigs, queries<_Queries...>>>; @@ -342,6 +348,23 @@ namespace experimental::execution completion_signatures<__single_value_sig_t<_Return>, set_stopped_t()>, __eptr_completion_unless_t<__mbool<_NoExcept>>>>; + //! computes the set of get_completion_domain queries that must be supported by any + //! sender that might be erased by the corresponding function + //! + //! we should support get_completion_domain only if _Sigs contains a completion + //! of type Tag + //! + //! the query form should be + //! + //! default_domain(get_completion_domain_t, Env) + //! + //! where Env is the environment type we'll be synthesizing from _Queries + template + using __default_attrs = + __canonical_t), + default_domain(get_completion_domain_t), + default_domain(get_completion_domain_t)>>; + //! Map a variety of function<...> specifications into the canonical type-erased //! contract represented by the user-provided specification. //! @@ -362,48 +385,74 @@ namespace experimental::execution //! The order of Args... is obviously important, but Sigs... and Queries... are both //! canonicalized into a sorted and uniqued list to ensure order is irrelevant. template - struct __make_function; + class __make_function; template - struct __make_function<_Return(_Args...)> + class __make_function<_Return(_Args...)> { - using type = __function<__sigs_from_t<_Return, false>, queries<>, _Args...>; + using __sigs = __sigs_from_t<_Return, false>; + using __queries = queries<>; + using __attrs = __default_attrs<__sigs, __queries>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; }; template - struct __make_function<_Return(_Args...) noexcept> + class __make_function<_Return(_Args...) noexcept> { - using type = __function<__sigs_from_t<_Return, true>, queries<>, _Args...>; + using __sigs = __sigs_from_t<_Return, true>; + using __queries = queries<>; + using __attrs = __default_attrs<__sigs, __queries>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; }; template - struct __make_function> + class __make_function> { - using type = __function<__canonical_t>, queries<>, _Args...>; + using __sigs = __canonical_t>; + using __queries = queries<>; + using __attrs = __default_attrs<__sigs, __queries>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; }; template - struct __make_function<_Return(_Args...), queries<_Queries...>> + class __make_function<_Return(_Args...), queries<_Queries...>> { - using type = - __function<__sigs_from_t<_Return, false>, __canonical_t>, _Args...>; + using __sigs = __sigs_from_t<_Return, false>; + using __queries = __canonical_t>; + using __attrs = __default_attrs<__sigs, __queries>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; }; template - struct __make_function<_Return(_Args...) noexcept, queries<_Queries...>> + class __make_function<_Return(_Args...) noexcept, queries<_Queries...>> { - using type = - __function<__sigs_from_t<_Return, true>, __canonical_t>, _Args...>; + using __sigs = __sigs_from_t<_Return, true>; + using __queries = __canonical_t>; + using __attrs = __default_attrs<__sigs, __queries>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; }; template - struct __make_function, - queries<_Queries...>> + class __make_function, + queries<_Queries...>> { - using type = __function<__canonical_t>, - __canonical_t>, - _Args...>; + using __sigs = __canonical_t>; + using __queries = __canonical_t>; + using __attrs = __default_attrs<__sigs, __queries>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; }; } // namespace __func From a80ed99355d355b66fdea3a7e4bbac8af822410f Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sat, 23 May 2026 19:44:21 -0700 Subject: [PATCH 2/8] Start computing the default sender attributes I realized that the completion domains reported by a `function` will be receiver environment-independant, so I deleted `__default_attrs`'s `_Queries` parameter, and I've updated the default to be a function of the given completion signatures using `completion_signatures<...>::__transform_reduce`. --- include/exec/function.hpp | 51 ++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index 7ef802677..aad96872c 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -348,6 +348,33 @@ namespace experimental::execution completion_signatures<__single_value_sig_t<_Return>, set_stopped_t()>, __eptr_completion_unless_t<__mbool<_NoExcept>>>>; + //! maps a completion signature to the default completion domain query + struct __domain_query_from_sig + { + template + consteval auto operator()(_Tag (*)(_Args...)) const noexcept // + -> default_domain (*)(get_completion_domain_t<_Tag>) + { + return nullptr; + } + }; + + //! maps a pack of domain queries produced by __domain_query_from_sig to the + //! corresponding attrs<_Attrs...> type + class __attrs_from_domain_queries + { + template + using __query_sig = default_domain (*)(get_completion_domain_t<_Tag>); + + public: + template + consteval auto operator()(__query_sig<_Tag>...) const noexcept // + -> __canonical_t)...>> + { + return {}; + } + }; + //! computes the set of get_completion_domain queries that must be supported by any //! sender that might be erased by the corresponding function //! @@ -356,14 +383,10 @@ namespace experimental::execution //! //! the query form should be //! - //! default_domain(get_completion_domain_t, Env) - //! - //! where Env is the environment type we'll be synthesizing from _Queries - template - using __default_attrs = - __canonical_t), - default_domain(get_completion_domain_t), - default_domain(get_completion_domain_t)>>; + //! default_domain(get_completion_domain_t) + template + using __default_attrs = decltype(_Sigs::__transform_reduce(__domain_query_from_sig(), + __attrs_from_domain_queries())); //! Map a variety of function<...> specifications into the canonical type-erased //! contract represented by the user-provided specification. @@ -392,7 +415,7 @@ namespace experimental::execution { using __sigs = __sigs_from_t<_Return, false>; using __queries = queries<>; - using __attrs = __default_attrs<__sigs, __queries>; + using __attrs = __default_attrs<__sigs>; public: using type = __function<__sigs, __queries, __attrs, _Args...>; @@ -403,7 +426,7 @@ namespace experimental::execution { using __sigs = __sigs_from_t<_Return, true>; using __queries = queries<>; - using __attrs = __default_attrs<__sigs, __queries>; + using __attrs = __default_attrs<__sigs>; public: using type = __function<__sigs, __queries, __attrs, _Args...>; @@ -414,7 +437,7 @@ namespace experimental::execution { using __sigs = __canonical_t>; using __queries = queries<>; - using __attrs = __default_attrs<__sigs, __queries>; + using __attrs = __default_attrs<__sigs>; public: using type = __function<__sigs, __queries, __attrs, _Args...>; @@ -425,7 +448,7 @@ namespace experimental::execution { using __sigs = __sigs_from_t<_Return, false>; using __queries = __canonical_t>; - using __attrs = __default_attrs<__sigs, __queries>; + using __attrs = __default_attrs<__sigs>; public: using type = __function<__sigs, __queries, __attrs, _Args...>; @@ -436,7 +459,7 @@ namespace experimental::execution { using __sigs = __sigs_from_t<_Return, true>; using __queries = __canonical_t>; - using __attrs = __default_attrs<__sigs, __queries>; + using __attrs = __default_attrs<__sigs>; public: using type = __function<__sigs, __queries, __attrs, _Args...>; @@ -449,7 +472,7 @@ namespace experimental::execution { using __sigs = __canonical_t>; using __queries = __canonical_t>; - using __attrs = __default_attrs<__sigs, __queries>; + using __attrs = __default_attrs<__sigs>; public: using type = __function<__sigs, __queries, __attrs, _Args...>; From 6e48238b0ee5b7abeae249c8ed75ee19e5a10a8f Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sat, 23 May 2026 20:05:02 -0700 Subject: [PATCH 3/8] Maybe implement function's get_env It build and the existing tests pass, but I don't know if it works. --- include/exec/function.hpp | 45 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index aad96872c..99e4cf877 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -198,6 +198,41 @@ namespace experimental::execution } }; + template + struct __make_domain + {}; + + template + struct __make_domain<_Tag, _Domain(get_completion_domain_t<_Tag>)> + { + constexpr _Domain operator()() const noexcept + { + return _Domain(); + } + }; + + template + inline constexpr auto __get_completion_domain = + __first_callable<__make_domain<_Tag, _Attrs>...>(); + + template + struct __attrs + { + template + constexpr auto query(get_completion_domain_t<>, _Env &&...) const noexcept + -> decltype(__get_completion_domain) + { + return __get_completion_domain(); + } + + template + constexpr auto query(get_completion_domain_t<_Tag>, _Env &&...) const noexcept + -> decltype(__get_completion_domain<_Tag, _Attrs...>) + { + return __get_completion_domain<_Tag, _Attrs...>(); + } + }; + template class __function; @@ -226,8 +261,9 @@ namespace experimental::execution -> _any::_any_opstate_base { auto &__make_sender = *__std::start_lifetime_as<_Factory>(__storage); - using __alloc_t = decltype(__choose_frame_allocator(get_env(__rcvr))); - auto __alloc = __frame_allocator_t<__alloc_t>(__choose_frame_allocator(get_env(__rcvr))); + using __alloc_t = decltype(__choose_frame_allocator(STDEXEC::get_env(__rcvr))); + auto __alloc = __frame_allocator_t<__alloc_t>( + __choose_frame_allocator(STDEXEC::get_env(__rcvr))); return _any::_any_opstate_base(__in_place_from, std::allocator_arg, __alloc, @@ -285,6 +321,11 @@ namespace experimental::execution return _Sigs(); } + constexpr __attrs<_Attrs...> get_env() const noexcept + { + return {}; + } + template constexpr auto connect(_Receiver __rcvr) && // -> __opstate_t<_Receiver> From ba3069f3ae40a442d9bd40001277f212601b9a23 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sun, 24 May 2026 07:51:54 -0700 Subject: [PATCH 4/8] Add tests for get_completion_domain of a function The tests confirm that we only report a completion domain on the channels we might complete on, and I had to fix some bugs in `function.hpp` to make the tests pass. --- include/exec/function.hpp | 16 ++++---- test/exec/test_function.cpp | 78 +++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index 99e4cf877..f9683786d 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -211,6 +211,13 @@ namespace experimental::execution } }; + //! get_completion_domain<> is a special case; its type parameter is void + //! and it's equivalent to get_completion_domain. + template + struct __make_domain)> + : __make_domain)> + {}; + template inline constexpr auto __get_completion_domain = __first_callable<__make_domain<_Tag, _Attrs>...>(); @@ -218,16 +225,9 @@ namespace experimental::execution template struct __attrs { - template - constexpr auto query(get_completion_domain_t<>, _Env &&...) const noexcept - -> decltype(__get_completion_domain) - { - return __get_completion_domain(); - } - template constexpr auto query(get_completion_domain_t<_Tag>, _Env &&...) const noexcept - -> decltype(__get_completion_domain<_Tag, _Attrs...>) + -> decltype(__get_completion_domain<_Tag, _Attrs...>()) { return __get_completion_domain<_Tag, _Attrs...>(); } diff --git a/test/exec/test_function.cpp b/test/exec/test_function.cpp index 6283df70e..2227bef02 100644 --- a/test/exec/test_function.cpp +++ b/test/exec/test_function.cpp @@ -411,4 +411,82 @@ namespace STATIC_REQUIRE(std::assignable_from); } } + + struct none_such + {}; + + template + inline constexpr auto get_completion_domain = + ex::__first_callable{ex::get_completion_domain, ex::__always{none_such()}}; + + TEST_CASE("function reports a default completion domain by default", "[types][function]") + { + SECTION("throwing function reports a completion domain for all three channels") + { + exec::function fn(ex::just); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + + SECTION("no-throw function reports a completion domain for value and stop channels only") + { + exec::function fn(ex::just); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + + SECTION("infallible function reports a completion domain for value channel only") + { + exec::function> fn(ex::just); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + + SECTION("just_error function reports a completion domain for error channel only") + { + exec::function> fn( + 42, + ex::just_error); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + + SECTION("just_stopped function reports a completion domain for stop channel only") + { + exec::function> fn( + ex::just_stopped); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + } } // namespace From fd8c3b8e60bdb87e6b0b9a7ae11381430a506fbb Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sun, 24 May 2026 08:44:37 -0700 Subject: [PATCH 5/8] Rename the implementation details of __func::__attrs This diff just renames the bits and pieces that compute the completion domain of a `function`. --- include/exec/function.hpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index f9683786d..97f9be5bf 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -199,11 +199,11 @@ namespace experimental::execution }; template - struct __make_domain + struct __make_domain_impl {}; template - struct __make_domain<_Tag, _Domain(get_completion_domain_t<_Tag>)> + struct __make_domain_impl<_Tag, _Domain(get_completion_domain_t<_Tag>)> { constexpr _Domain operator()() const noexcept { @@ -214,22 +214,21 @@ namespace experimental::execution //! get_completion_domain<> is a special case; its type parameter is void //! and it's equivalent to get_completion_domain. template - struct __make_domain)> - : __make_domain)> + struct __make_domain_impl)> + : __make_domain_impl)> {}; template - inline constexpr auto __get_completion_domain = - __first_callable<__make_domain<_Tag, _Attrs>...>(); + inline constexpr auto __make_domain = __first_callable<__make_domain_impl<_Tag, _Attrs>...>(); template struct __attrs { template constexpr auto query(get_completion_domain_t<_Tag>, _Env &&...) const noexcept - -> decltype(__get_completion_domain<_Tag, _Attrs...>()) + -> decltype(__make_domain<_Tag, _Attrs...>()) { - return __get_completion_domain<_Tag, _Attrs...>(); + return __make_domain<_Tag, _Attrs...>(); } }; From 7d3b327f74aec2be0acfae8f46150a65771770c3 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sun, 24 May 2026 08:46:04 -0700 Subject: [PATCH 6/8] Restrict function's ctor based on the erased sender's completion domains This is a partial implementation of constraining `function`'s constructor to accept only factories that produce senders that completion in the expected domain. It's partial because: - we're currently checking for a "matching" domain with `__same_as`, but we should be checking with some function of `common_domain`; and - there's no way to specify any expected domain other than `default_domain`. The current tests pass, which means we correctly allow factories that produce senders that completion in the default domain. We need tests that show we reject senders that *don't* complete in the default domain, and we need a way to use and validate the use of non-default domains. --- include/exec/function.hpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index 97f9be5bf..cf9a89f04 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -232,6 +232,28 @@ namespace experimental::execution } }; + template + using __completion_domain_t = __call_result_or_t< + get_completion_domain_t<_Tag>, + __call_result_or_t, indeterminate_domain<>, _Attrs>, + _Attrs, + _Env const &...>; + + template + concept __completion_domain_matches = + __same_as<__completion_domain_t<_Tag, _ActualAttrs, _Env...>, + __completion_domain_t<_Tag, _ExpectedAttrs, _Env...>>; + + template + concept __completion_domains_match_impl = + __completion_domain_matches + && __completion_domain_matches + && __completion_domain_matches; + + template + concept __completion_domains_match = + __completion_domains_match_impl, env_of_t<_Expected>, _Env...>; + template class __function; @@ -298,6 +320,9 @@ namespace experimental::execution && (STDEXEC_IS_TRIVIALLY_COPYABLE(_Factory)) // && (sizeof(_Factory) <= sizeof(__make_sender_)) // && sender_to<__invoke_result_t<_Factory, _Args...>, __receiver_t> + && __completion_domains_match<__invoke_result_t<_Factory, _Args...>, + __function, + env_of_t> constexpr explicit __function(_Args &&...__args, _Factory __factory) noexcept(__nothrow_move_constructible<_Args...>) : __args_(static_cast<_Args &&>(__args)...) From 76783615a5ba4fbfdf5070a04e1fe30077beed5b Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sun, 24 May 2026 10:52:47 -0700 Subject: [PATCH 7/8] First test confirming non-default domain support The implementation is incomplete because we check for identical domains but should be checking for compatible domains with `common_domain`. --- include/exec/function.hpp | 39 ++++++++++++++++++++++++++++--------- test/exec/test_function.cpp | 29 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index cf9a89f04..2c7d633ea 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -112,13 +112,13 @@ namespace experimental::execution {} constexpr auto get_env() const noexcept // - -> __join_env_t<__prop_t, env_of_t<_Receiver>> + -> __join_env_t<__prop_t const &, env_of_t<_Receiver>> { return __env::__join(*__env_, STDEXEC::get_env(*static_cast<_Receiver const *>(this))); } private: - __prop_t *__env_; + __prop_t const *__env_; }; template @@ -203,7 +203,7 @@ namespace experimental::execution {}; template - struct __make_domain_impl<_Tag, _Domain(get_completion_domain_t<_Tag>)> + struct __make_domain_impl<_Tag, _Domain(get_completion_domain_t<_Tag>) noexcept> { constexpr _Domain operator()() const noexcept { @@ -214,8 +214,15 @@ namespace experimental::execution //! get_completion_domain<> is a special case; its type parameter is void //! and it's equivalent to get_completion_domain. template - struct __make_domain_impl)> - : __make_domain_impl)> + struct __make_domain_impl) noexcept> + : __make_domain_impl) noexcept> + {}; + + //! get_completion_domain ought to be no-throw, so make it optional to specify + //! noexcept on the signature provided with attrs<...> + template + struct __make_domain_impl<_Tag1, _Domain(get_completion_domain_t<_Tag2>)> + : __make_domain_impl<_Tag1, _Domain(get_completion_domain_t<_Tag2>) noexcept> {}; template @@ -322,7 +329,7 @@ namespace experimental::execution && sender_to<__invoke_result_t<_Factory, _Args...>, __receiver_t> && __completion_domains_match<__invoke_result_t<_Factory, _Args...>, __function, - env_of_t> + env_of_t<__receiver_t>> constexpr explicit __function(_Args &&...__args, _Factory __factory) noexcept(__nothrow_move_constructible<_Args...>) : __args_(static_cast<_Args &&>(__args)...) @@ -418,7 +425,7 @@ namespace experimental::execution { template consteval auto operator()(_Tag (*)(_Args...)) const noexcept // - -> default_domain (*)(get_completion_domain_t<_Tag>) + -> default_domain (*)(get_completion_domain_t<_Tag>) noexcept { return nullptr; } @@ -429,12 +436,12 @@ namespace experimental::execution class __attrs_from_domain_queries { template - using __query_sig = default_domain (*)(get_completion_domain_t<_Tag>); + using __query_sig = default_domain (*)(get_completion_domain_t<_Tag>) noexcept; public: template consteval auto operator()(__query_sig<_Tag>...) const noexcept // - -> __canonical_t)...>> + -> __canonical_t) noexcept...>> { return {}; } @@ -542,6 +549,20 @@ namespace experimental::execution public: using type = __function<__sigs, __queries, __attrs, _Args...>; }; + + template + class __make_function, + queries<_Queries...>, + attrs<_Attrs...>> + { + using __sigs = __canonical_t>; + using __queries = __canonical_t>; + using __attrs = __canonical_t>; + + public: + using type = __function<__sigs, __queries, __attrs, _Args...>; + }; } // namespace __func //! the user-facing interface to exec::function that supports several different diff --git a/test/exec/test_function.cpp b/test/exec/test_function.cpp index 2227bef02..0838b058e 100644 --- a/test/exec/test_function.cpp +++ b/test/exec/test_function.cpp @@ -489,4 +489,33 @@ namespace STATIC_REQUIRE(std::same_as); } } + + struct domain : ex::default_domain + {}; + + TEST_CASE("function's constructor is constrained based on the common domain", "[types][function]") + { + using queries = exec::queries; + + SECTION("the constraint applies to set_value") + { + using function = + exec::function, + queries, + exec::attrs) noexcept>>; + + STATIC_REQUIRE(std::constructible_from); + + function fn(ex::just); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + } } // namespace From 0a52fdc46d9d447d0996cab9f846b5fed20233fc Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Mon, 1 Jun 2026 14:09:30 -0700 Subject: [PATCH 8/8] Modify domain-matching to use common-domain This diff modifies the `function` test that validates the domain-related constraints on `function`'s constructor. The old test required that `function` only erase senders with completion domains identitcal to those specified in the `function`'s type parameters; the intended design is that the erased sender's completion domains be _derived from_ the advertised domains. The new test requires the correct relationship, which forced a change in `function` to make the new test pass. --- include/exec/function.hpp | 8 +++++-- test/exec/test_function.cpp | 44 ++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/include/exec/function.hpp b/include/exec/function.hpp index 2c7d633ea..0fbfc9955 100644 --- a/include/exec/function.hpp +++ b/include/exec/function.hpp @@ -246,10 +246,14 @@ namespace experimental::execution _Attrs, _Env const &...>; + template + concept __completion_domain_matches_impl = + __same_as<_ExpectedDomain, __common_domain_t<_ActualDomain, _ExpectedDomain>>; + template concept __completion_domain_matches = - __same_as<__completion_domain_t<_Tag, _ActualAttrs, _Env...>, - __completion_domain_t<_Tag, _ExpectedAttrs, _Env...>>; + __completion_domain_matches_impl<__completion_domain_t<_Tag, _ActualAttrs, _Env...>, + __completion_domain_t<_Tag, _ExpectedAttrs, _Env...>>; template concept __completion_domains_match_impl = diff --git a/test/exec/test_function.cpp b/test/exec/test_function.cpp index 0838b058e..13160fc70 100644 --- a/test/exec/test_function.cpp +++ b/test/exec/test_function.cpp @@ -500,10 +500,7 @@ namespace SECTION("the constraint applies to set_value") { using function = - exec::function, - queries, - exec::attrs) noexcept>>; + exec::function, queries>; STATIC_REQUIRE(std::constructible_from); @@ -513,9 +510,46 @@ namespace auto error_domain = get_completion_domain(attrs); auto stop_domain = get_completion_domain(attrs); - STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); STATIC_REQUIRE(std::same_as); STATIC_REQUIRE(std::same_as); } + + SECTION("the constraint applies to set_error") + { + using function = exec::function, + queries>; + + STATIC_REQUIRE(std::constructible_from); + + function fn(42, ex::just_error); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } + + SECTION("the constraint applies to set_stopped") + { + using function = + exec::function, queries>; + + STATIC_REQUIRE(std::constructible_from); + + function fn(ex::just_stopped); + auto attrs = ex::get_env(fn); + auto value_domain = get_completion_domain(attrs); + auto error_domain = get_completion_domain(attrs); + auto stop_domain = get_completion_domain(attrs); + + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + STATIC_REQUIRE(std::same_as); + } } } // namespace