diff --git a/include/exec/when_any.hpp b/include/exec/when_any.hpp index 94d8e6918..1e1a58bae 100644 --- a/include/exec/when_any.hpp +++ b/include/exec/when_any.hpp @@ -34,39 +34,39 @@ namespace experimental::execution template using __env_t = __join_env_t, _Env>; - template - using __nothrow_decay_copyable_and_move_constructible_t = __mbool<( - (__nothrow_decay_copyable<_Ts> && __nothrow_move_constructible<__decay_t<_Ts>>) && ...)>; - template using __as_rvalues = set_value_t (*)(__decay_t...); template - using __as_error = set_error_t (*)(E...); + using __as_error = set_error_t (*)(__decay_t...); - // Here we convert all set_value(Args...) to set_value(__decay_t...). Note, we keep all - // error types as they are and unconditionally add set_stopped(). The indirection through the - // __completions_fn is to avoid a pack expansion bug in nvc++. + // Here we convert all set_value(Args...) and set_error(Args...) to use decayed arguments and + // unconditionally add set_stopped(). The indirection through the __completions_fn is to avoid + // a pack expansion bug in nvc++. template struct __completions_fn { + template + using __raw_completions_t = __completion_signatures_of_t<_CvSender, __env_t<_Env>...>; + + template + using __decayed_completions_t = + __transform_reduce_completion_signatures_t<__raw_completions_t<_CvSender>, + __as_rvalues, + __as_error, + set_stopped_t (*)(), + __completion_signature_ptrs_t>; + template - using __all_value_args_nothrow_decay_copyable = - __minvoke_q<__mand_t, - __value_types_t<__completion_signatures_of_t<_CvSenders, __env_t<_Env>...>, - __qq<__nothrow_decay_copyable_and_move_constructible_t>, - __qq<__mand_t>>...>; + using __all_nothrow_decay_copyable_results = + __mand<__nothrow_decay_copyable_results_t<__raw_completions_t<_CvSenders>>..., + __nothrow_decay_copyable_results_t<__decayed_completions_t<_CvSenders>>...>; template using __f = __mtry_q<__concat_completion_signatures_t>::__f< - __eptr_completion_unless_t<__all_value_args_nothrow_decay_copyable<_CvSenders...>>, + __eptr_completion_unless_t<__all_nothrow_decay_copyable_results<_CvSenders...>>, completion_signatures, - __transform_reduce_completion_signatures_t< - __completion_signatures_of_t<_CvSenders, __env_t<_Env>...>, - __as_rvalues, - __as_error, - set_stopped_t (*)(), - __completion_signature_ptrs_t>...>; + __decayed_completions_t<_CvSenders>...>; }; template diff --git a/test/exec/test_when_any.cpp b/test/exec/test_when_any.cpp index 58df5951c..7a5518f1b 100644 --- a/test/exec/test_when_any.cpp +++ b/test/exec/test_when_any.cpp @@ -31,6 +31,213 @@ using namespace STDEXEC; namespace { + struct error_ref_sender + { + struct error + {}; + + using sender_concept = ex::sender_tag; + + template + static consteval auto + get_completion_signatures() noexcept -> ex::completion_signatures + { + return {}; + } + + template + struct operation + { + Receiver rcvr_; + error error_; + + void start() & noexcept + { + ex::set_error(static_cast(rcvr_), error_); + } + }; + + template + auto connect(Receiver rcvr) && noexcept -> operation + { + return {static_cast(rcvr), {}}; + } + }; + + struct error_ref_receiver + { + using receiver_concept = ex::receiver_tag; + + bool* lvalue_error_; + + void set_value() noexcept {} + void set_stopped() noexcept {} + + void set_error(error_ref_sender::error&) noexcept + { + *lvalue_error_ = true; + } + + void set_error(error_ref_sender::error&&) noexcept + { + *lvalue_error_ = false; + } + + auto get_env() const noexcept -> ex::env<> + { + return {}; + } + }; + +#if !STDEXEC_NO_STDCPP_EXCEPTIONS() + struct copy_noexcept_move_throws + { + copy_noexcept_move_throws() = default; + + copy_noexcept_move_throws(copy_noexcept_move_throws const &) noexcept = default; + + copy_noexcept_move_throws(copy_noexcept_move_throws&&) noexcept(false) {} + }; + + struct throwing_move_error_sender + { + using sender_concept = ex::sender_tag; + + template + static consteval auto get_completion_signatures() noexcept + -> ex::completion_signatures + { + return {}; + } + + template + struct operation + { + Receiver rcvr_; + copy_noexcept_move_throws error_; + + void start() & noexcept + { + ex::set_error(static_cast(rcvr_), error_); + } + }; + + template + auto connect(Receiver rcvr) && noexcept -> operation + { + return {static_cast(rcvr), {}}; + } + }; + + template + struct throwing_move_receiver + { + using receiver_concept = ex::receiver_tag; + + bool* got_value_; + bool* got_error_; + bool* got_exception_; + + void set_value(Type&&) noexcept + { + *got_value_ = true; + } + + void set_error(Type&&) noexcept + { + *got_error_ = true; + } + + void set_error(std::exception_ptr) noexcept + { + *got_exception_ = true; + } + + void set_stopped() noexcept {} + + auto get_env() const noexcept -> ex::env<> + { + return {}; + } + }; + + struct error_copy_error + {}; + + struct throwing_error + { + explicit throwing_error(bool* throw_on_copy) noexcept + : throw_on_copy_{throw_on_copy} + {} + + throwing_error(throwing_error const & other) + : throw_on_copy_{other.throw_on_copy_} + { + if (*throw_on_copy_) + { + throw error_copy_error{}; + } + } + + throwing_error(throwing_error&&) noexcept = default; + + bool* throw_on_copy_; + }; + + struct throwing_error_sender + { + using sender_concept = ex::sender_tag; + + bool* throw_on_copy_; + + template + static consteval auto get_completion_signatures() noexcept + -> ex::completion_signatures + { + return {}; + } + + template + struct operation + { + Receiver rcvr_; + throwing_error error_; + + void start() & noexcept + { + ex::set_error(static_cast(rcvr_), error_); + } + }; + + template + auto connect(Receiver rcvr) && noexcept -> operation + { + return {static_cast(rcvr), throwing_error{throw_on_copy_}}; + } + }; + + struct throwing_error_receiver + { + using receiver_concept = ex::receiver_tag; + + bool* got_exception_; + + void set_error(throwing_error) noexcept {} + + void set_error(std::exception_ptr) noexcept + { + *got_exception_ = true; + } + + void set_stopped() noexcept {} + + auto get_env() const noexcept -> ex::env<> + { + return {}; + } + }; +#endif // !STDEXEC_NO_STDCPP_EXCEPTIONS() + TEST_CASE("when_ny returns a sender", "[adaptors][when_any]") { auto snd = exec::when_any(ex::just(3), ex::just(0.1415)); @@ -188,6 +395,80 @@ namespace // wait_for_value(std::move(snd), movable(42)); } + TEST_CASE("when_any decays error completion arguments", "[adaptors][when_any]") + { + auto snd = exec::when_any(error_ref_sender{}); + static_assert( + set_equivalent, + completion_signatures>); + + bool lvalue_error = false; + auto op = ex::connect(std::move(snd), error_ref_receiver{&lvalue_error}); + ex::start(op); + CHECK_FALSE(lvalue_error); + } + +#if !STDEXEC_NO_STDCPP_EXCEPTIONS() + TEST_CASE("when_any reports errors from throwing error decay", "[adaptors][when_any]") + { + bool throw_on_copy = false; + auto snd = exec::when_any(throwing_error_sender{&throw_on_copy}); + static_assert(set_equivalent, + completion_signatures>); + + bool got_exception = false; + auto op = ex::connect(std::move(snd), throwing_error_receiver{&got_exception}); + throw_on_copy = true; + ex::start(op); + CHECK(got_exception); + } + + TEST_CASE("when_any reports errors for potentially throwing value moves", "[adaptors][when_any]") + { + copy_noexcept_move_throws value; + auto snd = exec::when_any(just_ref{value}); + static_assert(set_equivalent, + completion_signatures>); + + bool got_value = false; + bool got_error = false; + bool got_exception = false; + auto op = ex::connect(std::move(snd), + throwing_move_receiver{&got_value, + &got_error, + &got_exception}); + ex::start(op); + CHECK(got_value); + CHECK_FALSE(got_error); + CHECK_FALSE(got_exception); + } + + TEST_CASE("when_any reports errors for potentially throwing error moves", "[adaptors][when_any]") + { + auto snd = exec::when_any(throwing_move_error_sender{}); + static_assert(set_equivalent, + completion_signatures>); + + bool got_value = false; + bool got_error = false; + bool got_exception = false; + auto op = ex::connect(std::move(snd), + throwing_move_receiver{&got_value, + &got_error, + &got_exception}); + ex::start(op); + CHECK_FALSE(got_value); + CHECK(got_error); + CHECK_FALSE(got_exception); + } +#endif // !STDEXEC_NO_STDCPP_EXCEPTIONS() + #if !STDEXEC_NO_STDCPP_EXCEPTIONS() template struct dup_op