Skip to content

Commit

Permalink
Add support for function references (free and static member) in bind_…
Browse files Browse the repository at this point in the history
…back (#605)

* Use std::is_invocable variants

These are drop in replacements of is_callable variants and we can
replace our concept shim with them as well.

* Use ASSERT_TRUE with optional/expected type has_value assertions

These assertions are usually followed by other expressions
assuming that they are passing and in case a change breaks them
they potentially trigger undefined behavior with `EXCEPT_TRUE`.

* Add support for function references (free and static member) in bind_back

The following program does not compile: https://godbolt.org/z/WfhEc51dY
But if we replace the function reference with a lambda, it does: https://godbolt.org/z/jrhTY4nse
Constructing a std::tuple with a function type fails but function pointers are allowed.
We instantiate _result with std::decay_t<ArgN>... which maps function references to function pointers
and then the corresponding std::tuple instantiation does not fail to compile. The problem with the
existing code is that, in order to instantiate std::tuple<std::decay_t<ArgN>...>, we first constructed a
std::tuple<ArgN> and passed it over. If instead we construct a std::tuple<std::decay_t<ArgN>...>
with the function arguments, they will be converted to function pointers and the std::tuple instantiation
will compile.
  • Loading branch information
denizevrenci committed Apr 12, 2024
1 parent 8a8486d commit 1b66205
Show file tree
Hide file tree
Showing 83 changed files with 365 additions and 399 deletions.
2 changes: 1 addition & 1 deletion include/unifex/allocate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ inline const struct _fn {
return _alloc::sender<Sender>{(Sender &&) predecessor};
}
constexpr auto operator()() const
noexcept(is_nothrow_callable_v<tag_t<bind_back>, _fn>)
noexcept(std::is_nothrow_invocable_v<tag_t<bind_back>, _fn>)
-> bind_back_result_t<_fn> {
return bind_back(*this);
}
Expand Down
4 changes: 2 additions & 2 deletions include/unifex/any_scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ struct _with<CPOs...>::any_scheduler {

template(typename Receiver) //
(requires receiver_of<Receiver> AND(
invocable<CPOs, const Receiver&>&&...)) //
std::is_invocable_v<CPOs, const Receiver&>&&...)) //
any_operation_state_for<Receiver> connect(Receiver rec) && {
any_scheduler_impl<CPOs...> const& impl = sched_.impl_;
return any_operation_state_for<Receiver>{
Expand Down Expand Up @@ -281,7 +281,7 @@ struct _with<CPOs...>::any_scheduler_ref {

template(typename Receiver) //
(requires receiver_of<Receiver> AND(
invocable<CPOs, const Receiver&>&&...)) //
std::is_invocable_v<CPOs, const Receiver&>&&...)) //
any_operation_state_for<Receiver> connect(Receiver rec) && {
any_scheduler_ref_impl<CPOs...> const& impl = sched_.impl_;
return any_operation_state_for<Receiver>{
Expand Down
12 changes: 6 additions & 6 deletions include/unifex/any_sender_of.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,20 @@ struct _op_for<Receiver>::type {
// to the receiver after unsubscribing the stop token.
template(typename CPO, typename... Args) //
(requires is_receiver_cpo_v<CPO> AND
is_callable_v<CPO, Receiver, Args...>) //
std::is_invocable_v<CPO, Receiver, Args...>) //
friend void tag_invoke(CPO cpo, type&& self, Args&&... args) noexcept(
is_nothrow_callable_v<CPO, Receiver, Args...>) {
std::is_nothrow_invocable_v<CPO, Receiver, Args...>) {
self.subscription_.unsubscribe();
cpo(std::move(self).rec_, (Args &&) args...);
}

// Forward other receiver queries
template(typename CPO) //
(requires is_receiver_query_cpo_v<CPO> AND
is_callable_v<CPO, const Receiver&>) //
std::is_invocable_v<CPO, const Receiver&>) //
friend auto tag_invoke(CPO cpo, const type& self) noexcept(
is_nothrow_callable_v<CPO, const Receiver&>)
-> callable_result_t<CPO, const Receiver&> {
std::is_nothrow_invocable_v<CPO, const Receiver&>)
-> std::invoke_result_t<CPO, const Receiver&> {
return std::move(cpo)(self.rec_);
}

Expand Down Expand Up @@ -238,7 +238,7 @@ struct _with<CPOs...>::_sender<Values...>::type

template(typename Receiver) //
(requires receiver_of<Receiver, Values...> AND(
invocable<CPOs, Receiver const&>&&...)) //
std::is_invocable_v<CPOs, Receiver const&>&&...)) //
_operation_state_for<Receiver> connect(Receiver r) && {
any_unique_t<_connect<type_list<CPOs...>, Values...>>& self = *this;
return _operation_state_for<Receiver>{
Expand Down
8 changes: 4 additions & 4 deletions include/unifex/at_coroutine_exit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ struct _die_on_done_rec {

template(typename CPO) //
(requires is_receiver_query_cpo_v<CPO> AND
is_callable_v<CPO, const Receiver&>) //
std::is_invocable_v<CPO, const Receiver&>) //
friend auto tag_invoke(CPO cpo, const type& p) noexcept(
is_nothrow_callable_v<CPO, const Receiver&>)
-> callable_result_t<CPO, const Receiver&> {
std::is_nothrow_invocable_v<CPO, const Receiver&>)
-> std::invoke_result_t<CPO, const Receiver&> {
return cpo(p.rec_);
}
};
Expand Down Expand Up @@ -313,7 +313,7 @@ inline constexpr struct _fn {

public:
template(typename Action, typename... Ts) //
(requires callable<std::decay_t<Action>, std::decay_t<Ts>...>) //
(requires std::is_invocable_v<std::decay_t<Action>, std::decay_t<Ts>...>) //
_cleanup_task<std::decay_t<Ts>...>
operator()(Action&& action, Ts&&... ts) const {
return _fn::at_coroutine_exit((Action &&) action, (Ts &&) ts...);
Expand Down
6 changes: 3 additions & 3 deletions include/unifex/await_transform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ struct _awaitable_base<Promise, Value>::type {

template(typename CPO) //
(requires is_receiver_query_cpo_v<CPO> AND
is_callable_v<CPO, const Promise&>) //
std::is_invocable_v<CPO, const Promise&>) //
friend auto tag_invoke(CPO cpo, const _rec& r) noexcept(
is_nothrow_callable_v<CPO, const Promise&>)
-> callable_result_t<CPO, const Promise&> {
std::is_nothrow_invocable_v<CPO, const Promise&>)
-> std::invoke_result_t<CPO, const Promise&> {
const Promise& p = r.continuation_.promise();
return std::move(cpo)(p);
}
Expand Down
30 changes: 16 additions & 14 deletions include/unifex/bind_back.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ namespace _compose {
struct _fn {
template <typename Target, typename Other, typename Self>
auto operator()(Target&& target, Other&& other, Self&& self) const noexcept(
is_nothrow_callable_v<Other, Target>&&
is_nothrow_callable_v<Self, callable_result_t<Other, Target>>)
-> callable_result_t<Self, callable_result_t<Other, Target>> {
std::is_nothrow_invocable_v<Other, Target>&&
std::is_nothrow_invocable_v<Self, std::invoke_result_t<Other, Target>>)
-> std::invoke_result_t<Self, std::invoke_result_t<Other, Target>> {
return ((Self &&) self)(((Other &&) other)((Target &&) target));
}
};
Expand All @@ -56,11 +56,11 @@ struct _apply_fn_impl<Cpo, Target>::type {
Target&& target_;

template(typename... ArgN) //
(requires callable<Cpo, Target, ArgN...>) //
(requires std::is_invocable_v<Cpo, Target, ArgN...>) //
auto
operator()(ArgN&&... argN) noexcept(
is_nothrow_callable_v<Cpo, Target, ArgN...>)
-> callable_result_t<Cpo, Target, ArgN...> {
std::is_nothrow_invocable_v<Cpo, Target, ArgN...>)
-> std::invoke_result_t<Cpo, Target, ArgN...> {
return ((Cpo &&) cpo_)((Target &&) target_, (ArgN &&) argN...);
}
};
Expand All @@ -82,19 +82,19 @@ struct _result_impl<Cpo, ArgN...>::type : _result_base {

template(typename Target) //
(requires(!derived_from<remove_cvref_t<Target>, _result_base>)
AND callable<Cpo const&, Target, ArgN const&...>) //
AND std::is_invocable_v<Cpo const&, Target, ArgN const&...>) //
decltype(auto)
operator()(Target&& target) const& noexcept(
is_nothrow_callable_v<Cpo const&, Target, ArgN const&...>) {
std::is_nothrow_invocable_v<Cpo const&, Target, ArgN const&...>) {
return std::apply(
_apply_fn<Cpo const&, Target>{cpo_, (Target &&) target}, argN_);
}
template(typename Target) //
(requires(!derived_from<remove_cvref_t<Target>, _result_base>)
AND callable<Cpo, Target, ArgN...>) //
AND std::is_invocable_v<Cpo, Target, ArgN...>) //
decltype(auto)
operator()(Target&& target) && noexcept(
is_nothrow_callable_v<Cpo, Target, ArgN...>) {
std::is_nothrow_invocable_v<Cpo, Target, ArgN...>) {
return std::apply(
_apply_fn<Cpo, Target>{(Cpo &&) cpo_, (Target &&) target},
std::move(argN_));
Expand All @@ -104,10 +104,10 @@ struct _result_impl<Cpo, ArgN...>::type : _result_base {
(requires(!derived_from<
remove_cvref_t<Target>,
_result_base>) AND same_as<remove_cvref_t<Self>, type> AND
callable<member_t<Self, Cpo>, Target, member_t<Self, ArgN>...>) //
std::is_invocable_v<member_t<Self, Cpo>, Target, member_t<Self, ArgN>...>) //
friend decltype(auto)
operator|(Target&& target, Self&& self) noexcept(
is_nothrow_callable_v<
std::is_nothrow_invocable_v<
member_t<Self, Cpo>,
Target,
member_t<Self, ArgN>...>) {
Expand Down Expand Up @@ -137,10 +137,12 @@ inline const struct _fn {
template <typename Cpo, typename... ArgN>
constexpr auto operator()(Cpo cpo, ArgN&&... argN) const
noexcept(noexcept(_result<Cpo, std::decay_t<ArgN>...>{
{}, (Cpo &&) cpo, std::tuple{(ArgN &&) argN...}}))
{},
(Cpo &&) cpo,
std::tuple<std::decay_t<ArgN>...>{(ArgN &&) argN...}}))
-> _result<Cpo, std::decay_t<ArgN>...> {
return _result<Cpo, std::decay_t<ArgN>...>{
{}, (Cpo &&) cpo, std::tuple{(ArgN &&) argN...}};
{}, (Cpo &&) cpo, std::tuple<std::decay_t<ArgN>...>{(ArgN &&) argN...}};
}
} bind_back{};

Expand Down
6 changes: 3 additions & 3 deletions include/unifex/bulk_join.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ class _join_receiver<Receiver>::type {
Self,
type>) //
friend auto tag_invoke(CPO cpo, const Self& self) noexcept(
is_nothrow_callable_v<CPO, const Receiver&>)
-> callable_result_t<CPO, const Receiver&> {
std::is_nothrow_invocable_v<CPO, const Receiver&>)
-> std::invoke_result_t<CPO, const Receiver&> {
return cpo(self.receiver_);
}

Expand Down Expand Up @@ -166,7 +166,7 @@ struct _fn {
return join_sender<remove_cvref_t<Source>>{(Source &&) source};
}
constexpr auto operator()() const
noexcept(is_nothrow_callable_v<tag_t<bind_back>, _fn>)
noexcept(std::is_nothrow_invocable_v<tag_t<bind_back>, _fn>)
-> bind_back_result_t<_fn> {
return bind_back(*this);
}
Expand Down
2 changes: 1 addition & 1 deletion include/unifex/bulk_schedule.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ struct _fn {
}
template <typename Integral>
constexpr auto operator()(Integral n) const
noexcept(is_nothrow_callable_v<tag_t<bind_back>, _fn, Integral>)
noexcept(std::is_nothrow_invocable_v<tag_t<bind_back>, _fn, Integral>)
-> bind_back_result_t<_fn, Integral> {
return bind_back(*this, n);
}
Expand Down
16 changes: 8 additions & 8 deletions include/unifex/bulk_transform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class _tfx_receiver<Func, Policy, Receiver>::type {
, policy_(std::move(policy)) {}

template(typename... Values) //
(requires invocable<Func&, Values...> AND
(requires std::is_invocable_v<Func&, Values...> AND
std::is_void_v<std::invoke_result_t<
Func&,
Values...>>) //
Expand All @@ -59,7 +59,7 @@ class _tfx_receiver<Func, Policy, Receiver>::type {
}

template(typename... Values) //
(requires invocable<Func&, Values...> AND(
(requires std::is_invocable_v<Func&, Values...> AND(
!std::is_void_v<std::invoke_result_t<
Func&,
Values...>>)) //
Expand Down Expand Up @@ -115,8 +115,8 @@ class _tfx_receiver<Func, Policy, Receiver>::type {
Self,
type>) //
friend auto tag_invoke(CPO cpo, const Self& self) noexcept(
is_nothrow_callable_v<CPO, const Receiver&>)
-> callable_result_t<CPO, const Receiver&> {
std::is_nothrow_invocable_v<CPO, const Receiver&>)
-> std::invoke_result_t<CPO, const Receiver&> {
return cpo(self.receiver_);
}

Expand Down Expand Up @@ -238,8 +238,8 @@ struct _fn {
(requires bulk_sender<Source>) //
auto
operator()(Source&& s, Func&& f) const
noexcept(is_nothrow_callable_v<_fn, Source, Func, FuncPolicy>)
-> callable_result_t<_fn, Source, Func, FuncPolicy> {
noexcept(std::is_nothrow_invocable_v<_fn, Source, Func, FuncPolicy>)
-> std::invoke_result_t<_fn, Source, Func, FuncPolicy> {
return operator()((Source &&) s, (Func &&) f, get_execution_policy(f));
}

Expand Down Expand Up @@ -271,13 +271,13 @@ struct _fn {
}
template <typename Func>
constexpr auto operator()(Func&& f) const
noexcept(is_nothrow_callable_v<tag_t<bind_back>, _fn, Func>)
noexcept(std::is_nothrow_invocable_v<tag_t<bind_back>, _fn, Func>)
-> bind_back_result_t<_fn, Func> {
return bind_back(*this, (Func &&) f);
}
template <typename Func, typename FuncPolicy>
constexpr auto operator()(Func&& f, FuncPolicy policy) const
noexcept(is_nothrow_callable_v<tag_t<bind_back>, _fn, Func, FuncPolicy>)
noexcept(std::is_nothrow_invocable_v<tag_t<bind_back>, _fn, Func, FuncPolicy>)
-> bind_back_result_t<_fn, Func, FuncPolicy> {
return bind_back(*this, (Func &&) f, (FuncPolicy &&) policy);
}
Expand Down
4 changes: 2 additions & 2 deletions include/unifex/config.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
// class foo_sender {
// template<
// typename Receiver,
// std::enable_if_t<is_callable_v<tag_t<set_value>, Receiver, foo>, int> = 0>
// std::enable_if_t<std::is_invocable_v<tag_t<set_value>, Receiver, foo>, int> = 0>
// friend auto tag_invoke(tag_t<connect>, foo_sender&& s, Receiver&& r) -> foo_operation<Receiver> {
// return ...;
// }
Expand All @@ -139,7 +139,7 @@
// typename Receiver,
// UNIFEX_DECLARE_NON_DEDUCED_TYPE(CPO, tag_t<connect>),
// UNIFEX_DECLARE_NON_DEDUCED_TYPE(S, foo_sender),
// std::enable_if_t<is_callable_v<tag_t<set_value>, Receiver, foo>, int> = 0?
// std::enable_if_t<std::is_invocable_v<tag_t<set_value>, Receiver, foo>, int> = 0?
// friend auto tag_invoke(
// UNIFEX_USE_NON_DEDUCED_TYPE(CPO, tag_t<connect>),
// UNIFEX_USE_NON_DEDUCED_TYPE(S, foo_sender)&& s,
Expand Down
8 changes: 4 additions & 4 deletions include/unifex/connect_awaitable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ class _sender_task<Receiver>::type {

template(typename CPO) //
(requires is_receiver_query_cpo_v<CPO> AND
is_callable_v<CPO, const Receiver&>) //
std::is_invocable_v<CPO, const Receiver&>) //
friend auto tag_invoke(CPO cpo, const promise_type& p) noexcept(
is_nothrow_callable_v<CPO, const Receiver&>)
-> callable_result_t<CPO, const Receiver&> {
std::is_nothrow_invocable_v<CPO, const Receiver&>)
-> std::invoke_result_t<CPO, const Receiver&> {
return cpo(std::as_const(p.receiver_));
}

Expand Down Expand Up @@ -148,7 +148,7 @@ inline const struct _fn {
// awaitable through unifex::await_transform. So take that into
// consideration when computing the result type:
using promise_type = typename _await::sender_task<Receiver>::promise_type;
using awaitable_type = callable_result_t<
using awaitable_type = std::invoke_result_t<
tag_t<unifex::await_transform>,
promise_type&,
Awaitable>;
Expand Down
4 changes: 2 additions & 2 deletions include/unifex/continuations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ namespace _visit_continuations_cpo {
inline const struct _fn {
#if !UNIFEX_NO_COROUTINES
template(typename Promise, typename Func) //
(requires(!same_as<Promise, void>) AND callable<_fn, Promise&, Func>) //
(requires(!same_as<Promise, void>) AND std::is_invocable_v<_fn, Promise&, Func>) //
friend void tag_invoke(
_fn cpo,
coro::coroutine_handle<Promise> h,
Func&& func) noexcept(is_nothrow_callable_v<_fn, Promise&, Func>) {
Func&& func) noexcept(std::is_nothrow_invocable_v<_fn, Promise&, Func>) {
cpo(h.promise(), (Func &&) func);
}
#endif // UNIFEX_NO_COROUTINES
Expand Down
8 changes: 4 additions & 4 deletions include/unifex/create.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ struct _op {
// Forward other receiver queries
template(typename CPO) //
(requires is_receiver_query_cpo_v<CPO> AND
is_callable_v<CPO, const Receiver&>) //
std::is_invocable_v<CPO, const Receiver&>) //
friend auto tag_invoke(CPO cpo, const type& self) noexcept(
is_nothrow_callable_v<CPO, const Receiver&>)
-> callable_result_t<CPO, const Receiver&> {
std::is_nothrow_invocable_v<CPO, const Receiver&>)
-> std::invoke_result_t<CPO, const Receiver&> {
return std::move(cpo)(self.rec_);
}

Expand Down Expand Up @@ -219,7 +219,7 @@ struct _fn {
* \endcode
* \tparam ValueTypes The value types of the resulting sender. Should be the
* list of value type(s) accepted by the callback (with the exception of the
* void* context). \param[in] fn A void-returning callable that accepts an
* void* context). \param[in] fn A void-returning invocable that accepts an
* lvalue reference to an object whose type satisfies the \c
* unifex::receiver_of<ValueTypes...> concept. This function should dispatch to
* the C-style callback (see example). \param[in] ctx An optional extra bit of
Expand Down
8 changes: 4 additions & 4 deletions include/unifex/defer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ namespace unifex {
namespace _defer {
inline const struct _fn {
template(typename Callable)(
requires callable<Callable> AND
sender<callable_result_t<Callable>>) constexpr auto
operator()(Callable&& callable) const {
return let_value(just(), (Callable &&) callable);
requires std::is_invocable_v<Callable> AND
sender<std::invoke_result_t<Callable>>) constexpr auto
operator()(Callable&& invocable) const {
return let_value(just(), (Callable &&) invocable);
}
} defer{};
} // namespace _defer
Expand Down
2 changes: 1 addition & 1 deletion include/unifex/delay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ inline const struct _fn {
template <typename Scheduler, typename Duration>
constexpr auto operator()(Scheduler&& scheduler, Duration&& duration) const
noexcept(
is_nothrow_callable_v<tag_t<bind_back>, _fn, Scheduler, Duration>)
std::is_nothrow_invocable_v<tag_t<bind_back>, _fn, Scheduler, Duration>)
-> bind_back_result_t<_fn, Scheduler, Duration> {
return bind_back(*this, (Scheduler &&) scheduler, (Duration &&) duration);
}
Expand Down
Loading

0 comments on commit 1b66205

Please sign in to comment.