Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I find the number of arguments a template function takes?

Tags:

c++

templates

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?

like image 523
David Avatar asked Jan 09 '17 20:01

David


People also ask

Can template function take any number of 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 arguments to templates?

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.

What is template argument list?

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.)

How are template function overloaded?

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.


2 Answers

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

like image 164
Quentin Avatar answered Oct 17 '22 14:10

Quentin


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.

like image 44
R Sahu Avatar answered Oct 17 '22 12:10

R Sahu