I have the following type trait:
template <class T>
struct Arity : Arity<decltype(&T::operator())> {};
template <class T, class R, class... Args>
struct Arity<R(T::*)(Args...)> {
static constexpr auto value = sizeof...(Args);
};
template <class T, class R, class... Args>
struct Arity<R(T::*)(Args...) const> {
static constexpr auto value = sizeof...(Args);
};
template <class R, class... Args>
struct Arity<R(*)(Args...)> {
static constexpr auto value = sizeof...(Args);
};
Which works great to find the number of arguments a function takes for most use cases, but it fails for one common case:
auto l1 = [](int, double){};
Arity<decltype(l1)>::value; // works, 2
auto l2 = [](auto, auto){};
Arity<decltype(l2)>::value; // error: Reference to overloaded function could not be resolved; did you mean to call it?
I believe it's impossible to generally make this work for any templated function / operator() because depending on the types/values passed as template types, a different overload could be selected, or perhaps no overload may be available at all. Also, there's no way to know what valid types and values to pass as template arguments. But still, I want this to work for the common case of a lambda taking auto
arguments. Is there any way to make this more robust and cover lambdas which take auto arguments?
Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration.
Can there be more than one argument to templates? Yes, like normal parameters, we can pass more than one data type as arguments to templates.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
A function template can overload non-template functions of the same name. In this scenario, the compiler first attempts to resolve a function call by using template argument deduction to instantiate the function template with a unique specialization.
I guess I achieved half of a solution here. Only works up to a fixed number of parameters, but for most applications that shouldn't be an issue. Also, it's probably highly simplifiable but my brain is not into tricky SFINAE right now.
template <
class, std::size_t N,
class = std::make_index_sequence<N>,
class = void_t<>
>
struct CanCall : std::false_type { };
template <class F, std::size_t N, std::size_t... Idx>
struct CanCall<
F, N,
std::index_sequence<Idx...>,
void_t<decltype(std::declval<F>()((Idx, std::declval<Any const&&>())...))>
> : std::true_type { };
CanCall<F, N>
will return whether F
is callable with N
parameters of arbitrary type. The Any
helper type has templated implicit conversion operators that allows it to morph into any desired parameter type.
template <class F, std::size_t N = 0u, class = void>
struct Arity : Arity<F, N + 1u, void> { };
template <class F, std::size_t N>
struct Arity<F, N, std::enable_if_t<CanCall<F, N>::value>>
: std::integral_constant<std::size_t, N> { };
template <class F>
struct Arity<F, MAX_ARITY_PROBING, void>
: std::integral_constant<std::size_t, ARITY_VARIADIC> { };
Arity<F>
just checks whether an F
can be called with zero, one, two... parameters. First positive check wins. If we reach MAX_ARITY_PROBING
parameters, Arity
bails out and supposes that the function is either variadic, or is not a function at all.
See it live on Coliru
I don't think you can use lambda functions in your use case whose argument types are auto
. The operator()
functions of such lambda functions are most likely implemented using function templates.
Hence, decltype
can't be used with:
auto l2 = [](auto, auto){};
Arity<decltype(l2)>::value;
See this answer to another SO question for more on the subject.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With