15

I am actually thinking of something similar to the '*' operator in python like this:

args = [1,2,4]
f(*args)

Is there a similar solution in C++?

What I can come up with is as follows:

template <size_t num_args, typename FuncType>
struct unpack_caller;

template <typename FuncType>
struct unpack_caller<3>
{
    void operator () (FuncType &f, std::vector<int> &args){
        f(args[0], args[1], args[3])
    }
};

Above I assume only int argument type.

The problem is that I feel it is a hassle to write all the specializations of unpack_caller for different value of num_args.

Any good solution to this? Thanks.

Danqi Wang
  • 1,597
  • 1
  • 10
  • 28
  • 2
    I would suspect the answer is no. The number of arguments to functions is a compile-time construct, while the number of elements in a vector is a runtime construct. I can't prove that it's impossible, but I strongly suspect it to be. – templatetypedef Jun 15 '12 at 04:25
  • 1
    templatetypedef: It is certainly possible. See the solution given by @R. Martinho Fernandes. – Nawaz Jun 15 '12 at 05:32
  • @templatetypedef Although the size of vector is a runtime variable, the number of argument of a function is a compile-time constant. That's why it is possible. We can grab it by `boost::function_traits::arity` – Danqi Wang Jun 15 '12 at 10:02
  • 1
    @DanqiWang- If you allow for variadic functions (either using variadic templates or using varargs), though, the solution listed below will not work. That was the concern that I was initially voicing. – templatetypedef Jun 15 '12 at 22:54
  • @templatetypedef I agree. I didn't take that into account. Good point. – Danqi Wang Jun 16 '12 at 06:39

3 Answers3

14

You can use a pack of indices:

template <size_t num_args>
struct unpack_caller
{
private:
    template <typename FuncType, size_t... I>
    void call(FuncType &f, std::vector<int> &args, indices<I...>){
        f(args[I]...);
    }

public:
    template <typename FuncType>
    void operator () (FuncType &f, std::vector<int> &args){
        assert(args.size() == num_args); // just to be sure
        call(f, args, BuildIndices<num_args>{});
    }
};

There's no way to remove the need to specify the size in the template though, because the size of a vector is a runtime construct, and we need the size at compile-time.

Community
  • 1
  • 1
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • I think `assert` should use `>=` rather than `==`, as `assert(args.size() >= num_args);`. It is more flexible! – Nawaz Jun 15 '12 at 05:33
  • @Fernandes I only have g++-4.5, which does not support template alias, so I use `typedef` instead. In `operator ()` I have to do `typedef typename build_indices::type idx_type; call(f, args, idx_type{});`. Any better solution? – Danqi Wang Jun 15 '12 at 07:41
  • 1
    @Fernandes Actually it is possible to remove the template parameter `num_args` by using `boost::function_traits::arity` in `opertor ()`. But I read the implementation of `boost::function_traits`, it seems it only supports up to 11 arguments. I think it is worth to keep `num_args` to make it general. – Danqi Wang Jun 15 '12 at 10:07
8

Update to @Fernandes's answer.

Yes, there does be a way to remove the need to specifying num_args in template parameter. This is because num_args is determined by the function signature, not the vector. What should be checked at run-time is the size of the vector and the arity of the function.

See the following example.

#include <iostream>
#include <utility>
#include <vector>
#include <cassert>

namespace util {
template <typename ReturnType, typename... Args>
struct function_traits_defs {
  static constexpr size_t arity = sizeof...(Args);

  using result_type = ReturnType;

  template <size_t i>
  struct arg {
    using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
  };
};

template <typename T>
struct function_traits_impl;

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(*)(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename T, typename V = void>
struct function_traits
    : function_traits_impl<T> {};

template <typename T>
struct function_traits<T, decltype((void)&T::operator())>
    : function_traits_impl<decltype(&T::operator())> {};

template <size_t... Indices>
struct indices {
  using next = indices<Indices..., sizeof...(Indices)>;
};
template <size_t N>
struct build_indices {
  using type = typename build_indices<N - 1>::type::next;
};
template <>
struct build_indices<0> {
  using type = indices<>;
};
template <size_t N>
using BuildIndices = typename build_indices<N>::type;

namespace details {
template <typename FuncType,
          typename VecType,
          size_t... I,
          typename Traits = function_traits<FuncType>,
          typename ReturnT = typename Traits::result_type>
ReturnT do_call(FuncType& func,
                VecType& args,
           indices<I...> ) {
  assert(args.size() >= Traits::arity);
  return func(args[I]...);
}
}  // namespace details

template <typename FuncType,
          typename VecType,
          typename Traits = function_traits<FuncType>,
          typename ReturnT = typename Traits::result_type>
ReturnT unpack_caller(FuncType& func,
                VecType& args) {
  return details::do_call(func, args, BuildIndices<Traits::arity>());
}
}  // namespace util

int func(int a, int b, int c) {
  return a + b + c;
}

int main() {
  std::vector<int> args = {1, 2, 3};

  int j = util::unpack_caller(func, args);
  std::cout << j << std::endl;

  return 0;
}
Ch'en Meng
  • 363
  • 3
  • 14
0

If you use c++14 there is update to @r-martinho-fernandes response using index-sequence:

template <size_t num_args>
struct unpack_caller
{
private:
    template <typename FuncType, size_t... I>
    void call(FuncType &f, std::vector<int> &args, std::index_sequence<I...>){
        f(args[I]...);
    }

public:
    template <typename FuncType>
    void operator () (FuncType &f, std::vector<int> &args){
        assert(args.size() == num_args); // just to be sure
        call(f, args, std::make_index_sequence<num_args>{});
    }
};
MOHRE
  • 1,096
  • 4
  • 15
  • 28