Reputation: 1321
Is there a way to use std::is_invocable
with arbitrary function arguments types, something like: std::is_invocable<Function, auto>
. The idea is to check whether Function
can accept 1 argument, regardless of the type of the argument. For a use case consider two lambdas: auto lambda1 = [](auto x) {...}
, auto lambda2 = [](auto x, auto y) {...}
, and a higher order templated function:
// specialize for 1 argument
template<typename Function, std::enable_if_t<(std::is_invocable<Function, auto>::value && !std::is_invocable<Function, auto, auto>::value)>, bool> = true>
void higherOrderFunc(Function&& func);
// specialize for 2 arguments
template<typename Function, std::enable_if_t<std::is_invocable<Function, auto, auto>::value, bool> = true>
void higherOrderFunc(Function&& func);
The !std::is_invocable<Function, auto, auto>::value
in the first case is to prevent ambiguity for overloaded functions (that is, the preferred specialization in this case would be the 2 argument one in case of ambiguity).
Note that I am aware that auto
cannot be used like this in this case. I am asking whether there's a way to implement this behaviour (at least partially).
Upvotes: 2
Views: 2340
Reputation: 66230
Maybe not a perfect solution... but you can try with a passepartout
struct passepartout
{
template <typename T>
operator T & ();
template <typename T>
operator T && ();
};
Observe that conversion operators are only declared, not defined; so this structure can be used in decltype()
and with std::declval()
(and std::is_invocable
) but can't be instantiated.
Now you can write your higherOrderFunc
passing reference to passepartout
to std::is_invocable
.
template <typename F,
std::enable_if_t<
std::is_invocable_v<F, passepartout &>
&& ! std::is_invocable_v<F, passepartout &, passepartout &>, bool>
= true>
void higherOrderFunc (F)
{ std::cout << "-- one parameter callable" << std::endl; }
template <typename F,
std::enable_if_t<
std::is_invocable_v<F, passepartout &, passepartout &>, bool> = true>
void higherOrderFunc (F)
{ std::cout << "-- two parameter callable" << std::endl; }
The trick is that if a callable waits for auto
(or auto &
, or auto &&
), the type is deduced as passepartout
itself; when the callable wait a specific type (int
, with or without references, in the following examples), the template operator T & ()
(or operator T && ()
, according the cases) is compatible (in a sense) with the expected type.
The following is a full compiling example
#include <type_traits>
#include <iostream>
struct passepartout
{
template <typename T>
operator T & ();
template <typename T>
operator T && ();
};
template <typename F,
std::enable_if_t<
std::is_invocable_v<F, passepartout &>
&& ! std::is_invocable_v<F, passepartout &, passepartout &>, bool>
= true>
void higherOrderFunc (F)
{ std::cout << "-- one parameter callable" << std::endl; }
template <typename F,
std::enable_if_t<
std::is_invocable_v<F, passepartout &, passepartout &>, bool> = true>
void higherOrderFunc (F)
{ std::cout << "-- two parameter callable" << std::endl; }
int main ()
{
auto l1a = [](auto &&){};
auto l1b = [](int &){};
auto l2a = [](auto &, int &&){};
auto l2b = [](auto, int const &){};
auto l2c = [](auto &&, auto const &){};
auto l2d = [](int &&, auto const &, auto && ...){};
higherOrderFunc(l1a);
higherOrderFunc(l1b);
higherOrderFunc(l2a);
higherOrderFunc(l2b);
higherOrderFunc(l2c);
higherOrderFunc(l2c);
higherOrderFunc(l2d);
}
Upvotes: 3