1

Trying to expand on the example found here: https://stackoverflow.com/a/17974752

Basically, I'd like to deduce the number of arguments from the function passed in automatically (which obviously wouldn't work for overloaded functions). I'd like this to also work with functors and lambdas.

It's failing to compile the call to call_helper: error: parse error in template argument

I can't seem to figure out how to pass in the constexpr that returns the number of arguments as a template argument.

This is what I have so far:

#include <vector>

template <typename R, typename ... T>
constexpr std::size_t get_args_count( R(*f)(T ...))
{
   return sizeof...(T);
}

template< std::size_t... Ns >
struct indices {
    typedef indices< Ns..., sizeof...( Ns ) > next;
};

template< std::size_t N >
struct make_indices {
    typedef typename make_indices< N - 1 >::type::next type;
};

template<>
struct make_indices< 0 > {
    typedef indices<> type;
};

void abc(int) {}
void abc2(int, int) {}

// helper function because we need a way
// to deduce indices pack

template<typename Func, size_t... Is>
void call_helper2(Func f, const std::vector<int>& args, indices<Is...>)
{
    f( args[Is]... ); // expand the indices pack
}

template<typename Func, size_t N>
void call_helper(Func f, const std::vector<int>& args)
{
    call_helper2(f, args, typename make_indices<N>::type());
}

template<typename Func>
void call(Func f, const std::vector<int>& args)
{
    if (args.size() < get_args_count(f)) throw 42;
    call_helper<get_args_count(decltype(f))>(f, args); // error: parse error in template argument list
}

int main()
{
    struct F
    {
        void operator()(int, int, int, int) {}
    };

    std::vector<int> v(4);
    call(&abc2, v);
    call(&abc, v);
    call([&](int, int, int) { (void)v.empty(); }, v);
    call(F(), v);
}

What am I missing or doing wrong? Any help is appreciated.

EDIT: Added functor and lambda use cases

Tom
  • 653
  • 1
  • 8
  • 15

2 Answers2

1

There was two mistakes:

  1. In call_helper<get_args_count(decltype(f))>(f, args), get_args_count(decltype(f)) is meaningless as get_args_count takes a function pointer and not a type (obviously);
  2. A function argument can never be constexpr (I'll let you search on SO why).

How to fix?

You need to extract the number of arguments of f as soon as possible, before it becomes something else than an expression usable in constexpr context.

#include <vector>

template< std::size_t... Ns >
struct indices {
    typedef indices< Ns..., sizeof...( Ns ) > next;
};

template< std::size_t N >
struct make_indices {
    typedef typename make_indices< N - 1 >::type::next type;
};

template<>
struct make_indices< 0 > {
    typedef indices<> type;
};

void abc(int) {}
void abc2(int, int) {}

// helper function because we need a way
// to deduce indices pack

template<typename Func, size_t... Is>
void call_helper2(Func f, const std::vector<int>& args, indices<Is...>)
{
    f( args[Is]... ); // expand the indices pack
}

template<typename Func, size_t N>
void call_helper(Func f, const std::vector<int>& args)
{
    call_helper2(f, args, typename make_indices<N>::type());
}

template<class R, class ... T>
void call(R(*f)(T ...), const std::vector<int>& args)
{
    if (args.size() < sizeof...(T)) throw 42;
    call_helper<
        decltype(f), sizeof...(T)
    >(f, args);
}

int main()
{
    std::vector<int> v(2);
    call(&abc2, v);
}
YSC
  • 38,212
  • 9
  • 96
  • 149
  • How do I make it work with lambdas as well? I'd like to also make with work for example `call([&](int, int, int) { (void)v.empty(); }, v);` – Tom Feb 07 '20 at 20:20
1

YSC's answer solves the problem for regular functions, but it doesn't work with lambdas and functors. To make this work with functors and lambdas as well, I had to add an overload of call (based on the solution in https://stackoverflow.com/a/7943765/1403469):

template< std::size_t... Ns >
struct indices {
    typedef indices< Ns..., sizeof...( Ns ) > next;
};

template< std::size_t N >
struct make_indices {
    typedef typename make_indices< N - 1 >::type::next type;
};

template<>
struct make_indices< 0 > {
    typedef indices<> type;
};

void abc(int) {}
void abc2(int, int) {}

// helper function because we need a way
// to deduce indices pack

template<typename Func, size_t... Is>
void call_helper2(Func f, const std::vector<int>& args, indices<Is...>)
{
    f( args[Is]... ); // expand the indices pack
}

template<typename Func, size_t N>
void call_helper(Func f, const std::vector<int>& args)
{
    call_helper2(f, args, typename make_indices<N>::type());
}

template<class R, class ... T>
void call(R(*f)(T ...), const std::vector<int>& args)
{
    // this overload is used for regular functions
    if (args.size() < sizeof...(T)) throw 42;
    call_helper<decltype(f), sizeof...(T)>(f, args);
}

template <typename T>
struct func_traits:
    public func_traits<decltype(&T::operator())>
{};

template <typename C, typename R, typename... Args>
struct func_traits<R(C::*)(Args...) const>
{
    enum { arity = sizeof...(Args) };
};

template<typename Func>
void call(Func f, const std::vector<int>& args)
{
    // this overload is used for functors and lambdas
    using traits = func_traits<decltype(f)>;
    if (args.size() < traits::arity) throw 42;
    call_helper<decltype(f), traits::arity>(f, args);
}

int main()
{
    struct F
    {
        void operator()(int, int, int, int) const {}
    };

    std::vector<int> v(4);
    call(&abc2, v);
    call(&abc, v);
    call([&](int, int, int) { (void)v.empty(); }, v);
    call(F(), v);
}
Tom
  • 653
  • 1
  • 8
  • 15
  • Nice. Note though that changing the scope of the question after an answer has been posted is considered second best; one'd rather ask a _new_ question. – YSC Feb 07 '20 at 23:45
  • You're right, I apologize. I just had assumed that it worked the same, but found out shortly after that it didn't cover all typical use cases. – user2129246 Feb 08 '20 at 02:31