7

I need to call a - template or overloaded - function for each element in an arbitrary tuple. To be precise, I need to call this function on the elements as they are specified in the tuple.

For example. I have a tuple std::tuple<int, float> t{1, 2.0f}; and a functional

class Lambda{
public: 
   template<class T>
   void operator()(T arg){ std::cout << arg << "; "; }
};

I need some struct/function Apply, which, if called like Apply<Lambda, int, float>()(Lambda(), t) would yield:

1; 2.0f; 

and NOT 2.0f; 1;.

Note that I know the solution, if a "raw" parameter pack is passed in to the function and I know how to do that for tuples in the reverse order. But the following attempt of partially specializing Apply fails:

template<class Func, size_t index, class ...Components>
class ForwardsApplicator{
public:
    void operator()(Func func, const std::tuple<Components...>& t){
        func(std::get<index>(t));
        ForwardsApplicator<Func, index + 1, Components...>()(func, t);
    }
};

template<class Func, class... Components>
class ForwardsApplicator < Func, sizeof...(Components), Components... > {
public:
    void operator()(Func func, const std::tuple<Components...>& t){}
};

int main{
    ForwardsApplicator<Lambda, 0, int, float>()(Lambda{}, std::make_tuple(1, 2.0f));
}

The code is compiled but only the first argument is printed. However, if I replace the ForwardsApplicator specialization with

template<class Func, class... Components>
class ForwardsApplicator < Func, 2, Components... >{...}

it works correctly - but, of course, only for tuples with length 2. How do I do that - if possible, elegantly -- for tuples of arbitrary length?

EDIT: Thanks guys for your answers! All three are really straight-to-the-point and explain the issue from all possible vantage points.

Casey
  • 41,449
  • 7
  • 95
  • 125
Mischa
  • 215
  • 1
  • 9

3 Answers3

9

This is a textbook case for the integer_sequence trick.

template<class Func, class Tuple, size_t...Is>
void for_each_in_tuple(Func f, Tuple&& tuple, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)f(std::get<Is>(std::forward<Tuple>(tuple))), 0)... };
}

template<class Func, class Tuple>
void for_each_in_tuple(Func f, Tuple&& tuple){
    for_each_in_tuple(f, std::forward<Tuple>(tuple),
               std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
}

Demo.

std::index_sequence and friends are C++14, but it's a pure library extension and can easily be implemented in C++11. You can easily find half a dozen implementations on SO.

T.C.
  • 133,968
  • 17
  • 288
  • 421
6

The problem is that size...(Components) can not be used in the specialization for an unknown type list Components. GCC complains about this with the error:

prog.cpp:16:7: error: template argument 'sizeof... (Components)' involves template parameter(s)
 class ForwardsApplicator < Func, sizeof...(Components), Components... > {
       ^

I suggest a slightly different approach. First, move the type list Components into the template parameter for the operator(), i.e.:

template<class ...Components>
void operator()(Func func, const std::tuple<Components...>& t) {
    ...
}

Then, reverse the call order: first do a recursive call, then the invokation with index-1 (i.e. call on the last tuple element). Start this recursion with index = sizeof...(Components) and go until index = 0 which is noop (so the specialization has 0, independent of sizeof...(Components) which was the problem I began to talk about).

To help call this, add a function for template argument deduction:

// General case (recursion)
template<class Func, size_t index>
class ForwardsApplicator{
public:
    template<class ...Components>
    void operator()(Func func, const std::tuple<Components...>& t){
        ForwardsApplicator<Func, index - 1>()(func, t);
        func(std::get<index - 1>(t));
    }
};

// Special case (stop recursion)
template<class Func>
class ForwardsApplicator<Func, 0> {
public:
    template<class ...Components>
    void operator()(Func func, const std::tuple<Components...>& t){}
};

// Helper function for template type deduction
template<class Func, class ...Components>
void apply(Func func, const std::tuple<Components...>& t) {
    ForwardsApplicator<Func, sizeof...(Components)>()(func, t);
}

It is then easy to invoke, without the need for any template parameters on the call site:

apply(Lambda{}, std::make_tuple(1, 2.0f));

Live demo

leemes
  • 44,967
  • 21
  • 135
  • 183
  • That's the answer I was looking for: elegant and very close to the design I had in mind. Thanks! – Mischa Feb 05 '15 at 12:54
  • 1
    You're right about that specialized non-type template argument being illegal. I think the OP is using VC12, which doesn't issue any errors but doesn't do anything good either; the problem has been fixed in VC14 CTP5, which issues a nice error. It's worth noting that Clang (3.5.0 and also trunk 228146) compiles the code with no diagnostic and matches the specialization, too. This should be reported. – bogdan Feb 05 '15 at 13:55
2

Counting down in the template doesn't have to mean that you process the tuple elements in that same order. A simple approach to process tuples head to tail is head recursion (as opposed to tail recursion):

#include <tuple>
#include <iostream>

// Our entry point is the tuple size
template<typename Tuple, std::size_t i = std::tuple_size<Tuple>::value>
struct tuple_applicator {
  template<typename Func> static void apply(Tuple &t, Func &&f) {
    // but we recurse before we process the element associated with that
    // number, reversing the order so that the front elements are processed
    // first.
    tuple_applicator<Tuple, i - 1>::apply(t, std::forward<Func>(f));
    std::forward<Func>(f)(std::get<i - 1>(t));
  }
};

// The recursion stops here.
template<typename Tuple>
struct tuple_applicator<Tuple, 0> {
  template<typename Func> static void apply(Tuple &, Func &&) { }
};

// and this is syntactical sugar
template<typename Tuple, typename Func>
void tuple_apply(Tuple &t, Func &&f) {
  tuple_applicator<Tuple>::apply(t, std::forward<Func>(f));
}

int main() {
  std::tuple<int, double> t { 1, 2.3 };

  // The generic lambda requires C++14, the rest
  // works with C++11 as well. Put your Lambda here instead.
  tuple_apply(t, [](auto x) { std::cout << x * 2 << '\n'; });
}

The output is

2
4.6
Wintermute
  • 42,983
  • 5
  • 77
  • 80