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
40 changes: 20 additions & 20 deletions include/exec/when_any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,39 +34,39 @@ namespace experimental::execution
template <class _Env>
using __env_t = __join_env_t<prop<get_stop_token_t, inplace_stop_token>, _Env>;

template <class... _Ts>
using __nothrow_decay_copyable_and_move_constructible_t = __mbool<(
(__nothrow_decay_copyable<_Ts> && __nothrow_move_constructible<__decay_t<_Ts>>) && ...)>;

template <class... Args>
using __as_rvalues = set_value_t (*)(__decay_t<Args>...);

template <class... E>
using __as_error = set_error_t (*)(E...);
using __as_error = set_error_t (*)(__decay_t<E>...);

// Here we convert all set_value(Args...) to set_value(__decay_t<Args>...). 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 <class... _Env>
struct __completions_fn
{
template <class _CvSender>
using __raw_completions_t = __completion_signatures_of_t<_CvSender, __env_t<_Env>...>;

template <class _CvSender>
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 <class... _CvSenders>
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 <class... _CvSenders>
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<set_stopped_t()>,
__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 <class _Env, class... _CvSenders>
Expand Down
281 changes: 281 additions & 0 deletions test/exec/test_when_any.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,213 @@ using namespace STDEXEC;
namespace
{

struct error_ref_sender
{
struct error
{};

using sender_concept = ex::sender_tag;

template <class...>
static consteval auto
get_completion_signatures() noexcept -> ex::completion_signatures<ex::set_error_t(error&)>
{
return {};
}

template <class Receiver>
struct operation
{
Receiver rcvr_;
error error_;

void start() & noexcept
{
ex::set_error(static_cast<Receiver&&>(rcvr_), error_);
}
};

template <class Receiver>
auto connect(Receiver rcvr) && noexcept -> operation<Receiver>
{
return {static_cast<Receiver&&>(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 <class...>
static consteval auto get_completion_signatures() noexcept
-> ex::completion_signatures<ex::set_error_t(copy_noexcept_move_throws&)>
{
return {};
}

template <class Receiver>
struct operation
{
Receiver rcvr_;
copy_noexcept_move_throws error_;

void start() & noexcept
{
ex::set_error(static_cast<Receiver&&>(rcvr_), error_);
}
};

template <class Receiver>
auto connect(Receiver rcvr) && noexcept -> operation<Receiver>
{
return {static_cast<Receiver&&>(rcvr), {}};
}
};

template <class Type>
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 <class...>
static consteval auto get_completion_signatures() noexcept
-> ex::completion_signatures<ex::set_error_t(throwing_error&)>
{
return {};
}

template <class Receiver>
struct operation
{
Receiver rcvr_;
throwing_error error_;

void start() & noexcept
{
ex::set_error(static_cast<Receiver&&>(rcvr_), error_);
}
};

template <class Receiver>
auto connect(Receiver rcvr) && noexcept -> operation<Receiver>
{
return {static_cast<Receiver&&>(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));
Expand Down Expand Up @@ -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_of_t<decltype(snd)>,
completion_signatures<set_error_t(error_ref_sender::error), set_stopped_t()>>);

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_of_t<decltype(snd)>,
completion_signatures<set_error_t(throwing_error),
set_error_t(std::exception_ptr),
set_stopped_t()>>);

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_of_t<decltype(snd)>,
completion_signatures<set_value_t(copy_noexcept_move_throws),
set_stopped_t(),
set_error_t(std::exception_ptr)>>);

bool got_value = false;
bool got_error = false;
bool got_exception = false;
auto op = ex::connect(std::move(snd),
throwing_move_receiver<copy_noexcept_move_throws>{&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_of_t<decltype(snd)>,
completion_signatures<set_error_t(copy_noexcept_move_throws),
set_error_t(std::exception_ptr),
set_stopped_t()>>);

bool got_value = false;
bool got_error = false;
bool got_exception = false;
auto op = ex::connect(std::move(snd),
throwing_move_receiver<copy_noexcept_move_throws>{&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 <class Receiver>
struct dup_op
Expand Down
Loading