Search code examples
c++c++17template-meta-programmingsfinae

C++ SFINAE to determine the number of function arguments (C++17)


I created a metafunction using SFINAE to determine the number of arguments of a function at compile time. It works fine with gcc when used with with function objects, but not with lambda closures, I don't understand why. The metafunction is here below

template < typename T >
int val (T &&){return 0;};

template <int N, typename Functor>
struct has_args {

  template <typename F , int ... Args>
  static auto test(F, decltype(val(std::declval<F>()( Args ... ))), std::integer_sequence<int, Args ...>){
          return std::true_type{};
      };

  template <typename F, typename Val, typename Seq>
  static auto test(F, Val, Seq){
          return std::false_type{};
      };

  using type =  decltype (test(std::declval<Functor>(), 0, std::make_integer_sequence<int, N>()));
};

and here is how it should behave

struct func{
    template<typename T>
    int operator()(T){}
};
int main(){

  auto lambda0 = [](auto arg){};

  static_assert(has_arg<1, func>::type::value==true, "error");
  //static_assert(has_arg<1, decltype(lambda0)>::type::value==true, "error"); // Assertion fails!
}

The full code (with few more examples) is in this git repo: https://github.com/crosetto/has_args/blob/main/number_of_arguments.cpp

Does anybody have an explanation of why this doesn't work with lambdas?


Solution

  • As pointed out in the comment by @rafix07, the issue here is that the lambda is returning void, so it's signature is not matched in the first definition of test, and falls back to the other overload. One fix is to apply a comma operator to the argument of val, i.e. changing

    static auto test(F, decltype(val(std::declval<F>()( Args ... ))), std::integer_sequence<int, Args ...>)
    

    into

    static auto test(F, decltype(val((std::declval<F>()( Args ... ),0))), std::integer_sequence<int, Args ...>)
    

    or, as pointed out by @Jarod42 in the comments, val is unnecessary, and one can write:

    static auto test(F, decltype(((std::declval<F>()( Args ... ),void(),0))), std::integer_sequence<int, Args ...>)
    

    Note that the function body gets parsed, and any use of the arguments which wouldn't compile with integers results in a compiler error. I think this cannot be worked around in a generic way (one can use a fake type other than int though, and make it satisfy the required API).