Skip to content
Closed
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
41 changes: 30 additions & 11 deletions include/exec/repeat_n.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,20 @@ namespace exec {
constexpr explicit __opstate_base(_Receiver &&__rcvr, std::size_t __count) noexcept
: __rcvr_{static_cast<_Receiver &&>(__rcvr)}
, __count_{__count} {
static_assert(
std::is_nothrow_default_constructible_v<trampoline_scheduler>,
"trampoline_scheduler c'tor is always expected to be noexcept");
}

virtual constexpr void __cleanup() noexcept = 0;
virtual constexpr void __repeat() noexcept = 0;

_Receiver __rcvr_;
std::size_t __count_;
trampoline_scheduler __sched_;
trampoline_scheduler __sched_{};

protected:
~__opstate_base() noexcept = default;
};

template <class _ReceiverId>
Expand Down Expand Up @@ -90,16 +96,14 @@ namespace exec {
};

template <class _Child, class _Receiver>
struct __opstate : __opstate_base<_Receiver> {
struct __opstate final : __opstate_base<_Receiver> {
using __receiver_t = STDEXEC::__t<__receiver<__id<_Receiver>>>;
using __bouncy_sndr_t =
__result_of<exec::sequence, schedule_result_t<trampoline_scheduler>, _Child &>;
using __child_op_t = STDEXEC::connect_result_t<__bouncy_sndr_t, __receiver_t>;
static constexpr bool __nothrow_connect =
STDEXEC::__nothrow_connectable<__bouncy_sndr_t, __receiver_t>;

constexpr explicit __opstate(std::size_t __count, _Child __child, _Receiver __rcvr)
noexcept(__nothrow_connect)
noexcept(std::is_nothrow_move_constructible_v<_Child> && noexcept(__connect()))
: __opstate_base<_Receiver>{static_cast<_Receiver &&>(__rcvr), __count}
, __child_(std::move(__child)) {
if (this->__count_ != 0) {
Expand All @@ -115,18 +119,19 @@ namespace exec {
}
}

constexpr auto __connect() noexcept(__nothrow_connect) -> __child_op_t & {
constexpr __child_op_t &
__connect() noexcept(STDEXEC::__nothrow_connectable<__bouncy_sndr_t, __receiver_t>) {
return __child_op_.__emplace_from(
STDEXEC::connect,
exec::sequence(STDEXEC::schedule(this->__sched_), __child_),
__receiver_t{this});
}

constexpr void __cleanup() noexcept final {
constexpr void __cleanup() noexcept override {
__child_op_.reset();
}

constexpr void __repeat() noexcept final {
constexpr void __repeat() noexcept override {
STDEXEC_ASSERT(this->__count_ > 0);
STDEXEC_TRY {
if (--this->__count_ == 0) {
Expand All @@ -137,7 +142,9 @@ namespace exec {
}
}
STDEXEC_CATCH_ALL {
STDEXEC::set_error(std::move(this->__rcvr_), std::current_exception());
if constexpr (!noexcept(__connect())) {
STDEXEC::set_error(std::move(this->__rcvr_), std::current_exception());
}
}
}

Expand All @@ -164,12 +171,24 @@ namespace exec {
template <class _Error>
using __error_t = completion_signatures<set_error_t(__decay_t<_Error>)>;

template <class _Sender, class... _Env>
using __errors_nothrow_copyable = STDEXEC::__error_types_t<
STDEXEC::__completion_signatures_of_t<_Sender, _Env...>, // sigs
STDEXEC::__q<STDEXEC::__nothrow_decay_copyable_t> // variant
>;

template <class _Sender, class... _Env>
using __with_eptr_completion = STDEXEC::__eptr_completion_unless_t<STDEXEC::__mand<
__errors_nothrow_copyable<_Sender, _Env...>,
__mbool<STDEXEC::__nothrow_connectable<_Sender, STDEXEC::__receiver_archetype<_Env>>>...
>>;

template <class _Child, class... _Env>
using __completions_t = STDEXEC::transform_completion_signatures<
__completion_signatures_of_t<_Child &, _Env...>,
STDEXEC::transform_completion_signatures<
__completion_signatures_of_t<STDEXEC::schedule_result_t<exec::trampoline_scheduler>, _Env...>,
__eptr_completion,
__completion_signatures_of_t<STDEXEC::schedule_result_t<trampoline_scheduler>, _Env...>,
__with_eptr_completion<_Child, _Env...>,
__cmplsigs::__default_set_value,
__error_t
>,
Expand Down
58 changes: 45 additions & 13 deletions include/exec/repeat_until.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,21 @@ namespace exec {

template <class _Receiver>
struct __opstate_base {
constexpr explicit __opstate_base(_Receiver &&__rcvr)
: __rcvr_{static_cast<_Receiver &&>(__rcvr)} {
constexpr explicit __opstate_base(_Receiver &&__rcvr) noexcept
: __rcvr_{std::move(__rcvr)} {
static_assert(
std::is_nothrow_default_constructible_v<trampoline_scheduler>,
"trampoline_scheduler c'tor is always expected to be noexcept");
}

virtual constexpr void __cleanup() noexcept = 0;
virtual constexpr void __repeat() noexcept = 0;

_Receiver __rcvr_;
trampoline_scheduler __sched_;
trampoline_scheduler __sched_{};

protected:
~__opstate_base() noexcept = default;
};

template <class _Boolean, bool _Expected>
Expand Down Expand Up @@ -123,15 +129,13 @@ namespace exec {
STDEXEC_PRAGMA_IGNORE_GNU("-Wtsan")

template <class _Child, class _Receiver>
struct __opstate : __opstate_base<_Receiver> {
struct __opstate final : __opstate_base<_Receiver> {
using __receiver_t = STDEXEC::__t<__receiver<__id<_Receiver>>>;
using __bouncy_sndr_t =
__result_of<exec::sequence, schedule_result_t<trampoline_scheduler>, _Child &>;
using __child_op_t = STDEXEC::connect_result_t<__bouncy_sndr_t, __receiver_t>;
static constexpr bool __nothrow_connect =
STDEXEC::__nothrow_connectable<__bouncy_sndr_t, __receiver_t>;

constexpr explicit __opstate(_Child __child, _Receiver __rcvr) noexcept(__nothrow_connect)
constexpr explicit __opstate(_Child __child, _Receiver __rcvr) noexcept(noexcept(__connect()))
: __opstate_base<_Receiver>(static_cast<_Receiver &&>(__rcvr))
, __child_(static_cast<_Child &&>(__child)) {
__connect();
Expand All @@ -141,23 +145,25 @@ namespace exec {
STDEXEC::start(*__child_op_);
}

constexpr auto __connect() noexcept(__nothrow_connect) -> __child_op_t & {
constexpr __child_op_t& __connect() noexcept(STDEXEC::__nothrow_connectable<__bouncy_sndr_t, __receiver_t>) {
return __child_op_.__emplace_from(
STDEXEC::connect,
exec::sequence(STDEXEC::schedule(this->__sched_), __child_),
__receiver_t{this});
}

constexpr void __cleanup() noexcept final {
constexpr void __cleanup() noexcept override {
__child_op_.reset();
}

constexpr void __repeat() noexcept final {
constexpr void __repeat() noexcept override {
STDEXEC_TRY {
STDEXEC::start(__connect());
}
STDEXEC_CATCH_ALL {
STDEXEC::set_error(static_cast<_Receiver &&>(this->__rcvr_), std::current_exception());
if constexpr (!noexcept(__connect())) {
STDEXEC::set_error(static_cast<_Receiver &&>(this->__rcvr_), std::current_exception());
}
}
}

Expand Down Expand Up @@ -189,20 +195,46 @@ namespace exec {
>
>;

template <class... _Booleans>
using __values_overload_nothrow_bool_convertible =
STDEXEC::__mand<std::is_nothrow_convertible<_Booleans &&, bool>...>;

template <class _Sender, class... _Env>
using __values_nothrow_bool_convertible = STDEXEC::__value_types_t<
STDEXEC::__completion_signatures_of_t<_Sender, _Env...>, // sigs
STDEXEC::__qq<__values_overload_nothrow_bool_convertible>, // tuple
STDEXEC::__q<STDEXEC::__mand> // variant
>;

template <class _Sender, class... _Env>
using __errors_nothrow_copyable = STDEXEC::__error_types_t<
STDEXEC::__completion_signatures_of_t<_Sender, _Env...>, // sigs
STDEXEC::__q<STDEXEC::__nothrow_decay_copyable_t> // variant
>;

template <typename _Sender, typename... _Env>
using __with_eptr_completion = STDEXEC::__eptr_completion_unless_t<STDEXEC::__mand<
__values_nothrow_bool_convertible<_Sender, _Env...>,
__errors_nothrow_copyable<_Sender, _Env...>,
__mbool<STDEXEC::__nothrow_connectable<_Sender, STDEXEC::__receiver_archetype<_Env>>>...
>>;

template <class...>
using __delete_set_value_t = completion_signatures<>;

template <class _Child, class... _Env>
using __completions_t = STDEXEC::transform_completion_signatures<
__completion_signatures_of_t<__decay_t<_Child> &, _Env...>,
STDEXEC::transform_completion_signatures<
__completion_signatures_of_t<STDEXEC::schedule_result_t<exec::trampoline_scheduler>, _Env...>,
__eptr_completion,
__completion_signatures_of_t<STDEXEC::schedule_result_t<trampoline_scheduler>, _Env...>,
__with_eptr_completion<_Child, _Env...>,
__delete_set_value_t
>,
__mbind_front_q<__values_t, _Child>::template __f
>;

struct __repeat_until_tag { };

struct __repeat_until_impl : __sexpr_defaults {
template <class _Sender, class... _Env>
static consteval auto get_completion_signatures() {
Expand Down
43 changes: 43 additions & 0 deletions test/exec/test_repeat_n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,47 @@ namespace {
REQUIRE(called);
REQUIRE(!failed.load());
}

TEST_CASE("repeat_n conditionally adds set_error_t(exception)", "[adaptors][repeat_n]") {
// 0. ensure exception isn't always added
{
ex::sender auto snd = ex::just() | exec::repeat_n(1);
static_assert(
std::same_as<ex::error_types_of_t<decltype(snd)>, ex::__detail::__not_a_variant>,
"Expected no errors ");
}

// There are two main cases that will contribute set_error_t(std::exception_ptr)
// 1. error's copy constructor could throw
// 2. connect() could throw
{
// 1.
struct Error_with_throw_copy {
Error_with_throw_copy() noexcept = default;
Error_with_throw_copy(const Error_with_throw_copy&) noexcept(false) = default;
};
ex::sender auto snd = ex::just_error(Error_with_throw_copy{}) | exec::repeat_n(1);
static_assert(
std::same_as<
ex::error_types_of_t<decltype(snd)>,
std::variant<Error_with_throw_copy, std::exception_ptr>
>,
"Missing added set_error_t(std::exception_ptr)");
}
{
// 2.
using Sender_connect_throws = just_with_env<ex::env<>>;
static_assert(
!ex::__error_types_t<
ex::completion_signatures_of_t<Sender_connect_throws>,
ex::__mcontains<ex::set_error_t(std::exception_ptr)>
>::value,
"Sender can't already emit exception to test if repeat_until() adds it");
ex::sender auto snd = Sender_connect_throws{} | exec::repeat_n(1);
static_assert(
std::same_as<ex::error_types_of_t<decltype(snd)>, std::variant<std::exception_ptr>>,
"Missing added set_error_t(std::exception_ptr)");
}
}

} // namespace
Loading