Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion include/exec/sequence/transform_each.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ namespace experimental::execution

template <class _Adaptor, class _Sequence>
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),
Expand Down
126 changes: 126 additions & 0 deletions test/exec/sequence/test_transform_each.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
#include <test_common/senders.hpp>
#include <test_common/type_helpers.hpp>

#include <array>
#include <exception>
#include <ranges>
#include <utility>

namespace
{

Expand All @@ -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 <bool ThrowFromBegin>
struct throwing_range
{
using iterator = std::array<int, 1>::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<int, 1> 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<true>,
throwing_range<false>)
{
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]")
{
Expand Down