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.
Tuples are immutable. Lists are mutable. Tuples can contain different data types. Lists consist of a singular data type.
Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair. If std::is_trivially_destructible<Ti>::value is true for every Ti in Types , the destructor of tuple is trivial.
Tuple types The template parameters specify the types of the tuple elements. The current version supports tuples with 0-10 elements. If necessary, the upper limit can be increased up to, say, a few dozen elements. The data element can be any C++ type.
get(): get() is used to access the tuple values and modify them, it accepts the index and tuple name as arguments to access a particular tuple element. 2. make_tuple(): make_tuple() is used to assign tuple with values. The values passed should be in order with the values declared in the tuple.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With