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

65,329

Solution 1

In C++17 you can do this:

std::apply(the_function, the_tuple);

This already works in Clang++ 3.9, using std::experimental::apply.

Responding to the comment saying that this won't work if the_function is templated, the following is a work-around:

#include <tuple>

template <typename T, typename U> void my_func(T &&t, U &&u) {}

int main(int argc, char *argv[argc]) {

  std::tuple<int, float> my_tuple;

  std::apply([](auto &&... args) { my_func(args...); }, my_tuple);

  return 0;
}

This work around is a simplified solution to the general problem of passing overload sets and function template where a function would be expected. The general solution (one that is taking care of perfect-forwarding, constexpr-ness, and noexcept-ness) is presented here: https://blog.tartanllama.xyz/passing-overload-sets/.

Solution 2

Here's my code if anyone is interested

Basically at compile time the compiler will recursively unroll all arguments in various inclusive function calls <N> -> calls <N-1> -> calls ... -> calls <0> which is the last one and the compiler will optimize away the various intermediate function calls to only keep the last one which is the equivalent of func(arg1, arg2, arg3, ...)

Provided are 2 versions, one for a function called on an object and the other for a static function.

#include <tr1/tuple>

/**
 * Object Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_obj_func
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_obj_func<N-1>::applyTuple( pObj, f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_obj_func<0>
{
  template < typename T, typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( T* pObj,
                          void (T::*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    (pObj->*f)( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Object Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename T, typename... ArgsF, typename... ArgsT >
void applyTuple( T* pObj,
                 void (T::*f)( ArgsF... ),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_obj_func<sizeof...(ArgsT)>::applyTuple( pObj, f, t );
}

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @tparam N Number of tuple arguments to unroll
 *
 * @ingroup g_util_tuple
 */
template < uint N >
struct apply_func
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& t,
                          Args... args )
  {
    apply_func<N-1>::applyTuple( f, t, std::tr1::get<N-1>( t ), args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Tuple Argument Unpacking End Point
 *
 * This recursive template unpacks the tuple parameters into
 * variadic template arguments until we reach the count of 0 where the function
 * is called with the correct parameters
 *
 * @ingroup g_util_tuple
 */
template <>
struct apply_func<0>
{
  template < typename... ArgsF, typename... ArgsT, typename... Args >
  static void applyTuple( void (*f)( ArgsF... ),
                          const std::tr1::tuple<ArgsT...>& /* t */,
                          Args... args )
  {
    f( args... );
  }
};

//-----------------------------------------------------------------------------

/**
 * Static Function Call Forwarding Using Tuple Pack Parameters
 */
// Actual apply function
template < typename... ArgsF, typename... ArgsT >
void applyTuple( void (*f)(ArgsF...),
                 std::tr1::tuple<ArgsT...> const& t )
{
   apply_func<sizeof...(ArgsT)>::applyTuple( f, t );
}

// ***************************************
// Usage
// ***************************************

template < typename T, typename... Args >
class Message : public IMessage
{

  typedef void (T::*F)( Args... args );

public:

  Message( const std::string& name,
           T& obj,
           F pFunc,
           Args... args );

private:

  virtual void doDispatch( );

  T*  pObj_;
  F   pFunc_;
  std::tr1::tuple<Args...> args_;
};

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
Message<T, Args...>::Message( const std::string& name,
                              T& obj,
                              F pFunc,
                              Args... args )
: IMessage( name ),
  pObj_( &obj ),
  pFunc_( pFunc ),
  args_( std::forward<Args>(args)... )
{

}

//-----------------------------------------------------------------------------

template < typename T, typename... Args >
void Message<T, Args...>::doDispatch( )
{
  try
  {
    applyTuple( pObj_, pFunc_, args_ );
  }
  catch ( std::exception& e )
  {

  }
}

Solution 3

In C++ there is many ways of expanding/unpacking tuple and apply those tuple elements to a variadic template function. Here is a small helper class which creates index array. It is used a lot in template metaprogramming:

// ------------- UTILITY---------------
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

Now the code which does the job is not that big:

 // ----------UNPACK TUPLE AND APPLY TO FUNCTION ---------
#include <tuple>
#include <iostream> 

using namespace std;

template<class Ret, class... Args, int... Indexes > 
Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup) 
{ 
    return pf( forward<Args>( get<Indexes>(tup))... ); 
} 

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), const tuple<Args...>&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), tuple<Args...>(tup));
}

template<class Ret, class ... Args> 
Ret apply(Ret (*pf)(Args...), tuple<Args...>&&  tup)
{
    return apply_helper(pf, typename make_indexes<Args...>::type(), forward<tuple<Args...>>(tup));
}

Test is shown bellow:

// --------------------- TEST ------------------
void one(int i, double d)
{
    std::cout << "function one(" << i << ", " << d << ");\n";
}
int two(int i)
{
    std::cout << "function two(" << i << ");\n";
    return i;
}

int main()
{
    std::tuple<int, double> tup(23, 4.5);
    apply(one, tup);

    int d = apply(two, std::make_tuple(2));    

    return 0;
}

I'm not big expert in other languages, but I guess that if these languages do not have such functionality in their menu, there is no way to do that. At least with C++ you can, and I think it is not so much complicated...

Solution 4

I find this to be the most elegant solution (and it is optimally forwarded):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a)
        -> decltype(Apply<N-1>::apply(
            ::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        ))
    {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a)
        -> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
    {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t)
    -> decltype(Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
    return Apply< ::std::tuple_size<
        typename ::std::decay<T>::type
    >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Example usage:

void foo(int i, bool b);

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&foo, t);
}

Unfortunately GCC (4.6 at least) fails to compile this with "sorry, unimplemented: mangling overload" (which simply means that the compiler doesn't yet fully implement the C++11 spec), and since it uses variadic templates, it wont work in MSVC, so it is more or less useless. However, once there is a compiler that supports the spec, it will be the best approach IMHO. (Note: it isn't that hard to modify this so that you can work around the deficiencies in GCC, or to implement it with Boost Preprocessor, but it ruins the elegance, so this is the version I am posting.)

GCC 4.7 now supports this code just fine.

Edit: Added forward around actual function call to support rvalue reference form *this in case you are using clang (or if anybody else actually gets around to adding it).

Edit: Added missing forward around the function object in the non-member apply function's body. Thanks to pheedbaq for pointing out that it was missing.

Edit: And here is the C++14 version just since it is so much nicer (doesn't actually compile yet):

#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T && t, A &&... a) {
        return Apply<N-1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
            ::std::get<N-1>(::std::forward<T>(t)), ::std::forward<A>(a)...
        );
    }
};

template<>
struct Apply<0> {
    template<typename F, typename T, typename... A>
    static inline auto apply(F && f, T &&, A &&... a) {
        return ::std::forward<F>(f)(::std::forward<A>(a)...);
    }
};

template<typename F, typename T>
inline auto apply(F && f, T && t) {
    return Apply< ::std::tuple_size< ::std::decay_t<T>
      >::value>::apply(::std::forward<F>(f), ::std::forward<T>(t));
}

Here is a version for member functions (not tested very much!):

using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.

template<size_t N>
struct ApplyMember
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
        decltype(ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...))
    {
        return ApplyMember<N-1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N-1>(forward<T>(t)), forward<A>(a)...);
    }
};

template<>
struct ApplyMember<0>
{
    template<typename C, typename F, typename T, typename... A>
    static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
        decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
    {
        return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
    }
};

// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
    decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
    return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
// Example:

class MyClass
{
public:
    void foo(int i, bool b);
};

MyClass mc;

std::tuple<int, bool> t = make_tuple(20, false);

void m()
{
    apply(&mc, &MyClass::foo, t);
}

Solution 5

template<typename F, typename Tuple, std::size_t ... I>
auto apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename Tuple>
auto apply(F&& f, Tuple&& t) {
    using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}

This is adapted from the C++14 draft using index_sequence. I might propose to have apply in a future standard (TS).

Share:
65,329

Related videos on Youtube

Xeo
Author by

Xeo

Game Programmer, Bookworm, Japanese Culture Fanatic, C++ Lover and Template Hacker. I'm usually found hanging out in the Lounge, where the cool kids are. Sanity is just a mask.

Updated on May 11, 2020

Comments

  • Xeo
    Xeo about 4 years

    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?

    • Skeen
      Skeen over 10 years
      The C++14 standard has a solution see; open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
    • Skeen
      Skeen over 10 years
      The idea is to unpack the tuple in a single variadic blast, using integer_sequence, see en.cppreference.com/w/cpp/utility/integer_sequence
    • Skeen
      Skeen over 10 years
      Having an integer_sequence S, you simply call your function as func(std::get<S>(tuple)...), and let the compiler handle the rest.
    • lewis
      lewis about 5 years
      If using C++17 or later, ignore this answer and see the one below using std::apply
  • Daniel Earwicker
    Daniel Earwicker over 15 years
    I wonder why they even bother having separate notions of tuple and function argument pack. Maybe in a conforming compiler they are interchangeable but I haven't spotted an indication of that anywhere I've read about them.
  • user1685501
    user1685501 about 15 years
    Because tuple<int, char, string> is necessary as a separate type; as is the ability to make a function that doesn't require make_type in the middle of every call.
  • user1685501
    user1685501 about 15 years
    Also, the best place is not comp.lang.c++.moderated. Questions about C++1x are almost always better directed to comp.std.c++.
  • HighCommander4
    HighCommander4 almost 14 years
    Is it possible to adapt this to work in a case where the "function" in question is actually a constructor?
  • David
    David almost 14 years
    Could you provide an example of what you want to do and we can go from there.
  • Goofy
    Goofy over 13 years
    This solution proviedes only a compile time overhead and at the end it will be simplified to (pObj->*f)( arg0, arg,1, ... argN); right?
  • David
    David over 13 years
    yes, the compiler will compress the multiple function calls into the final one as if you had written it yourself which is the beauty of all this meta programming stuff.
  • Xeo
    Xeo over 12 years
    The question was the other way around. Not Args... -> tuple, but tuple -> Args....
  • kfmfe04
    kfmfe04 over 11 years
    +1 out of the answers listed, yours was the closest I could get to working with arguments whose arguments are vectors... ...but I am still getting compile errors. ideone.com/xH5kBH If you compile this with -DDIRECT_CALL and run it, you will see what the output should be. I get a compile error otherwise (I think decltype is not smart enough to figure out my special case), with gcc 4.7.2.
  • DRayX
    DRayX over 11 years
    The version of gcc on ideaone is to old for this to pass, it doesn't support the mangled decltype return type overloading. I have tested this code relatively thoroughly in gcc 4.7.2, and I haven't run into any problems. With gcc 4.8, you can use the new C++17 automatic return value feature to avoid all the nasty decltype trailing return types.
  • kfmfe04
    kfmfe04 over 11 years
    +1 I really like that automatic return value feature - thx for the heads up
  • DRayX
    DRayX over 11 years
    I know, in my book the lack of the automatic return value thing for single statement (non-lambda) functions was the biggest miss of C++11. That and shared_from_this.
  • Brett Rossier
    Brett Rossier over 11 years
    Out of curiosity, in the non-member apply function, why is f not wrapped with a std::forward call, as it is in the return type? Is it not needed?
  • DRayX
    DRayX about 11 years
    That would be because I forgot it :) Fixed
  • DRayX
    DRayX almost 11 years
    Out of curiosity, I tried compiling this in GCC 4.8, and foo('x', true) compiled to the exact same assembly code as apply(foo, ::std::make_tuple('x', true)) with any level of optimization besides -O0.
  • Ryan Haining
    Ryan Haining almost 11 years
    all the tr1 stuff can get taken out now with c++11
  • PeterSom
    PeterSom almost 11 years
    With C++14 integer_sequence you even get an almost correct implementation of apply() in its example. see my answer below.
  • tower120
    tower120 almost 10 years
    This is really short version, no doubts. But what about calling member function?
  • tower120
    tower120 almost 10 years
    Why you make object argument a const pointer? Not reference, not const reference, not just pointer? What if callable function will not const?
  • Timmmm
    Timmmm over 8 years
    @tower120: I added a version for member functions. It compiles but I haven't tested it. Should work though!
  • DrP3pp3r
    DrP3pp3r over 8 years
    One thing I cannot quite grasp: Why does the functor/function object f in Apply<0> need to be forwarded for the call?
  • DRayX
    DRayX over 8 years
    The functor gets forwarded for the call due the rvalue from *this spec. It allows you to have a different overload if *this is an rvalue. For example, a copy method could actually return the object itself if *this is an rvalue.
  • Zitrax
    Zitrax almost 7 years
    According to the example code at std::apply it does not seem to work if the_function is templated.
  • Zitrax
    Zitrax almost 7 years
    When called as in the example on a non templated function this works, but OP's question specifically mentioned a variadic template. If I use that I get errors about template argument deduction/substitution failed. Just as the example for the C++17's std::apply which mentions that same error. Is this supposed to also work on variadic templates or am I missing something ?
  • Zitrax
    Zitrax almost 7 years
    "... and apply those tuple elements to a variadic template function". The test section only contains non template variadic functions though. If I add one like template<class ... T> void three(T...) {} and try to use apply on that it does not compile.
  • CrepeGoat
    CrepeGoat over 6 years
    any comments on the compile-time optimization of the first option would be appreciated, so I can make my answer more complete (and maybe learn something new).
  • Erbureth
    Erbureth about 6 years
    @Zitrax You can specify the function's template arguments: std::apply(add_generic<float>, std::make_pair(2.0f, 3.0f));
  • lewis
    lewis about 5 years
    If using C++17 or later, ignore this answer and see the one below using std::apply
  • Elliott
    Elliott over 4 years
    This is the simplest, most elegant solution. And it works wonders. Thanks so much, M. Alaggan!!!!!! +100 votes
  • alfC
    alfC over 3 years
    Unfortunatelly, this technique doesn't work in nvcc (and probably other EDG-based compilers). It fails with error: template parameter pack not at end of parameter list, in the line template<class Ret, class... Args, int... Indexes > Ret apply_helper( Ret (*pf)(Args...), index_tuple< Indexes... >, tuple<Args...>&& tup) .