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