Skip to content
Merged
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
154 changes: 95 additions & 59 deletions include/exec/repeat_until.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,21 @@
#include "../stdexec/__detail/__optional.hpp"
#include "../stdexec/execution.hpp"

#include "completion_signatures.hpp"
#include "sequence.hpp"
#include "trampoline_scheduler.hpp"

#include <exception>
#include <type_traits>

STDEXEC_PRAGMA_PUSH()
STDEXEC_PRAGMA_IGNORE_EDG(not_used_in_template_function_params)

namespace experimental::execution
{
struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_;
struct _EXPECTING_A_SENDER_OF_VOID_;

namespace __repeat
{
using namespace STDEXEC;
Expand Down Expand Up @@ -81,8 +88,7 @@ namespace experimental::execution
else
{
// Mixed results:
constexpr bool __is_nothrow = noexcept(
(static_cast<bool>(static_cast<_Booleans &&>(__bools)) && ...));
constexpr bool __is_nothrow = (std::is_nothrow_convertible_v<_Booleans, bool> && ...);
STDEXEC_TRY
{
// If the child sender completed with true, we're done
Expand Down Expand Up @@ -110,11 +116,11 @@ namespace experimental::execution

template <class _Error>
constexpr void set_error(_Error &&__err) noexcept
{ // intentionally pass-by-value
{
STDEXEC_TRY
{
auto __err_copy = static_cast<_Error &&>(__err); // make a local copy of the error...
__state_->__cleanup(); // because this could potentially invalidate it.
__state_->__cleanup(); // ... because this could potentially invalidate it.
STDEXEC::set_error(std::move(__state_->__rcvr_), static_cast<_Error &&>(__err_copy));
}
STDEXEC_CATCH_ALL
Expand All @@ -134,9 +140,9 @@ namespace experimental::execution
}

[[nodiscard]]
constexpr auto get_env() const noexcept -> env_of_t<_Receiver>
constexpr auto get_env() const noexcept -> __fwd_env_t<env_of_t<_Receiver>>
{
return STDEXEC::get_env(__state_->__rcvr_);
return __fwd_env(STDEXEC::get_env(__state_->__rcvr_));
}

__opstate_base<_Receiver> *__state_;
Expand Down Expand Up @@ -207,69 +213,97 @@ namespace experimental::execution

STDEXEC_PRAGMA_POP()

struct _EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_
{};
struct _EXPECTING_A_SENDER_OF_VOID_
{};

template <class _Child, class... _Args>
using __values_t =
// There's something funny going on with __if_c here. Use std::conditional_t instead. :-(
std::conditional_t<
((sizeof...(_Args) == 1) && (__std::convertible_to<_Args, bool> && ...)),
std::conditional_t<(__bool_constant<_Args, false> && ...),
completion_signatures<>,
completion_signatures<set_value_t()>>,
__mexception<_WHAT_(_INVALID_ARGUMENT_),
_WHERE_(_IN_ALGORITHM_, repeat_until_t),
_WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_),
_WITH_PRETTY_SENDER_<_Child>>>;

template <class... _Booleans>
using __values_overload_nothrow_bool_convertible_t =
__mand<std::is_nothrow_convertible<_Booleans, bool>...>;

template <class _Sender, class... _Env>
using __values_nothrow_bool_convertible_t =
__value_types_t<__completion_signatures_of_t<_Sender, _Env...>, // sigs
__qq<__values_overload_nothrow_bool_convertible_t>, // tuple
__qq<__mand> // variant
>;

template <typename _Sender, typename... _Env>
using __with_eptr_completion_t = __eptr_completion_unless<
__values_nothrow_bool_convertible_t<_Sender, _Env...>::value
&& __cmplsigs::__partitions_of_t<
__completion_signatures_of_t<_Sender, _Env...>>::__nothrow_decay_copyable::__errors::value
&& (__nothrow_connectable<_Sender, __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<trampoline_scheduler>, _Env...>,
__with_eptr_completion_t<_Child, _Env...>,
__delete_set_value_t>,
__mbind_front_q<__values_t, _Child>::template __f>;

struct __repeat_until_impl : __sexpr_defaults
{
template <class _Child>
static constexpr auto __transform_values = []<class... _Args>()
{
if constexpr (sizeof...(_Args) != 1 || (!__std::convertible_to<_Args, bool> || ...))
{
return exec::throw_compile_time_error<
_WHAT_(_INVALID_ARGUMENT_),
_WHERE_(_IN_ALGORITHM_, repeat_until_t),
_WHY_(_EXPECTING_A_SENDER_OF_ONE_VALUE_THAT_IS_CONVERTIBLE_TO_BOOL_),
_WITH_PRETTY_SENDER_<_Child>>();
}
else if constexpr ((__bool_constant<_Args, false> && ...))
{
return STDEXEC::completion_signatures{};
}
else if constexpr ((std::is_nothrow_convertible_v<_Args, bool> && ...))
{
return STDEXEC::completion_signatures<set_value_t()>();
}
else
{
return STDEXEC::completion_signatures<set_value_t(), set_error_t(std::exception_ptr)>();
}
};

static constexpr auto __transform_errors = []<class _Error>() noexcept
{
if constexpr (__nothrow_decay_copyable<_Error> || __decays_to<_Error, std::exception_ptr>)
{
return STDEXEC::completion_signatures<set_error_t(_Error)>();
}
else
{
return STDEXEC::completion_signatures<set_error_t(_Error),
set_error_t(std::exception_ptr)>();
}
};

template <class _Sender, class... _Env>
static consteval auto __get_completion_signatures()
{
// TODO: port this to use constant evaluation
return __completions_t<__child_of<_Sender>, _Env...>{};
using __child_t = __child_of<_Sender>;
using __bouncer_t = schedule_result_t<trampoline_scheduler>;
using __eptr_completion_t = set_error_t(std::exception_ptr);
constexpr auto __eptr_completion = (__eptr_completion_t *) nullptr;

STDEXEC_COMPLSIGS_LET(
__sigs,
exec::transform_completion_signatures(get_completion_signatures<__child_t, _Env...>(),
__transform_values<__child_t>,
__transform_errors))
{
// The repeat_until sender is a dependent sender if one of the following is
// true:
// - the child sender is a dependent sender, or
// - the trampoline scheduler's sender is a dependent sender, or
// - sizeof...(_Env) == 0 and the child sender does not have a
// set_error(exception_ptr) completion.
constexpr bool __is_dependent = (sizeof...(_Env) == 0)
&& (dependent_sender<__bouncer_t>
|| !__sigs.__contains(__eptr_completion));
if constexpr (__is_dependent)
{
return exec::throw_compile_time_error<dependent_sender_error,
_WITH_PRETTY_SENDER_<__child_t>>();
}
else
{
constexpr bool __has_nothrow_connect =
(__nothrow_connectable<__child_t, __receiver_archetype<_Env>> || ...);
constexpr auto __eptr_sigs = __eptr_completion_unless<__has_nothrow_connect>();
constexpr auto __bouncer_sigs = exec::transform_completion_signatures(
get_completion_signatures<__bouncer_t, _Env...>(),
exec::ignore_completion()); // drop the set_value_t() completion from the
// trampoline scheduler.

return exec::concat_completion_signatures(__sigs, __eptr_sigs, __bouncer_sigs);
}
}
};

static constexpr auto __connect =
[]<class _Sender, class _Receiver>(_Sender &&__sndr, _Receiver __rcvr) noexcept(
noexcept(__opstate(STDEXEC::__get<2>(__declval<_Sender>()), __declval<_Receiver>())))
__nothrow_constructible_from<__opstate<__child_of<_Sender>, _Receiver>,
__child_of<_Sender>,
_Receiver>)
{
return __opstate(STDEXEC::__get<2>(static_cast<_Sender &&>(__sndr)),
static_cast<_Receiver &&>(__rcvr));
auto &[__tag, __ign, __child] = __sndr;
return __opstate(STDEXEC::__forward_like<_Sender>(__child), std::move(__rcvr));
};
};

Expand Down Expand Up @@ -362,3 +396,5 @@ namespace STDEXEC
struct __sexpr_impl<exec::repeat_until_t> : exec::__repeat::__repeat_until_impl
{};
} // namespace STDEXEC

STDEXEC_PRAGMA_POP()
12 changes: 8 additions & 4 deletions include/exec/sequence.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ namespace experimental::execution
{
template <class Sender>
STDEXEC_ATTRIBUTE(nodiscard, host, device)
constexpr auto operator()(Sender sndr) const -> Sender;
constexpr auto operator()(Sender sndr) const
noexcept(STDEXEC::__nothrow_move_constructible<Sender>) -> Sender;

template <class... Senders>
requires(sizeof...(Senders) > 1)
STDEXEC_ATTRIBUTE(nodiscard, host, device)
constexpr auto operator()(Senders... sndrs) const -> _sndr<Senders...>;
constexpr auto operator()(Senders... sndrs) const
noexcept(STDEXEC::__nothrow_move_constructible<Senders...>) -> _sndr<Senders...>;
};

template <class Rcvr>
Expand Down Expand Up @@ -334,15 +336,17 @@ namespace experimental::execution

template <class Sender>
STDEXEC_ATTRIBUTE(host, device)
constexpr auto sequence_t::operator()(Sender sndr) const -> Sender
constexpr auto sequence_t::operator()(Sender sndr) const
noexcept(STDEXEC::__nothrow_move_constructible<Sender>) -> Sender
{
return sndr;
}

template <class... Senders>
requires(sizeof...(Senders) > 1)
STDEXEC_ATTRIBUTE(host, device)
constexpr auto sequence_t::operator()(Senders... sndrs) const -> _sndr<Senders...>
constexpr auto sequence_t::operator()(Senders... sndrs) const
noexcept(STDEXEC::__nothrow_move_constructible<Senders...>) -> _sndr<Senders...>
{
return _sndr<Senders...>{{}, {}, {static_cast<Senders&&>(sndrs)...}};
}
Expand Down
12 changes: 9 additions & 3 deletions include/stdexec/__detail/__receivers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,10 @@ namespace STDEXEC
};
}

template <class _Env>
struct __receiver_archetype
// Used to test whether a sender has a nothrow connect to a receiver whose environment
// is _Env..., or if _Env... is empty (indicating that the sender is non-dependent), to
// a receiver with an arbitrary environment.
struct __receiver_archetype_base
{
using receiver_concept = receiver_t;

Expand All @@ -267,9 +269,13 @@ namespace STDEXEC

STDEXEC_ATTRIBUTE(host, device)
constexpr void set_stopped() noexcept {}
};

template <class _Env>
struct __receiver_archetype : __receiver_archetype_base
{
STDEXEC_ATTRIBUTE(nodiscard, noreturn, host, device)
_Env get_env() const noexcept
auto get_env() const noexcept -> _Env
{
STDEXEC_ASSERT(false);
STDEXEC_TERMINATE();
Expand Down
16 changes: 8 additions & 8 deletions include/stdexec/__detail/__transform_completion_signatures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,24 +325,24 @@ namespace STDEXEC
template <class _Fn, class... _Args>
using __transform_result_t = decltype(__declval<_Fn>().template operator()<_Args...>());

template <class _SetTag, class... _Args, class _Fn>
template <class... _Args, class _Fn>
[[nodiscard]]
consteval auto
__transform_expr(_Fn const &__fn) -> __transform_result_t<_Fn const &, _SetTag, _Args...>
__transform_expr(_Fn const &__fn, int) -> __transform_result_t<_Fn const &, _Args...>
{
return __fn.template operator()<_SetTag, _Args...>();
return __fn.template operator()<_Args...>();
}

template <class _Fn>
[[nodiscard]]
consteval auto __transform_expr(_Fn const &__fn) -> __call_result_t<_Fn const &>
consteval auto __transform_expr(_Fn const &__fn, long) -> __call_result_t<_Fn const &>
{
return __fn();
}

template <class _Fn, class... _Args>
using __transform_expr_t = decltype(__cmplsigs::__transform_expr<_Args...>(
__declval<_Fn const &>()));
using __transform_expr_t =
decltype(__cmplsigs::__transform_expr<_Args...>(__declval<_Fn const &>(), 0));

// transform_completion_signatures:
template <class... _Args, class _Fn>
Expand All @@ -354,11 +354,11 @@ namespace STDEXEC
using __completions_t = __transform_expr_t<_Fn, _Args...>;
if constexpr (__well_formed_completions<__completions_t>)
{
return __cmplsigs::__transform_expr<_Args...>(__fn);
return __cmplsigs::__transform_expr<_Args...>(__fn, 0);
}
else
{
(void) __cmplsigs::__transform_expr<_Args...>(__fn); // potentially throwing
(void) __cmplsigs::__transform_expr<_Args...>(__fn, 0); // potentially throwing
return STDEXEC::__throw_compile_time_error<
_IN_TRANSFORM_COMPLETION_SIGNATURES_,
_A_TRANSFORM_FUNCTION_RETURNED_A_TYPE_THAT_IS_NOT_A_COMPLETION_SIGNATURES_SPECIALIZATION_,
Expand Down
21 changes: 20 additions & 1 deletion test/exec/test_repeat_until.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
#include <catch2/catch.hpp>

#include <concepts>
#include <cstddef>
#include <limits>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>

namespace ex = STDEXEC;
Expand Down Expand Up @@ -315,7 +317,7 @@ namespace
static_assert(
std::same_as<ex::error_types_of_t<decltype(only_stopped)>, ex::__detail::__not_a_variant>,
"Expect no value completions");
static_assert(ex::sender_of<decltype(only_stopped), ex::set_stopped_t()>,
static_assert(ex::sender_of<decltype(only_stopped), ex::set_stopped_t(), ex::env<>>,
"Missing set_stopped_t() from upstream");

// operator| and sync_wait require valid completion signatures
Expand Down Expand Up @@ -465,4 +467,21 @@ namespace
"Missing added set_error_t(std::exception_ptr)");
}
}

TEST_CASE("repeat_until works with a dependent sender", "[adaptors][repeat_until]")
{
std::size_t invoked = 0;
auto snd = exec::repeat_until(ex::read_env(ex::get_stop_token)
| ex::then(
[&](auto token) noexcept
{
++invoked;
return std::is_same_v<ex::never_stop_token, decltype(token)>;
}));
static_assert(ex::dependent_sender<decltype(snd)>);
auto op = ex::connect(std::move(snd), expect_void_receiver{});
CHECK(!invoked);
ex::start(op);
CHECK(invoked == 1);
}
} // namespace
Loading