Arity common lambda

You can infer the arity of a minor lambda by accessing its operator() .

 template <typename F> struct fInfo : fInfo<decltype(&F::operator())> { }; template <typename F, typename Ret, typename... Args> struct fInfo<Ret(F::*)(Args...)const> { static const int arity = sizeof...(Args); }; 

It's nice and dandy for something like [](int x){ return x; } [](int x){ return x; } because operator() not a template.

However, common lambdas templates make the operator() template, and you can access a specific instance of the template - this is a bit problematic because I cannot manually provide the template arguments for operator() , since I don't know what its arity is.

So of course, something like

 auto lambda = [](auto x){ return x; }; auto arity = fInfo<decltype(lambda)>::arity; 

does not work.

I do not know what to do, and I do not know what template arguments provide (or how much) ( operator()<??> ).
Any ideas how to do this?

+8
c ++ c ++ 14 generic-lambda
source share
5 answers

This is not possible, since a function call statement can be a variation pattern. It was impossible to do this forever for functional objects in general, and special iambic, because they were not so powerful, were always bad ideas. Now the time has come when this bad idea will return home to rise.

+2
source share

This method will work in some cases. I am creating a fake_anything type that can fake almost everything, and try calling your lambda with some number of instances.

 #include <iostream> struct fake_anything { fake_anything(fake_anything const&); fake_anything(); fake_anything&operator=(fake_anything const&); template<class T>operator T&() const; template<class T>operator T&&() const; template<class T>operator T const&() const; template<class T>operator T const&&() const; fake_anything operator*() const; fake_anything operator++() const; fake_anything operator++(int) const; fake_anything operator->() const; template<class T>fake_anything(T&&); }; fake_anything operator+(fake_anything, fake_anything); fake_anything operator-(fake_anything, fake_anything); fake_anything operator*(fake_anything, fake_anything); fake_anything operator/(fake_anything, fake_anything); // etc for every operator template<class>using void_t=void; template<class Sig, class=void> struct can_invoke:std::false_type{}; template<class F, class...Args> struct can_invoke<F(Args...), void_t< decltype( std::declval<F>()( std::declval<Args>()... ) ) > > : std::true_type {}; template<class Sig>struct is_sig:std::false_type{}; template<class R, class...Args>struct is_sig<R(Args...)>:std::true_type{}; template<unsigned...>struct indexes{using type=indexes;}; template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...>{}; template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{}; template<unsigned max>using make_indexes_t=typename make_indexes<max>::type; template<class T,unsigned>using unpacker=T; template<class F, class A, class indexes> struct nary_help; template<class F, class A, unsigned...Is> struct nary_help<F,A,indexes<Is...>>: can_invoke<F( unpacker<A,Is>... )> {}; template<class F, unsigned N> struct has_n_arity: nary_help<F, fake_anything, make_indexes_t<N>> {}; template<class F, unsigned Min=0, unsigned Max=10> struct max_arity{ enum{Mid=(Max+Min)/2}; enum{ lhs = max_arity<F,Min,Mid>::value, rhs = max_arity<F,Mid+1,Max>::value, value = lhs>rhs?lhs:rhs, }; }; template<class F, unsigned X> struct max_arity<F,X,X>: std::integral_constant<int, has_n_arity<F,X>::value?(int)X:-1> {}; template<class F, unsigned Min=0, unsigned Max=10> struct min_arity{ enum{Mid=(Max+Min)/2}; enum{ lhs = min_arity<F,Min,Mid>::value, rhs = min_arity<F,Mid+1,Max>::value, value = lhs<rhs?lhs:rhs, }; }; template<class F, unsigned X> struct min_arity<F,X,X>: std::integral_constant<unsigned,has_n_arity<F,X>::value?X:(unsigned)-1> {}; auto test1 = [](auto x, auto y)->bool { return x < y; }; auto test2 = [](auto x, auto y) { return x + y; }; auto test3 = [](auto x) { return xy; }; int main() { std::cout << can_invoke< decltype(test1)( fake_anything, fake_anything ) >::value << "\n"; std::cout << can_invoke< decltype(test1)( int, int ) >::value << "\n"; std::cout << has_n_arity< decltype(test1), 2 >::value << "\n"; std::cout << max_arity< decltype(test1) >::value << "\n"; std::cout << max_arity< decltype(test2) >::value << "\n"; // will fail to compile: // std::cout << max_arity< decltype(test3) >::value << "\n"; } 

living example .

Note, a sufficient amount of SFINAE will mean that the above will lead to an incorrect result, as well as the use of operator. , or using operator. for certain types of "derived" types or for accessing types based on the fake_anything parameter, etc.

However, if lambda indicates its return value with a sentence ->X , then fake_anything more than enough. The hard part is dealing with the body.

Note that this approach is often a bad idea, because if you want to know the arity of a function, you probably also know the types of what you want to call the function object! And above, I answer this question very easily (can this function object be called with these arguments?). It can even be improved to ask "what is the longest / shortest prefix of these arguments that can call this function object", or to process "how many repetitions of type X work to call this function object" (if you want to get a clean glitch, you need upper bound).

+6
source share

I would say that this is partially possible, at least you can find out the general arity (templated + regular types) when you explicitly create automatic operator() parameters:

 template <typename F, typename... Args> struct autofInfo : fInfo<decltype(&F::template operator()<Args...>)> {}; auto lambda = [](auto x, int y, float z) { return x + y + z; }; auto arity = autofInfo<decltype(lambda), int>::arity; // ^^^ list of auto parameters instantiations assert(3 == arity); 
+1
source share

This is a C ++ 17 solution that works with universal and variable lambdas and functors with the variable operator templatet (). the idea is to recursively simulate a call with a decreasing number of arguments and use SFINAE to break the recursion when it finds the first suitable number of arguments. These are the gcc> = 7 and Clang> = 5 compilers. A working example can be found here .

 #include<utility> constexpr size_t max_arity = 10; struct variadic_t { }; namespace detail { // it is templated, to be able to create a // "sequence" of arbitrary_t of given size and // hece, to 'simulate' an arbitrary function signature. template <size_t> struct arbitrary_t { // this type casts implicitly to anything, // thus, it can represent an arbitrary type. template <typename T> operator T &&(); template <typename T> operator T &(); }; template <typename F, size_t... Is, typename U = decltype(std::declval<F>()(arbitrary_t<Is>{}...))> constexpr auto test_signature(std::index_sequence<Is...>) { return std::integral_constant<size_t, sizeof...(Is)>{}; } template <size_t I, typename F> constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{})) { return {}; } template <size_t I, typename F, typename = std::enable_if_t<(I > 0)>> constexpr auto arity_impl(...) { // try the int overload which will only work, // if F takes I-1 arguments. Otherwise this // overload will be selected and we'll try it // with one element less. return arity_impl<I - 1, F>(0); } template <typename F, size_t MaxArity = 10> constexpr auto arity_impl() { // start checking function signatures with max_arity + 1 elements constexpr auto tmp = arity_impl<MaxArity + 1, F>(0); if constexpr (tmp == MaxArity + 1) { // if that works, F is considered variadic return variadic_t{}; } else { // if not, tmp will be the correct arity of F return tmp; } } } template <typename F, size_t MaxArity = max_arity> constexpr auto arity(F&& f) { return detail::arity_impl<std::decay_t<F>, MaxArity>(); } template <typename F, size_t MaxArity = max_arity> constexpr auto arity_v = detail::arity_impl<std::decay_t<F>, MaxArity>(); template <typename F, size_t MaxArity = max_arity> constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_t>; 

Using:

 auto l = [](auto...){}; static_assert(is_variadic_v<decltype(l)>); 

and:

 auto l = [](auto, auto, auto){}; static_assert(!is_variadic_v<decltype(l)>); static_assert(arity(l) == 3); 
+1
source share

Another possible solution for the case when the possible types of templates are known: http://coliru.stacked-crooked.com/a/e3a07d723a8f27e9

 using T1 = string; using T2 = int; std::integral_constant<int, 1> static arity(function<void(T1)>){ return {}; } std::integral_constant<int, 2> static arity(function<void(T1, T2)>){ return {}; } template<class Fn> using Arity = decltype(arity(Fn{})); 
0
source share

All Articles