Vincent
Vincent

Reputation: 60421

Detect the maximum numbers of arguments a function can take using template metaprogramming

Consider the following code:

template <class T, class F>
std::size_t maximum_number_of_arguments(F&& f) {
    // Code here
}

I would like a function that can find the maximum number of arguments of type T that the callable f can take.

For example for the following overload set:

void f(int, int); // f called with 2 ints compiles
void f(int, int, int); // f called with 3 ints compiles
void f(int, char, int, double); // f called with 4 ints compiles
void f(int, std::string, int, int, int); // f called with 5 ints does not compile

As a result:

maximum_number_of_arguments<int>(f)

should return 4.

I guess the way to fix a limiting number of parameters a function can take (for example 256), and test all the possibility, and keep track of the calls that does not fail to compile. How to do this with template/contexpr metaprogramming?

EDIT: To answer the comments on the fact that we cannot pass an overload set to a function (my example with f is probably not the best):

#include <iostream>

struct functor
{
    void operator()(int) {std::cout << "int" << std::endl;}
    void operator()(int, int) {std::cout << "int, int" << std::endl;}
    void operator()(int, int, int) {std::cout << "int, int, int" << std::endl;}
};

template <class F>
void caller(F&& f)
{
    std::forward<F>(f)(1);
    std::forward<F>(f)(1, 2);
    std::forward<F>(f)(1, 2, 3);
}

int main(int argc, char* argv[])
{
    functor f;
    caller(f);
    return 0;
}

Upvotes: 2

Views: 102

Answers (1)

max66
max66

Reputation: 66230

What you ask isn't exactly trivial...

If you interested in the maximum number of integer that alone can be used to call a function or a set of function with a common identifier, excluding the calls with other types...

I mean... if the check for the following set of f() functions

void f(int, int); 
void f(int, char, int);
void f(int, std::string, int, int, int); // excluded by std::string

should get 3 because the version that receive a std::string is to be excluded...

Well... with a macro (so my solution is intrinsic evil) that define a check struct dedicated to a symbol

setMaxStruct(f);

you can get the maximum number of integer arguments (with a settable but defaulted maximum) writing something as follows

std::cout << "--- f: " << getMaxArgsFor_f<int>::value << std::endl;

The following is a full working example

#include <utility>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t>
using typer = T;

#define setMaxStruct(func)                                        \
                                                                  \
template <typename Type, std::size_t MaxArgs = 64U>               \
struct getMaxArgsFor_ ## func                                     \
 {                                                                \
   template <typename, std::size_t ...>                           \
   static std::false_type isCallable (...);                       \
                                                                  \
   template <typename T, std::size_t ... Is>                      \
   static auto isCallable (int)                                   \
      -> decltype( func(std::declval<typer<T, Is>>()...),         \
                   std::true_type{} );                            \
                                                                  \
   template <typename T, std::size_t ... Is>                      \
   static constexpr bool getMaxTH3                                \
      (std::index_sequence<Is...> const &)                        \
    { return decltype(isCallable<T, Is...>(0))::value; }          \
                                                                  \
   template <typename T, std::size_t I>                           \
   static constexpr bool getMaxTH2 ()                             \
    { return getMaxTH3<T>(std::make_index_sequence<I>{}); }       \
                                                                  \
   template <typename T, std::size_t ... Is>                      \
   static constexpr std::size_t getMaxTH1                         \
      (std::index_sequence<Is...> const &)                        \
    {                                                             \
      std::size_t ret ( -1 );                                     \
                                                                  \
      ( (ret = getMaxTH2<T, Is>() ? Is : ret), ...);              \
                                                                  \
      return ret;                                                 \
    }                                                             \
                                                                  \
   template <typename T, std::size_t MaxAs>                       \
   static constexpr std::size_t getMaxT ()                        \
    { return getMaxTH1<T>(std::make_index_sequence<MaxAs>{}); }   \
                                                                  \
   static constexpr std::size_t value = getMaxT<Type, MaxArgs>(); \
 }

void f(int, int);
void f(int, int, int);
void f(int, char, int, double);
void f(int, std::string, int, int, int);

template <typename ... Args>
void h (Args ... args);

setMaxStruct(f);
setMaxStruct(g);
setMaxStruct(h);

int main()
 {
   std::cout << "--- f: " << getMaxArgsFor_f<int>::value << std::endl;
   std::cout << "--- g: " << getMaxArgsFor_g<int>::value << std::endl;
   std::cout << "--- h: " << getMaxArgsFor_h<int>::value << std::endl;
 }

Observe that from f you get 4, from g you get size_t(-1) (g() is undefined) and from h() you get 63 (the maximum value minus 1).

Upvotes: 3

Related Questions