diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index ca1ca923e..9d325deed 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -125,7 +125,9 @@ namespace experimental::execution template auto operator()(__ignore, _Adaptor __adaptor, _Sequence&& __sequence) - noexcept(__nothrow_decay_copyable<_Adaptor, _Sequence>) + noexcept(__nothrow_decay_copyable<_Adaptor, _Sequence> + && __nothrow_move_constructible<_Receiver> + && __nothrow_subscribable<_Sequence, __receiver<_Receiver, _Adaptor>>) -> __operation<_Sequence, _Receiver, _Adaptor> { return {static_cast<_Sequence&&>(__sequence), diff --git a/test/exec/sequence/test_transform_each.cpp b/test/exec/sequence/test_transform_each.cpp index 919aba25a..841f488b6 100644 --- a/test/exec/sequence/test_transform_each.cpp +++ b/test/exec/sequence/test_transform_each.cpp @@ -28,6 +28,11 @@ #include #include +#include +#include +#include +#include + namespace { @@ -43,6 +48,127 @@ namespace void set_value() noexcept {} }; + TEST_CASE("transform_each - subscribe is noexcept for a nonthrowing child", + "[sequence_senders][transform_each]") + { + auto transformed = exec::transform_each(exec::empty_sequence(), ex::then([]() noexcept {})); + + STATIC_REQUIRE(noexcept(exec::subscribe(transformed, next_rcvr{}))); + STATIC_REQUIRE(noexcept(exec::subscribe(std::as_const(transformed), next_rcvr{}))); + STATIC_REQUIRE(noexcept(exec::subscribe(std::move(transformed), next_rcvr{}))); + + auto op = exec::subscribe(std::move(transformed), next_rcvr{}); + ex::start(op); + } + +#if !STDEXEC_NO_STDCPP_EXCEPTIONS() + struct range_error + {}; + + struct throwing_move_rcvr : next_rcvr + { + throwing_move_rcvr() = default; + + throwing_move_rcvr(throwing_move_rcvr&&) + { + throw range_error{}; + } + }; + + TEST_CASE("transform_each - subscribe propagates receiver move exceptions", + "[sequence_senders][transform_each]") + { + auto transformed = exec::transform_each(exec::empty_sequence(), ex::then([]() noexcept {})); + + STATIC_REQUIRE_FALSE(noexcept(exec::subscribe(transformed, throwing_move_rcvr{}))); + CHECK_THROWS_AS(exec::subscribe(transformed, throwing_move_rcvr{}), range_error); + } + + struct throwing_copy_value + { + throwing_copy_value() = default; + + throwing_copy_value(throwing_copy_value const &) + { + throw range_error{}; + } + + throwing_copy_value(throwing_copy_value&&) noexcept = default; + }; + + TEST_CASE("transform_each - subscribe propagates adaptor copy exceptions", + "[sequence_senders][transform_each]") + { + auto transformed = exec::transform_each( + exec::empty_sequence(), + ex::then([capture = throwing_copy_value{}]() noexcept { (void) capture; })); + + STATIC_REQUIRE_FALSE(noexcept(exec::subscribe(transformed, next_rcvr{}))); + CHECK_THROWS_AS(exec::subscribe(transformed, next_rcvr{}), range_error); + } + + template + struct throwing_range + { + using iterator = std::array::iterator; + + auto begin() noexcept(!ThrowFromBegin) -> iterator + { + if constexpr (ThrowFromBegin) + { + throw range_error{}; + } + return values_.begin(); + } + + auto end() noexcept(ThrowFromBegin) -> iterator + { + if constexpr (!ThrowFromBegin) + { + throw range_error{}; + } + return values_.end(); + } + + std::array values_{0}; + }; + + struct range_rcvr : next_rcvr + { + void set_error(std::exception_ptr) noexcept {} + + void set_stopped() noexcept {} + }; + + TEMPLATE_TEST_CASE("transform_each - subscribe propagates range exceptions", + "[sequence_senders][transform_each][iterate]", + throwing_range, + throwing_range) + { + TestType range; + auto transformed = exec::transform_each(exec::iterate(std::views::all(range)), + ex::then([](int) noexcept {})); + + SECTION("lvalue sender") + { + STATIC_REQUIRE_FALSE(noexcept(exec::subscribe(transformed, range_rcvr{}))); + CHECK_THROWS_AS(exec::subscribe(transformed, range_rcvr{}), range_error); + } + + SECTION("const lvalue sender") + { + STATIC_REQUIRE_FALSE(noexcept(exec::subscribe(std::as_const(transformed), range_rcvr{}))); + CHECK_THROWS_AS(exec::subscribe(std::as_const(transformed), range_rcvr{}), range_error); + } + + SECTION("rvalue sender") + { + STATIC_REQUIRE_FALSE(noexcept(exec::subscribe(std::move(transformed), range_rcvr{}))); + CHECK_THROWS_AS(exec::subscribe(std::move(transformed), range_rcvr{}), range_error); + } + } +#endif // !STDEXEC_NO_STDCPP_EXCEPTIONS() + TEST_CASE("transform_each - transform sender applies adaptor to no elements", "[sequence_senders][transform_each][empty_sequence]") {