Search code examples
c++lambdastd-function

Query number of arguments from a lambda/function


I would like to query a lambda/function about how much parameters it uses. Small (pseudo) example code:

template <class InputIterator, class Predicate>
bool visitAll( InputIterator f, InputIterator l, Predicate p, UserData* d=nullptr)
{
    for(; f != l; ++f)
    {
        if(number_of_arguments(p) == 1)
        {
            if(!p(*f))
                return false;
        }
        else
        {
            if(!p(*f, *d))
                return false;
        }
    }
}

Note that the function I am asking for is number_of_arguments(...). I have been searching in the Closure and std::function reference, but did not find a clue about a solution.

Thanks for your help!


Solution

  • Obviously the code as you posted does not make much sense, as anyway either p(*f) or p(*f, *d) would fail to compile. Therefore you need to split this into two templates, and then you can test the number of arguments of Predicate using a rather straightforward SFINAE approach:

    template <class InputIterator, class Predicate>
    bool visitAll( InputIterator f, InputIterator l, Predicate p, UserData* d=nullptr, 
       decltype(declval<Predicate>()(*declval<InputIterator>()),1) unused = 1)
    {
        cout << "1" << std::endl;
        for(; f != l; ++f)
        {
            if(!p(*f))
                return false;
        }
        return true;
    }
    
    template <class InputIterator, class Predicate>
    bool visitAll( InputIterator f, InputIterator l, Predicate p, UserData* d=nullptr, 
       decltype(declval<Predicate>()(*declval<InputIterator>(), declval<UserData>()),1) unused = 1)
    {
        cout << "2" << std::endl;
        for(; f != l; ++f)
        {
            if(!p(*f, *d))
                return false;
        }
        return true;
    }
    

    Usage:

    std::vector<int> a{1,2};
    const auto t = [](int x){ return 1;};
    const auto t2 = [](int x, UserData y){ return 1;};
    
    UserData d;
    visitAll(a.begin(), a.end(), t);
    visitAll(a.begin(), a.end(), t2, &d);
    

    Of course, you can use std::bind to avoid code duplication by calling first version from the second.


    Another approach is to use code similar to how std::bind checks that it got needed number of arguments:

    template<typename _Func>
    struct noa_helper { 
    };
    
    template<typename _Ret, typename... _Args>
    struct noa_helper<_Ret (*)(_Args...)> {
        static int noa() { return sizeof...(_Args); }
    };
    
    template<class F>
    int number_of_arguments(F f) {
        return noa_helper<typename std::decay<F>::type>::noa();
    }
    
    void foo();
    int bar(int x, int y);
    ...
    std::cout << number_of_arguments(foo) << std::endl; // prints 0
    std::cout << number_of_arguments(bar) << std::endl; // prints 2
    

    This works only for real functions, not lambdas, nor std::function, though probably some more template magic can make it work for the latter two categories.