From 5c3e5147c64e2b3f70585cf040bd86dc219114dc Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 23 Jul 2026 21:26:58 +0000 Subject: [PATCH 1/2] fix `stopped_as_optional` handling of multi-value senders --- .../stdexec/__detail/__sender_concepts.hpp | 29 ++++---- .../__detail/__stopped_as_optional.hpp | 66 +++++++++---------- include/stdexec/__detail/__sync_wait.hpp | 2 +- .../adaptors/test_stopped_as_optional.cpp | 22 ++++--- test/test_common/receivers.hpp | 28 ++++---- 5 files changed, 74 insertions(+), 73 deletions(-) diff --git a/include/stdexec/__detail/__sender_concepts.hpp b/include/stdexec/__detail/__sender_concepts.hpp index 2d18ab29b..065e60987 100644 --- a/include/stdexec/__detail/__sender_concepts.hpp +++ b/include/stdexec/__detail/__sender_concepts.hpp @@ -188,7 +188,7 @@ namespace STDEXEC //! `sender_to` is the strongest form of the sender concept: it //! requires that @c S is a sender whose completion signatures can be //! computed in @c R's environment, that @c R is a receiver that accepts - //! all of those signatures, *and* that @c connect(S, R) is well-formed. + //! all of those signatures, *and* that `connect(S, R)` is well-formed. //! //! This is the constraint a sender consumer or scheduler implementation //! uses just before actually calling @c connect — it's the strongest @@ -234,14 +234,20 @@ namespace STDEXEC template concept dependent_sender = sender<_Sender> && __is_dependent_sender<_Sender>; - template - using __single_sender_value_t = __value_types_t<__completion_signatures_of_t<_Sender, _Env...>, - __qq<__msingle>, - __qq<__msingle>>; + struct __as_single_value + { + template + requires(sizeof...(_Ts) >= 1) + using __f = + __mcall<__if_c<(sizeof...(_Ts) == 1), __q<__decay_t>, __qq<__decayed_std_tuple>>, _Ts...>; + }; + //! See @c single-sender-value-type in [exec.snd.concepts] in the C++26 working draft. template - using __single_value_variant_sender_t = - __value_types_t<__completion_signatures_of_t<_Sender, _Env...>, __qq<__mlist>, __qq<__msingle>>; + using __single_sender_value_t = // + __value_types_t<__completion_signatures_of_t<_Sender, _Env...>, + __as_single_value, + __qq<__msingle>>; template concept __sends = sender_in<_Sender, _Env...> // @@ -255,23 +261,18 @@ namespace STDEXEC using __never_sends_t = __mbool<__never_sends<_Tag, _Sender, _Env...>>; template - using __is_eptr = __mbool<__decays_to<_Error, std::exception_ptr>>; + using __is_eptr_t = __mbool<__decays_to<_Error, std::exception_ptr>>; template concept __has_eptr_completion = sender_in<_Sender, _Env...> // && __error_types_t<__completion_signatures_of_t<_Sender, _Env...>, - __q1<__is_eptr>, + __q1<__is_eptr_t>, __qq<__mor_t>>::value; template concept __single_value_sender = sender_in<_Sender, _Env...> // && requires { typename __single_sender_value_t<_Sender, _Env...>; }; - template - concept __single_value_variant_sender = - sender_in<_Sender, _Env...> // - && requires { typename __single_value_variant_sender_t<_Sender, _Env...>; }; - namespace __detail { template diff --git a/include/stdexec/__detail/__stopped_as_optional.hpp b/include/stdexec/__detail/__stopped_as_optional.hpp index 05d9092b3..f39fccc55 100644 --- a/include/stdexec/__detail/__stopped_as_optional.hpp +++ b/include/stdexec/__detail/__stopped_as_optional.hpp @@ -27,7 +27,6 @@ #include "__sender_adaptor_closure.hpp" #include "__senders.hpp" #include "__transform_completion_signatures.hpp" -#include "__type_traits.hpp" #include #include @@ -40,7 +39,7 @@ namespace STDEXEC // [exec.stopped.opt] namespace __sao { - struct _SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_ONE_ARGUMENT_; + struct _SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_AT_LEAST_ONE_ARGUMENT_; template struct __state @@ -53,53 +52,48 @@ namespace STDEXEC struct __stopped_as_optional_impl : __sexpr_defaults { - template - requires(sizeof...(_Tys) == 1) - using __set_value_t = completion_signatures>...)>; - - template - using __set_error_t = completion_signatures; - - template - using __value_type_t = - __decay_t<__single_sender_value_t<__child_of<_Sender>, env_of_t<_Receiver>>>; + static consteval auto __get_value_transform_fn() noexcept + { + return []() + { + using __value_t = __mcall<__as_single_value, _Ts...>; + return STDEXEC::__concat_completion_signatures( + completion_signatures)>(), + __eptr_completion_unless_t<__nothrow_decay_copyable_t<_Ts...>>()); + }; + } template - static constexpr auto __get_completion_signatures() + static consteval auto __get_completion_signatures() { static_assert(__sender_for<_Self, stopped_as_optional_t>); - auto __completions = STDEXEC::get_completion_signatures<__child_of<_Self>, _Env...>(); - + using __cv_sndr_t = __child_of<_Self>; + auto __completions = STDEXEC::get_completion_signatures<__cv_sndr_t, _Env...>(); STDEXEC_IF_OK(__completions) { - using _Completions = decltype(__completions); - if constexpr (__single_value_sender<__child_of<_Self>, _Env...>) + if constexpr (!__single_value_sender<__cv_sndr_t, _Env...>) { - return __transform_completion_signatures_t< - _Completions, - completion_signatures, - __set_value_t, - __set_error_t, - completion_signatures<>>(); + return STDEXEC::__throw_compile_time_error< + _WHAT_(_SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_AT_LEAST_ONE_ARGUMENT_), + _WHERE_(_IN_ALGORITHM_, stopped_as_optional_t), + _WITH_PRETTY_SENDER_<__cv_sndr_t>>(); } else { - return STDEXEC::__throw_compile_time_error< - _WHAT_(_SENDER_MUST_HAVE_EXACTLY_ONE_VALUE_COMPLETION_WITH_ONE_ARGUMENT_), - _WHERE_(_IN_ALGORITHM_, stopped_as_optional_t), - _WITH_PRETTY_SENDER_<__child_of<_Self>>>(); + return STDEXEC::__transform_completion_signatures(__completions, + __get_value_transform_fn(), + {}, + __ignore_completion()); } } - }; + } static constexpr auto __get_state = [](_Self&&, _Receiver&& __rcvr) noexcept - -> __state<_Receiver, __value_type_t<_Self, _Receiver>> - requires sender_in<__child_of<_Self>, env_of_t<_Receiver>> + -> __state<_Receiver, __single_sender_value_t<__child_of<_Self>, env_of_t<_Receiver>>> { static_assert(__sender_for<_Self, stopped_as_optional_t>); - using __value_t = __value_type_t<_Self, _Receiver>; - return __state<_Receiver, __value_t>{static_cast<_Receiver&&>(__rcvr)}; + return {static_cast<_Receiver&&>(__rcvr)}; }; static constexpr auto __complete = @@ -115,11 +109,15 @@ namespace STDEXEC { static_assert(__std::constructible_from<__value_t, _Args...>); STDEXEC::set_value(static_cast<_State&&>(__state).__rcvr_, - std::optional<__value_t>{static_cast<_Args&&>(__args)...}); + std::optional<__value_t>{std::in_place, + static_cast<_Args&&>(__args)...}); } STDEXEC_CATCH_ALL { - STDEXEC::set_error(static_cast<_State&&>(__state).__rcvr_, std::current_exception()); + if constexpr (!__nothrow_decay_copyable<_Args...>) + { + STDEXEC::set_error(static_cast<_State&&>(__state).__rcvr_, std::current_exception()); + } } } else if constexpr (__same_as<_Tag, set_error_t>) diff --git a/include/stdexec/__detail/__sync_wait.hpp b/include/stdexec/__detail/__sync_wait.hpp index 083e8c774..7caacbd4f 100644 --- a/include/stdexec/__detail/__sync_wait.hpp +++ b/include/stdexec/__detail/__sync_wait.hpp @@ -185,7 +185,7 @@ namespace STDEXEC::__sync_wait template concept __valid_sync_wait_argument = __ok<__minvoke< - __mtry_catch_q<__single_value_variant_sender_t, __q<__too_many_successful_completions_error_t>>, + __mtry_catch_q<__single_sender_value_t, __q<__too_many_successful_completions_error_t>>, _CvSender, __env>>; } // namespace STDEXEC::__sync_wait diff --git a/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp b/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp index b90dc3167..4f7df75a7 100644 --- a/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp +++ b/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp @@ -63,12 +63,14 @@ namespace wait_for_value(std::move(snd), std::optional{11}); } - // TEST_CASE( - // "stopped_as_optional shall not work with multi-value senders", - // "[adaptors][stopped_as_optional]") { - // auto snd = ex::just(3, 0.1415) | ex::stopped_as_optional(); - // static_assert(!ex::sender_to>); - // } + TEST_CASE("stopped_as_optional works with multi-value senders", "[adaptors][stopped_as_optional]") + { + auto snd = ex::just(3, 0.1415) | ex::stopped_as_optional(); + wait_for_value(std::move(snd), + std::optional>{ + std::tuple{3, 0.1415} + }); + } TEST_CASE("stopped_as_optional shall not work with senders that have multiple alternatives", "[adaptors][stopped_as_optional]") @@ -111,13 +113,13 @@ namespace error_scheduler sched2{}; error_scheduler sched3{-1}; - check_err_types>(ex::just(11) | ex::continues_on(sched1) - | ex::stopped_as_optional()); + check_err_types>(ex::just(11) | ex::continues_on(sched1) + | ex::stopped_as_optional()); check_err_types>(ex::just(13) | ex::continues_on(sched2) | ex::stopped_as_optional()); - check_err_types>(ex::just(13) | ex::continues_on(sched3) - | ex::stopped_as_optional()); + check_err_types>(ex::just(13) | ex::continues_on(sched3) + | ex::stopped_as_optional()); } TEST_CASE("stopped_as_optional overrides sends_stopped to false", diff --git a/test/test_common/receivers.hpp b/test/test_common/receivers.hpp index b557551ab..89158dcd7 100644 --- a/test/test_common/receivers.hpp +++ b/test/test_common/receivers.hpp @@ -35,7 +35,7 @@ namespace { struct recv0 { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; void set_value() noexcept {} @@ -46,7 +46,7 @@ namespace struct recv_int { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; void set_value(int) noexcept {} @@ -57,7 +57,7 @@ namespace struct recv0_ec { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; void set_value() noexcept {} @@ -70,7 +70,7 @@ namespace struct recv_int_ec { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; void set_value(int) noexcept {} @@ -90,7 +90,7 @@ namespace _Env env_{}; public: - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; base_expect_receiver() = default; ~base_expect_receiver() @@ -153,7 +153,7 @@ namespace struct expect_void_receiver_ex { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; expect_void_receiver_ex(bool& executed) : executed_(&executed) @@ -228,7 +228,7 @@ namespace Env env_{}; public: - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; explicit expect_value_receiver_ex(T& dest) : dest_(&dest) @@ -295,7 +295,7 @@ namespace template > struct expect_stopped_receiver_ex { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; explicit expect_stopped_receiver_ex(bool& executed) : executed_(&executed) @@ -415,7 +415,7 @@ namespace template > struct expect_error_receiver_ex { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; explicit expect_error_receiver_ex(T& value) : value_(&value) @@ -460,7 +460,7 @@ namespace struct logging_receiver { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; logging_receiver(int& state) : state_(&state) @@ -499,7 +499,7 @@ namespace template struct typecat_receiver { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; T* value_; typecat* cat_; @@ -539,7 +539,7 @@ namespace template struct fun_receiver { - using receiver_concept = STDEXEC::receiver_tag; + using receiver_concept = ex::receiver_tag; F f_; template @@ -590,10 +590,10 @@ namespace { // Ensure that the given sender type has only one variant for set_value calls // If not, sync_wait will not work - static_assert(STDEXEC::__single_value_variant_sender, + static_assert(ex::__count_of::value == 1, "Sender passed to sync_wait needs to have one variant for sending set_value"); - std::optional> res = STDEXEC::sync_wait(static_cast(snd)); + std::optional> res = ex::sync_wait(static_cast(snd)); CHECK(res.has_value()); std::tuple expected(static_cast(val)...); if constexpr (std::tuple_size_v> == 1) From 71d26e5bc47fe4f179368b4e559dc287338c2859 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 23 Jul 2026 22:39:48 +0000 Subject: [PATCH 2/2] try to fix CUDA build and silence nvc++ warnings --- include/nvexec/stream/sync_wait.cuh | 2 +- include/stdexec/__detail/__env.hpp | 2 ++ test/nvexec/common.cuh | 6 +++++- test/test_common/catch2.hpp | 4 +++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/nvexec/stream/sync_wait.cuh b/include/nvexec/stream/sync_wait.cuh index 6c3b4c193..813c4584f 100644 --- a/include/nvexec/stream/sync_wait.cuh +++ b/include/nvexec/stream/sync_wait.cuh @@ -181,7 +181,7 @@ namespace nv::execution::_strm struct sync_wait_t { template Sender> - requires __single_value_variant_sender + requires(__count_of::value == 1) auto operator()(context ctx, Sender&& sndr) const // -> std::optional> { diff --git a/include/stdexec/__detail/__env.hpp b/include/stdexec/__detail/__env.hpp index 3d9aba8e1..89994a413 100644 --- a/include/stdexec/__detail/__env.hpp +++ b/include/stdexec/__detail/__env.hpp @@ -222,6 +222,8 @@ namespace STDEXEC sizeof...(_Envs) - __mcall<__mfind_if<__q1<__has_query_t>, __msize>, _Envs...>::value; if constexpr (__index < sizeof...(_Envs)) return STDEXEC::__get<__index>(__env); + else + return void(); } }; } // namespace __detail diff --git a/test/nvexec/common.cuh b/test/nvexec/common.cuh index bec6028c9..ad81ec050 100644 --- a/test/nvexec/common.cuh +++ b/test/nvexec/common.cuh @@ -26,9 +26,11 @@ #include #include +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_EDG(is_constant_evaluated_in_nonconstexpr_context) + namespace { - template requires(N > 0) class flags_storage_t @@ -362,3 +364,5 @@ namespace static_assert(!std::is_trivially_copyable_v); } // namespace + +STDEXEC_PRAGMA_POP() diff --git a/test/test_common/catch2.hpp b/test/test_common/catch2.hpp index a3403f3af..85907a1a9 100644 --- a/test/test_common/catch2.hpp +++ b/test/test_common/catch2.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Lucian Radu Teodorescu + * Copyright (c) 2026 NVIDIA Corporation * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with @@ -20,6 +20,8 @@ STDEXEC_PRAGMA_PUSH() STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-parameter") +STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity) +STDEXEC_PRAGMA_IGNORE_EDG(deprecated_entity_with_custom_message) #include