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
214 changes: 188 additions & 26 deletions include/exec/function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "../stdexec/__detail/__completion_signatures.hpp"
#include "../stdexec/__detail/__concepts.hpp"
#include "../stdexec/__detail/__domain.hpp"
#include "../stdexec/__detail/__meta.hpp"
#include "../stdexec/__detail/__read_env.hpp"
#include "../stdexec/__detail/__receivers.hpp"
Expand Down Expand Up @@ -55,6 +56,11 @@
// queries to pick the frame allocator from the environment without relying on TLS.
namespace experimental::execution
{
// for specifying required sender attributes in exec::function
template <_query::_query_signature... Sigs>
struct attrs
{};

namespace __func
{
using namespace STDEXEC;
Expand Down Expand Up @@ -106,13 +112,13 @@ namespace experimental::execution
{}

constexpr auto get_env() const noexcept //
-> __join_env_t<__prop_t, env_of_t<_Receiver>>
-> __join_env_t<__prop_t const &, env_of_t<_Receiver>>
{
return __env::__join(*__env_, STDEXEC::get_env(*static_cast<_Receiver const *>(this)));
}

private:
__prop_t *__env_;
__prop_t const *__env_;
Comment on lines +115 to +121
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericniebler, what do you think of this change? It resolves a compiler error where __join receives a __prop_t& but is expecting a __prop_t and can't find a conversion. The other thing that worked was to invoke __join like so:

return __env::__join(auto(*__env_), …);

which seemed like a bunch of unnecessary copying.

};

template <class _Sigs, class _Queries>
Expand Down Expand Up @@ -192,7 +198,74 @@ namespace experimental::execution
}
};

template <class _Sigs, class _Queries, class... _Args>
template <class _Tag, class _Query>
struct __make_domain_impl
{};

template <class _Domain, class _Tag>
struct __make_domain_impl<_Tag, _Domain(get_completion_domain_t<_Tag>) noexcept>
{
constexpr _Domain operator()() const noexcept
{
return _Domain();
}
};

//! get_completion_domain<> is a special case; its type parameter is void
//! and it's equivalent to get_completion_domain<set_value_t>.
template <class _Domain>
struct __make_domain_impl<void, _Domain(get_completion_domain_t<set_value_t>) noexcept>
: __make_domain_impl<set_value_t, _Domain(get_completion_domain_t<set_value_t>) noexcept>
{};

//! get_completion_domain ought to be no-throw, so make it optional to specify
//! noexcept on the signature provided with attrs<...>
template <class _Domain, class _Tag1, class _Tag2>
struct __make_domain_impl<_Tag1, _Domain(get_completion_domain_t<_Tag2>)>
: __make_domain_impl<_Tag1, _Domain(get_completion_domain_t<_Tag2>) noexcept>
{};

template <class _Tag, class... _Attrs>
inline constexpr auto __make_domain = __first_callable<__make_domain_impl<_Tag, _Attrs>...>();

template <class... _Attrs>
struct __attrs
{
template <class _Tag, class... _Env>
constexpr auto query(get_completion_domain_t<_Tag>, _Env &&...) const noexcept
-> decltype(__make_domain<_Tag, _Attrs...>())
{
return __make_domain<_Tag, _Attrs...>();
}
};

template <class _Tag, class _Attrs, class... _Env>
using __completion_domain_t = __call_result_or_t<
get_completion_domain_t<_Tag>,
__call_result_or_t<get_completion_domain_t<_Tag>, indeterminate_domain<>, _Attrs>,
_Attrs,
_Env const &...>;

template <class _ActualDomain, class _ExpectedDomain>
concept __completion_domain_matches_impl =
__same_as<_ExpectedDomain, __common_domain_t<_ActualDomain, _ExpectedDomain>>;

template <class _Tag, class _ActualAttrs, class _ExpectedAttrs, class... _Env>
concept __completion_domain_matches =
__completion_domain_matches_impl<__completion_domain_t<_Tag, _ActualAttrs, _Env...>,
__completion_domain_t<_Tag, _ExpectedAttrs, _Env...>>;

template <class _ActualAttrs, class _ExpectedAttrs, class... _Env>
concept __completion_domains_match_impl =
__completion_domain_matches<set_value_t, _ActualAttrs, _ExpectedAttrs, _Env...>
&& __completion_domain_matches<set_error_t, _ActualAttrs, _ExpectedAttrs, _Env...>
&& __completion_domain_matches<set_stopped_t, _ActualAttrs, _ExpectedAttrs, _Env...>;

template <class _Actual, class _Expected, class... _Env>
concept __completion_domains_match =
__completion_domains_match_impl<env_of_t<_Actual>, env_of_t<_Expected>, _Env...>;

template <class _Sigs, class _Queries, class _Attrs, class... _Args>
class __function;

//! the main implementation of the type-erasing sender function<...>
Expand All @@ -206,8 +279,8 @@ namespace experimental::execution
//! not, as appropriate
//!
//! \tparam _Args The argument types used to construct the erased sender
template <class _Sigs, class... _Queries, class... _Args>
class __function<_Sigs, queries<_Queries...>, _Args...>
template <class _Sigs, class... _Queries, class... _Attrs, class... _Args>
class __function<_Sigs, queries<_Queries...>, attrs<_Attrs...>, _Args...>
{
using __receiver_t = __receiver_wrapper<__any_receiver_ref<_Sigs, queries<_Queries...>>>;

Expand All @@ -220,8 +293,9 @@ namespace experimental::execution
-> _any::_any_opstate_base
{
auto &__make_sender = *__std::start_lifetime_as<_Factory>(__storage);
using __alloc_t = decltype(__choose_frame_allocator(get_env(__rcvr)));
auto __alloc = __frame_allocator_t<__alloc_t>(__choose_frame_allocator(get_env(__rcvr)));
using __alloc_t = decltype(__choose_frame_allocator(STDEXEC::get_env(__rcvr)));
auto __alloc = __frame_allocator_t<__alloc_t>(
__choose_frame_allocator(STDEXEC::get_env(__rcvr)));
return _any::_any_opstate_base(__in_place_from,
std::allocator_arg,
__alloc,
Expand Down Expand Up @@ -257,6 +331,9 @@ namespace experimental::execution
&& (STDEXEC_IS_TRIVIALLY_COPYABLE(_Factory)) //
&& (sizeof(_Factory) <= sizeof(__make_sender_)) //
&& sender_to<__invoke_result_t<_Factory, _Args...>, __receiver_t>
&& __completion_domains_match<__invoke_result_t<_Factory, _Args...>,
__function,
env_of_t<__receiver_t>>
constexpr explicit __function(_Args &&...__args, _Factory __factory)
noexcept(__nothrow_move_constructible<_Args...>)
: __args_(static_cast<_Args &&>(__args)...)
Expand All @@ -279,6 +356,11 @@ namespace experimental::execution
return _Sigs();
}

constexpr __attrs<_Attrs...> get_env() const noexcept
{
return {};
}

template <class _Receiver>
constexpr auto connect(_Receiver __rcvr) && //
-> __opstate_t<_Receiver>
Expand Down Expand Up @@ -342,6 +424,46 @@ namespace experimental::execution
completion_signatures<__single_value_sig_t<_Return>, set_stopped_t()>,
__eptr_completion_unless_t<__mbool<_NoExcept>>>>;

//! maps a completion signature to the default completion domain query
struct __domain_query_from_sig
{
template <class _Tag, class... _Args>
consteval auto operator()(_Tag (*)(_Args...)) const noexcept //
-> default_domain (*)(get_completion_domain_t<_Tag>) noexcept
{
return nullptr;
}
};

//! maps a pack of domain queries produced by __domain_query_from_sig to the
//! corresponding attrs<_Attrs...> type
class __attrs_from_domain_queries
{
template <class _Tag>
using __query_sig = default_domain (*)(get_completion_domain_t<_Tag>) noexcept;

public:
template <class... _Tag>
consteval auto operator()(__query_sig<_Tag>...) const noexcept //
-> __canonical_t<attrs<default_domain(get_completion_domain_t<_Tag>) noexcept...>>
{
return {};
}
};

//! computes the set of get_completion_domain queries that must be supported by any
//! sender that might be erased by the corresponding function
//!
//! we should support get_completion_domain<Tag> only if _Sigs contains a completion
//! of type Tag
//!
//! the query form should be
//!
//! default_domain(get_completion_domain_t<Tag>)
template <class _Sigs>
using __default_attrs = decltype(_Sigs::__transform_reduce(__domain_query_from_sig(),
__attrs_from_domain_queries()));

//! Map a variety of function<...> specifications into the canonical type-erased
//! contract represented by the user-provided specification.
//!
Expand All @@ -362,48 +484,88 @@ namespace experimental::execution
//! The order of Args... is obviously important, but Sigs... and Queries... are both
//! canonicalized into a sorted and uniqued list to ensure order is irrelevant.
template <class...>
struct __make_function;
class __make_function;

template <class _Return, class... _Args>
struct __make_function<_Return(_Args...)>
class __make_function<_Return(_Args...)>
{
using type = __function<__sigs_from_t<_Return, false>, queries<>, _Args...>;
using __sigs = __sigs_from_t<_Return, false>;
using __queries = queries<>;
using __attrs = __default_attrs<__sigs>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};

template <class _Return, class... _Args>
struct __make_function<_Return(_Args...) noexcept>
class __make_function<_Return(_Args...) noexcept>
{
using type = __function<__sigs_from_t<_Return, true>, queries<>, _Args...>;
using __sigs = __sigs_from_t<_Return, true>;
using __queries = queries<>;
using __attrs = __default_attrs<__sigs>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};

template <class... _Args, class... _Sigs>
struct __make_function<sender_tag(_Args...), completion_signatures<_Sigs...>>
class __make_function<sender_tag(_Args...), completion_signatures<_Sigs...>>
{
using type = __function<__canonical_t<completion_signatures<_Sigs...>>, queries<>, _Args...>;
using __sigs = __canonical_t<completion_signatures<_Sigs...>>;
using __queries = queries<>;
using __attrs = __default_attrs<__sigs>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};

template <class _Return, class... _Args, class... _Queries>
struct __make_function<_Return(_Args...), queries<_Queries...>>
class __make_function<_Return(_Args...), queries<_Queries...>>
{
using type =
__function<__sigs_from_t<_Return, false>, __canonical_t<queries<_Queries...>>, _Args...>;
using __sigs = __sigs_from_t<_Return, false>;
using __queries = __canonical_t<queries<_Queries...>>;
using __attrs = __default_attrs<__sigs>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};

template <class _Return, class... _Args, class... _Queries>
struct __make_function<_Return(_Args...) noexcept, queries<_Queries...>>
class __make_function<_Return(_Args...) noexcept, queries<_Queries...>>
{
using type =
__function<__sigs_from_t<_Return, true>, __canonical_t<queries<_Queries...>>, _Args...>;
using __sigs = __sigs_from_t<_Return, true>;
using __queries = __canonical_t<queries<_Queries...>>;
using __attrs = __default_attrs<__sigs>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};

template <class... _Args, class... _Sigs, class... _Queries>
struct __make_function<sender_tag(_Args...),
completion_signatures<_Sigs...>,
queries<_Queries...>>
class __make_function<sender_tag(_Args...),
completion_signatures<_Sigs...>,
queries<_Queries...>>
{
using __sigs = __canonical_t<completion_signatures<_Sigs...>>;
using __queries = __canonical_t<queries<_Queries...>>;
using __attrs = __default_attrs<__sigs>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};

template <class... _Args, class... _Sigs, class... _Queries, class... _Attrs>
class __make_function<sender_tag(_Args...),
completion_signatures<_Sigs...>,
queries<_Queries...>,
attrs<_Attrs...>>
{
using type = __function<__canonical_t<completion_signatures<_Sigs...>>,
__canonical_t<queries<_Queries...>>,
_Args...>;
using __sigs = __canonical_t<completion_signatures<_Sigs...>>;
using __queries = __canonical_t<queries<_Queries...>>;
using __attrs = __canonical_t<attrs<_Attrs...>>;

public:
using type = __function<__sigs, __queries, __attrs, _Args...>;
};
} // namespace __func

Expand Down
Loading