How do I expand a tuple into variadic template function's arguments?

Viewed 68282

Consider the case of a templated function with variadic template arguments:

template<typename Tret, typename... T> Tret func(const T&... t);

Now, I have a tuple t of values. How do I call func() using the tuple values as arguments? I've read about the bind() function object, with call() function, and also the apply() function in different some now-obsolete documents. The GNU GCC 4.4 implementation seems to have a call() function in the bind() class, but there is very little documentation on the subject.

Some people suggest hand-written recursive hacks, but the true value of variadic template arguments is to be able to use them in cases like above.

Does anyone have a solution to is, or hint on where to read about it?

13 Answers

The news does not look good.

Having read over the just-released draft standard, I'm not seeing a built-in solution to this, which does seem odd.

The best place to ask about such things (if you haven't already) is comp.lang.c++.moderated, because some folks involved in drafting the standard post there regularly.

If you check out this thread, someone has the same question (maybe it's you, in which case you're going to find this whole answer a little frustrating!), and a few butt-ugly implementations are suggested.

I just wondered if it would be simpler to make the function accept a tuple, as the conversion that way is easier. But this implies that all functions should accept tuples as arguments, for maximum flexibility, and so that just demonstrates the strangeness of not providing a built-in expansion of tuple to function argument pack.

Update: the link above doesn't work - try pasting this:

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/750fa3815cdaac45/d8dc09e34bbb9661?lnk=gst&q=tuple+variadic#d8dc09e34bbb9661

Extending on @David's solution, you can write a recursive template that

  1. Doesn't use the (overly-verbose, imo) integer_sequence semantics
  2. Doesn't use an extra temporary template parameter int N to count recursive iterations
  3. (Optional for static/global functors) uses the functor as a template parameter for compile-time optimizaion

E.g.:

template <class F, F func>
struct static_functor {
    template <class... T, class... Args_tmp>
    static inline auto apply(const std::tuple<T...>& t, Args_tmp... args)
            -> decltype(func(std::declval<T>()...)) {
        return static_functor<F,func>::apply(t, args...,
                std::get<sizeof...(Args_tmp)>(t));
    }
    template <class... T>
    static inline auto apply(const std::tuple<T...>& t, T... args)
            -> decltype(func(args...)) {
        return func(args...);
    }
};

static_functor<decltype(&myFunc), &myFunc>::apply(my_tuple);

Alternatively if your functor is not defined at compile-time (e.g., a non-constexpr functor instance, or a lambda expression), you can use it as a function parameter instead of a class template parameter, and in fact remove the containing class entirely:

template <class F, class... T, class... Args_tmp>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        Args_tmp... args) -> decltype(func(std::declval<T>()...)) {
    return apply_functor(func, t, args..., std::get<sizeof...(Args_tmp)>(t));
}
template <class F, class... T>
inline auto apply_functor(F&& func, const std::tuple<T...>& t,
        T... args) -> decltype(func(args...)) {
    return func(args...);
}

apply_functor(&myFunc, my_tuple);

For pointer-to-member-function callables, you can adjust either of the above code pieces similarly as in @David's answer.

Explanation

In reference to the second piece of code, there are two template functions: the first one takes the functor func, the tuple t with types T..., and a parameter pack args of types Args_tmp.... When called, it recursively adds the objects from t to the parameter pack one at a time, from beginning (0) to end, and calls the function again with the new incremented parameter pack.

The second function's signature is almost identical to the first, except that it uses type T... for the parameter pack args. Thus, once args in the first function is completely filled with the values from t, it's type will be T... (in psuedo-code, typeid(T...) == typeid(Args_tmp...)), and thus the compiler will instead call the second overloaded function, which in turn calls func(args...).

The code in the static functor example works identically, with the functor instead used as a class template argument.

Related