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
38 changes: 28 additions & 10 deletions include/exec/any_sender_of.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ namespace experimental::execution
ACTUAL_QUERY_RESULT_TYPE(__call_result_t<Query, Env, Args...>)>{};

template <class Result, class Query, class... Args, class Env>
requires(!__nothrow_queryable_with<Env, Query, Args...>)
requires(!__nothrow_callable<Query, Env const &, Args...>)
inline constexpr auto _check_query_impl_v<Result(Query, Args...), true, Env> =
_ERROR_<_WHAT_(CANNOT_TYPE_ERASE_THE_GIVEN_RECEIVER),
_WHY_(THE_RECEIVERS_QUERY_IS_NOT_NOEXCEPT_BUT_THE_TYPE_ERASED_QUERY_IS),
Expand All @@ -116,7 +116,7 @@ namespace experimental::execution
// either. The constraints keep this specialization from overlapping the
// two above, which report their own errors.
template <class Result, class Query, class... Args, class Env>
requires __nothrow_queryable_with<Env, Query, Args...>
requires __nothrow_callable<Query, Env const &, Args...>
&& __std::convertible_to<__call_result_t<Query, Env, Args...>, Result>
&& (!std::is_nothrow_convertible_v<__call_result_t<Query, Env, Args...>, Result>)
inline constexpr auto _check_query_impl_v<Result(Query, Args...), true, Env> = _ERROR_<
Expand All @@ -133,21 +133,39 @@ namespace experimental::execution
_WITH_QUERY_(QueryFn),
_WITH_ENVIRONMENT_(Env...)>;

// A type-erased query is a requirement on the receiver's environment: the
// environment must answer the query, with a result that converts to the
// query's result type, and without throwing if the query is noexcept.
template <class QueryFn, class... Env>
inline constexpr auto _check_query_v = std::conditional_t<sizeof...(Env) == 0,
__mexception<dependent_sender_error>,
_no_query_error_t<QueryFn, Env...>>{};

inline constexpr auto _check_required_query_v =
std::conditional_t<sizeof...(Env) == 0,
__mexception<dependent_sender_error>,
_no_query_error_t<QueryFn, Env...>>{};

// The type-erased query answers by calling the query object on the
// receiver's environment, so check that call rather than whether the
// environment has a query member of its own: some queries, such as
// get_domain, have an answer for an environment that doesn't.
template <class Result, class Query, class... Args, class... Env>
requires(__queryable_with<Env, Query, Args...> || ...)
inline constexpr auto _check_query_v<Result(Query, Args...), Env...> =
requires(__callable<Query, Env const &, Args...> || ...)
inline constexpr auto _check_required_query_v<Result(Query, Args...), Env...> =
_check_query_impl_v<Result(Query, Args...), false, Env...>;

template <class Result, class Query, class... Args, class... Env>
requires(__queryable_with<Env, Query, Args...> || ...)
inline constexpr auto _check_query_v<Result(Query, Args...) noexcept, Env...> =
requires(__callable<Query, Env const &, Args...> || ...)
inline constexpr auto _check_required_query_v<Result(Query, Args...) noexcept, Env...> =
_check_query_impl_v<Result(Query, Args...), true, Env...>;

template <class QueryFn, class... Env>
using _check_required_query_t = __decay_t<decltype(_check_required_query_v<QueryFn, Env...>)>;

// any_sender relaxes the requirement for the stop-token queries: when the
// receiver's stop token doesn't convert, its operation state bridges stop
// requests through an inplace_stop_source or answers with a token that is
// never stopped.
template <class QueryFn, class... Env>
inline constexpr auto _check_query_v = _check_required_query_v<QueryFn, Env...>;

template <class... Env>
inline constexpr auto _check_query_v<never_stop_token(get_stop_token_t) noexcept, Env...> =
__msuccess{};
Expand Down
20 changes: 11 additions & 9 deletions include/exec/function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,29 +168,31 @@ namespace experimental::execution
template <class _Sigs, class _Queries>
using __any_receiver_ref = ::exec::_any::_any_receiver_ref<_Sigs, _Queries>;

//! The concrete receiver is stored as it is, without any_sender's stop-token
//! bridging: a declared stop-token query is a requirement on the receiver's
//! environment like any other declared query, so the type-erased receiver
//! answers it by converting the receiver's own stop token.
template <class _Receiver, class _Sigs, class _Queries>
struct __opstate_base
{
using __receiver_t = __receiver_wrapper<__any_receiver_ref<_Sigs, _Queries>>;
using __stop_token_t = stop_token_of_t<env_of_t<__receiver_t>>;
using __receiver_t = __receiver_wrapper<__any_receiver_ref<_Sigs, _Queries>>;

//! The declared get_frame_allocator query's result is what allocates, so
//! check it as the type-erased receiver's environment reports it.
static constexpr bool __nothrow_frame_allocation =
__has_nothrow_frame_allocator<env_of_t<__receiver_t>>;

_any::_state<_Receiver, __stop_token_t> __rcvr_;
_Receiver __rcvr_;
};

template <class _Receiver, class _Sigs, class _Queries>
requires(
!__queryable_with<env_of_t<__any_receiver_ref<_Sigs, _Queries>>, get_frame_allocator_t>)
struct __opstate_base<_Receiver, _Sigs, _Queries>
{
using __receiver_t = __receiver_wrapper<__any_receiver_ref<_Sigs, _Queries>>;
using __prop_t = __receiver_t::__prop_t;
using __stop_token_t = stop_token_of_t<env_of_t<__receiver_t>>;
using __adaptee_t = __result_of<__choose_frame_allocator, env_of_t<_Receiver>>;
using __receiver_t = __receiver_wrapper<__any_receiver_ref<_Sigs, _Queries>>;
using __prop_t = __receiver_t::__prop_t;
using __adaptee_t = __result_of<__choose_frame_allocator, env_of_t<_Receiver>>;

//! The injected allocator forwards to the one chosen from the concrete
//! receiver's environment, so check that one.
Expand All @@ -199,7 +201,7 @@ namespace experimental::execution

__memory_resource_adaptor_t<__adaptee_t> __resource_;
__prop_t __env_;
_any::_state<_Receiver, __stop_token_t> __rcvr_;
_Receiver __rcvr_;

explicit __opstate_base(_Receiver __rcvr)
: __resource_(__choose_frame_allocator(STDEXEC::get_env(__rcvr)))
Expand Down Expand Up @@ -335,7 +337,7 @@ namespace experimental::execution
template <class... _Queries, class... _Env>
struct __check_queries<queries<_Queries...>, _Env...>
{
using type = __mfind_error<_any::_check_query_t<_Queries, _Env...>...>;
using type = __mfind_error<_any::_check_required_query_t<_Queries, _Env...>...>;
};

template <class _Queries, class... _Env>
Expand Down
19 changes: 19 additions & 0 deletions test/exec/test_any_sender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,25 @@ namespace
ex::start(do_check);
}

TEST_CASE("any_sender - a declared query accepts an environment for which the query has a "
"default answer",
"[types][any_sender]")
{
using completions_t = ex::completion_signatures<ex::set_value_t(bool)>;
constexpr auto domain_query = ex::get_domain.signature<ex::default_domain() noexcept>;

using sender = exec::any_receiver_ref<completions_t, domain_query>::any_sender<>;

// ex::env<> has no get_domain member, but ex::get_domain answers
// ex::default_domain for it
STATIC_REQUIRE(ex::sender_in<sender, ex::env<>>);

sender sndr = ex::read_env(ex::get_domain)
| ex::then([](ex::default_domain) noexcept { return true; });
auto [answered] = ex::sync_wait(std::move(sndr)).value();
CHECK(answered);
}

TEST_CASE("any_sender - get_completion_signatures is constrained with respect to stop-token"
"ex::receiver stop_token queries",
"[types][any_sender]")
Expand Down
70 changes: 70 additions & 0 deletions test/exec/test_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <exec/function.hpp>

#include <test_common/catch2.hpp>
#include <test_common/receivers.hpp>
#include <test_common/schedulers.hpp>

#include <stdexec/execution.hpp>

Expand Down Expand Up @@ -702,6 +704,74 @@ namespace
STATIC_REQUIRE(!std::constructible_from<fn, decltype(takes_string)>);
}

TEST_CASE("function's declared query accepts an environment for which the query has a default "
"answer",
"[types][function]")
{
// ex::env<> has no get_domain member, but ex::get_domain answers
// ex::default_domain for it
using fn = exec::function<int(), exec::queries<ex::default_domain(ex::get_domain_t) noexcept>>;
STATIC_REQUIRE(ex::sender_in<fn, ex::env<>>);
}

TEST_CASE("function forwards the receiver's stop token through a declared stop-token query",
"[types][function]")
{
using fn = exec::function<ex::sender_tag(impulse_scheduler),
ex::completion_signatures<ex::set_value_t(), ex::set_stopped_t()>,
exec::queries<ex::inplace_stop_token(ex::get_stop_token_t) noexcept>>;

auto factory = [](impulse_scheduler sched) noexcept
{
return ex::schedule(sched);
};

impulse_scheduler sched;
ex::inplace_stop_source source;
auto env = ex::prop{ex::get_stop_token, source.get_token()};

SECTION("a stop request made after start reaches the implementation")
{
auto op = ex::connect(fn(impulse_scheduler(sched), factory), expect_stopped_receiver{env});
ex::start(op);
source.request_stop();
sched.start_next();
}

SECTION("without a stop request, the implementation completes normally")
{
auto op = ex::connect(fn(impulse_scheduler(sched), factory), expect_void_receiver{env});
ex::start(op);
sched.start_next();
}
}

TEST_CASE("function rejects a receiver whose stop token doesn't convert to the declared one",
"[types][function]")
{
using inplace_env = ex::prop<ex::get_stop_token_t, ex::inplace_stop_token>;
using never_env = ex::prop<ex::get_stop_token_t, ex::never_stop_token>;

SECTION("a declared inplace_stop_token requires one from the receiver")
{
using fn =
exec::function<int(), exec::queries<ex::inplace_stop_token(ex::get_stop_token_t) noexcept>>;
STATIC_REQUIRE(ex::sender_in<fn, inplace_env>);
STATIC_REQUIRE(!ex::sender_in<fn, never_env>);
// ex::get_stop_token answers ex::never_stop_token for ex::env<>
STATIC_REQUIRE(!ex::sender_in<fn, ex::env<>>);
}

SECTION("a declared never_stop_token accepts an environment without a stop token")
{
using fn =
exec::function<int(), exec::queries<ex::never_stop_token(ex::get_stop_token_t) noexcept>>;
STATIC_REQUIRE(ex::sender_in<fn, never_env>);
STATIC_REQUIRE(ex::sender_in<fn, ex::env<>>);
STATIC_REQUIRE(!ex::sender_in<fn, inplace_env>);
}
}

#if !STDEXEC_NO_STDCPP_EXCEPTIONS()
TEST_CASE("an exception thrown by the sender factory propagates out of connect",
"[types][function]")
Expand Down