diff --git a/include/exec/sequence/merge_each.hpp b/include/exec/sequence/merge_each.hpp index 89cdf6146..ea3ea6119 100644 --- a/include/exec/sequence/merge_each.hpp +++ b/include/exec/sequence/merge_each.hpp @@ -1095,8 +1095,10 @@ namespace experimental::execution using sender_concept = STDEXEC::sender_tag; template - using __next_sequence_op_t = - __next_sequence_op<_NestedSequenceSender, _NextReceiver, _OperationBase, _NestedSeqOp>; + using __next_sequence_op_t = __next_sequence_op<_NestedSequenceSender, + __decay_t<_NextReceiver>, + _OperationBase, + _NestedSeqOp>; template static consteval auto get_completion_signatures() noexcept diff --git a/include/stdexec/__detail/__parallel_scheduler.hpp b/include/stdexec/__detail/__parallel_scheduler.hpp index 1c1ad3add..855efebe8 100644 --- a/include/stdexec/__detail/__parallel_scheduler.hpp +++ b/include/stdexec/__detail/__parallel_scheduler.hpp @@ -539,7 +539,7 @@ namespace STDEXEC auto st = get_stop_token(STDEXEC::get_env(__state_.__rcvr_)); if (st.stop_requested()) { - STDEXEC::set_stopped(__state_.__rcvr_); + STDEXEC::set_stopped(static_cast<__rcvr_t&&>(__state_.__rcvr_)); return; } diff --git a/include/stdexec/__detail/__receivers.hpp b/include/stdexec/__detail/__receivers.hpp index baf8d4f43..2cc3197bd 100644 --- a/include/stdexec/__detail/__receivers.hpp +++ b/include/stdexec/__detail/__receivers.hpp @@ -41,6 +41,10 @@ namespace STDEXEC { namespace __detail { + // For deduced forwarding-reference arguments, rejects lvalues and const rvalues. + template + concept __non_const_rvalue = !STDEXEC_IS_SAME(_Ty, _Ty const); + template <__disposition _Disposition> struct __completion_tag { @@ -114,7 +118,7 @@ namespace STDEXEC //! `.set_value(_As...)` member. //! @tparam _As The value-datum argument types. template - requires __set_value_member<_Receiver, _As...> + requires __detail::__non_const_rvalue<_Receiver> && __set_value_member<_Receiver, _As...> STDEXEC_ATTRIBUTE(host, device, always_inline) constexpr void operator()(_Receiver &&__rcvr, _As &&...__as) const noexcept { @@ -182,7 +186,7 @@ namespace STDEXEC //! @tparam _Receiver A type with a matching `.set_error(_Error)` member. //! @tparam _Error The error datum type. template - requires __set_error_member<_Receiver, _Error> + requires __detail::__non_const_rvalue<_Receiver> && __set_error_member<_Receiver, _Error> STDEXEC_ATTRIBUTE(host, device, always_inline) constexpr void operator()(_Receiver &&__rcvr, _Error &&__err) const noexcept { @@ -248,7 +252,7 @@ namespace STDEXEC //! @tparam _Receiver A type with a matching nullary //! `.set_stopped()` member. template - requires __set_stopped_member<_Receiver> + requires __detail::__non_const_rvalue<_Receiver> && __set_stopped_member<_Receiver> STDEXEC_ATTRIBUTE(host, device, always_inline) constexpr void operator()(_Receiver &&__rcvr) const noexcept { diff --git a/test/stdexec/cpos/test_cpo_receiver.cpp b/test/stdexec/cpos/test_cpo_receiver.cpp index 96267e389..674127ccd 100644 --- a/test/stdexec/cpos/test_cpo_receiver.cpp +++ b/test/stdexec/cpos/test_cpo_receiver.cpp @@ -35,7 +35,7 @@ namespace struct recv_value { - int* target_; + int *target_; void set_value(int val) noexcept { @@ -55,7 +55,7 @@ namespace struct recv_rvalref { - int* target_; + int *target_; void set_value(int val) noexcept { @@ -75,7 +75,7 @@ namespace struct recv_ref { - int* target_; + int *target_; void set_value(int val) noexcept { @@ -95,7 +95,7 @@ namespace struct recv_cref { - int* target_; + int *target_; void set_value(int val) noexcept { @@ -113,6 +113,14 @@ namespace } }; + // Const members ensure that rejection comes from the CPO constraints. + struct recv_const + { + void set_value(int) const noexcept {} + void set_error(int) const noexcept {} + void set_stopped() const noexcept {} + }; + TEST_CASE("can call set_value on a void receiver", "[cpo][cpo_receiver]") { ex::set_value(expect_void_receiver{}); @@ -170,16 +178,6 @@ namespace REQUIRE(val == 10); } - TEST_CASE("can call set_value on a receiver with ref type", "[cpo][cpo_receiver]") - { - static_assert(std::invocable, - "cannot call set_value on recv_ref"); - int val = 0; - recv_ref recv{&val}; - ex::set_value(recv, 10); - REQUIRE(val == 10); - } - TEST_CASE("can call set_value on a receiver with const ref type", "[cpo][cpo_receiver]") { static_assert(std::invocable, @@ -207,16 +205,6 @@ namespace REQUIRE(val == -10); } - TEST_CASE("can call set_error on a receiver with ref type", "[cpo][cpo_receiver]") - { - static_assert(std::invocable, - "cannot call set_error on recv_ref"); - int val = 0; - recv_ref recv{&val}; - ex::set_error(recv, 10); - REQUIRE(val == -10); - } - TEST_CASE("can call set_error on a receiver with const ref type", "[cpo][cpo_receiver]") { static_assert(std::invocable, @@ -244,16 +232,6 @@ namespace REQUIRE(val == INT_MAX); } - TEST_CASE("can call set_stopped on a receiver with ref type", "[cpo][cpo_receiver]") - { - static_assert(std::invocable, - "cannot call set_stopped on recv_ref"); - int val = 0; - recv_ref recv{&val}; - ex::set_stopped(recv); - REQUIRE(val == INT_MAX); - } - TEST_CASE("can call set_stopped on a receiver with const ref type", "[cpo][cpo_receiver]") { static_assert(std::invocable, @@ -263,6 +241,28 @@ namespace REQUIRE(val == INT_MAX); } + TEST_CASE("completion CPOs reject lvalue receivers", "[cpo][cpo_receiver]") + { + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + } + + TEST_CASE("completion CPOs reject const rvalue receivers", "[cpo][cpo_receiver]") + { + static_assert(std::invocable); + static_assert(std::invocable); + static_assert(std::invocable); + + static_assert(!std::invocable); + static_assert(!std::invocable); + static_assert(!std::invocable); + } + TEST_CASE("tag types can be deduced from set_value, set_error and set_stopped", "[cpo][cpo_receiver]") {