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.
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));
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.
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
来源:https://stackoverflow.com/questions/28343591/applying-func-to-elements-in-stdtuple-in-the-natural-not-reverse-order