9

I'm trying to create a function which can be called with a lambda that takes either 0, 1 or 2 arguments. Since I need the code to work on both g++ 4.5 and vs2010(which doesn't support variadic templates or lambda conversions to function pointers) the only idea I've come up with is to choose which implementation to call based on arity. The below is my non working guess at how this should look. Is there any way to fix my code or is there a better way to do this in general?

#include <iostream>
#include <functional>
using namespace std;

template <class Func> struct arity;

template <class Func>
struct arity<Func()>{ static const int val = 0; };

template <class Func, class Arg1>
struct arity<Func(Arg1)>{ static const int val = 1; };

template <class Func, class Arg1, class Arg2>
struct arity<Func(Arg1,Arg2)>{ static const int val = 2; };

template<class F>
void bar(F f)
{
    cout << arity<F>::val << endl;
}

int main()
{
    bar([]{cout << "test" << endl;});
}
Motti
  • 110,860
  • 49
  • 189
  • 262
Derek
  • 93
  • 1
  • 3

4 Answers4

17

A lambda function is a class type with a single function call operator. You can thus detect the arity of that function call operator by taking its address and using overload resolution to select which function to call:

#include <iostream>

template<typename F,typename R>
void do_stuff(F& f,R (F::*mf)() const)
{
    (f.*mf)();
}

template<typename F,typename R,typename A1>
void do_stuff(F& f,R (F::*mf)(A1) const)
{
    (f.*mf)(99);
}

template<typename F,typename R,typename A1,typename A2>
void do_stuff(F& f,R (F::*mf)(A1,A2) const)
{
    (f.*mf)(42,123);
}

template<typename F>
void do_stuff(F f)
{
    do_stuff(f,&F::operator());
}

int main()
{
    do_stuff([]{std::cout<<"no args"<<std::endl;});
    do_stuff([](int a1){std::cout<<"1 args="<<a1<<std::endl;});
    do_stuff([](int a1,int a2){std::cout<<"2 args="<<a1<<","<<a2<<std::endl;});
}

Be careful though: this won't work with function types, or class types that have more than one function call operator, or non-const function call operators.

Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
  • Very cool, however you don't need to actually call the function (as it stands you constrain the parameter types) and you would want to return the number of parameters from `do_stuff` (which may be better named `arity`). – Motti Nov 16 '10 at 18:57
  • With C++11 and variadic templates we can do better now. – Spencer Feb 26 '19 at 13:57
1

I thought the following would work but it doesn't, I'm posting it for two reasons.

  1. To save people the time if they had the same idea
  2. If someone knows why this doesn't work, I'm not 100% sure I understand (although I have my suspicions)

Code follows:

#include <iostream>
#include <functional>

template <typename Ret>
unsigned arity(std::function<Ret()>) { return 0; }

template <typename Ret, typename A1>
unsigned arity(std::function<Ret(A1)>) { return 1; }

template <typename Ret, typename A1, typename A2>
unsigned arity(std::function<Ret(A1, A2)>) { return 2; }

// rinse and repeat 

int main() 
{
    std::function<void(int)>  f = [](int i) { }; // this binds fine

    //  Error: no matching function for call to 'arity(main()::<lambda(int)>)'
    std::cout << arity([](int i) { }); 
} 
Motti
  • 110,860
  • 49
  • 189
  • 262
  • 1
    std::function knows how to wrap lambdas, but *you* have to provide the right <...> signature directly. For example: given "template void foo(vector);", you cant pass foo an initializer list! The compiler knows what to do with vector::vector(initializer_list), but it has to know T first to get that far. The compiler cant start from initializer_list, see a vector, and then magically pick T=X so that then things will match up. It's backward. – Jared Grubb Sep 08 '13 at 02:00
0

Compile time means of obtaining the arity of a function or a function object, including that of a lambda:

int main (int argc, char ** argv) {
    auto f0 = []() {};
    auto f1 = [](int) {};
    auto f2 = [](int, void *) {};

    std::cout << Arity<decltype(f0)>::value << std::endl; // 0
    std::cout << Arity<decltype(f1)>::value << std::endl; // 1
    std::cout << Arity<decltype(f2)>::value << std::endl; // 2

    std::cout << Arity<decltype(main)>::value << std::endl; // 2
}

template <typename Func>
class Arity {
private:
    struct Any {
        template <typename T>
        operator T ();
    };

    template <typename T>
    struct Id {
        typedef T type;
    };

    template <size_t N>
    struct Size {
        enum { value = N };
    };

    template <typename F>
    static Size<0> match (
        F f,
        decltype(f()) * = nullptr);

    template <typename F>
    static Size<1> match (
        F f,
        decltype(f(Any())) * = nullptr,
        decltype(f(Any())) * = nullptr);

    template <typename F>
    static Size<2> match (
        F f,
        decltype(f(Any(), Any())) * = nullptr,
        decltype(f(Any(), Any())) * = nullptr,
        decltype(f(Any(), Any())) * = nullptr);

public:
    enum { value = Id<decltype(match(static_cast<Func>(Any())))>::type::value };
};
Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
-1

This way works:

template<typename F>
auto call(F f) -> decltype(f(1))
{
    return f(1);
}

template<typename F>
auto call(F f, void * fake = 0) -> decltype(f(2,3))
{
    return f(2,3);
}

template<typename F>
auto call(F f, void * fake = 0, void * fake2 = 0) -> decltype(f(4,5,6))
{
    return f(4,5,6);
}

int main()
{
    auto x1 = call([](int a){ return a*10; });
    auto x2 = call([](int a, int b){ return a*b; });
    auto x3 = call([](int a, int b, int c){ return a*b*c; });
    // x1 == 1*10
    // x2 == 2*3
    // x3 == 4*5*6
}

It works for all callable types (lambdas, functors, etc)

k06a
  • 17,755
  • 10
  • 70
  • 110