Template tuple - calling a function on each element

33,665

Solution 1

You can quite easily do that with some indices machinery. Given a meta-function gen_seq for generating compile-time integer sequences (encapsulated by the seq class template):

namespace detail
{
    template<int... Is>
    struct seq { };

    template<int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };

    template<int... Is>
    struct gen_seq<0, Is...> : seq<Is...> { };
}

And the following function templates:

#include <tuple>

namespace detail
{
    template<typename T, typename F, int... Is>
    void for_each(T&& t, F f, seq<Is...>)
    {
        auto l = { (f(std::get<Is>(t)), 0)... };
    }
}

template<typename... Ts, typename F>
void for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
    detail::for_each(t, f, detail::gen_seq<sizeof...(Ts)>());
}

You can use the for_each_in_tuple function above this way:

#include <string>
#include <iostream>

struct my_functor
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};

int main()
{
    std::tuple<int, double, std::string> t(42, 3.14, "Hello World!");
    for_each_in_tuple(t, my_functor());
}

Here is a live example.

In your concrete situation, this is how you could use it:

template<typename... Ts>
struct TupleOfVectors
{
    std::tuple<std::vector<Ts>...> t;

    void do_something_to_each_vec()
    {
        for_each_in_tuple(t, tuple_vector_functor());
    }

    struct tuple_vector_functor
    {
        template<typename T>
        void operator () (T const &v)
        {
            // Do something on the argument vector...
        }
    };
};

And once again, here is a live example.

Update

If you're using C++14 or later, you can replace the seq and gen_seq classes above with std::integer_sequence like so:

namespace detail
{
    template<typename T, typename F, int... Is>
    void
    for_each(T&& t, F f, std::integer_sequence<int, Is...>)
    {
        auto l = { (f(std::get<Is>(t)), 0)... };
    }
} // namespace detail

template<typename... Ts, typename F>
void
for_each_in_tuple(std::tuple<Ts...> const& t, F f)
{
    detail::for_each(t, f, std::make_integer_sequence<int, sizeof...(Ts)>());
}

If you're using C++17 or later you can do this (from this comment below):

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

Solution 2

In C++17 you can do this:

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple);

given that some_function has suitable overloads for all the types in the tuple.

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

Solution 3

In addition to the answer of @M. Alaggan, if you need to call a function on tuple elements in order of their appearance in the tuple, in C++17 you can also use a fold expression like this:

std::apply([](auto& ...x){(..., some_function(x));}, the_tuple);

(live example).

Because otherwise order of evaluation of function arguments is unspecified.

Solution 4

Here's one approach which may work well in your case:

template<typename... Ts>
struct TupleOfVectors {
    std::tuple<std::vector<Ts>...> tuple;

    void do_something_to_each_vec()
    {
        // First template parameter is just a dummy.
        do_something_to_each_vec_helper<0,Ts...>();
    }

    template<size_t N>
    void do_something_to_vec()
    {
        auto &vec = std::get<N>(tuple);
        //do something to vec
    }

private:
    // Anchor for the recursion
    template <int>
    void do_something_to_each_vec_helper() { }

    // Execute the function for each template argument.
    template <int,typename Arg,typename...Args>
    void do_something_to_each_vec_helper()
    {
        do_something_to_each_vec_helper<0,Args...>();
        do_something_to_vec<sizeof...(Args)>();
    }
};

The only thing that is a bit messy here is the extra dummy int template parameter to do_something_to_each_vec_helper. It is necessary to make the do_something_to_each_vec_helper still be a template when no arguments remain. If you had another template parameter you wanted to use, you could use it there instead.

Solution 5

If you are not particularly wedded to a solution in the form of generic "for each" function template then you can use one like this:

#ifndef TUPLE_OF_VECTORS_H
#define TUPLE_OF_VECTORS_H

#include <vector>
#include <tuple>
#include <iostream>

template<typename... Ts>
struct TupleOfVectors 
{
    std::tuple<std::vector<Ts>...> tuple;

    template<typename ...Args>
    TupleOfVectors(Args... args)
    : tuple(args...){}

    void do_something_to_each_vec() {
        do_something_to_vec(tuple);
    }

    template<size_t I = 0, class ...P>
    typename std::enable_if<I == sizeof...(P)>::type
    do_something_to_vec(std::tuple<P...> &) {}

    template<size_t I = 0, class ...P>
    typename std::enable_if<I < sizeof...(P)>::type
    do_something_to_vec(std::tuple<P...> & parts) {
        auto & part = std::get<I>(tuple);
        // Doing something...
        std::cout << "vector[" << I << "][0] = " << part[0] << std::endl;
        do_something_to_vec<I + 1>(parts);
    }
};

#endif // EOF

A test program, built with GCC 4.7.2 and clang 3.2:

#include "tuple_of_vectors.h"

using namespace std;

int main()
{
    TupleOfVectors<int,int,int,int> vecs(vector<int>(1,1),
        vector<int>(2,2),
        vector<int>(3,3),
        vector<int>(4,4));

    vecs.do_something_to_each_vec();
    return 0;
}

The same style of recursion can be used in a generic "for_each" function template without auxiliary indices apparatus:

#ifndef FOR_EACH_IN_TUPLE_H
#define FOR_EACH_IN_TUPLE_H

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

template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I == sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> &, Func) {}

template<size_t I = 0, typename Func, typename ...Ts>
typename std::enable_if<I < sizeof...(Ts)>::type
for_each_in_tuple(std::tuple<Ts...> & tpl, Func func) 
{
    func(std::get<I>(tpl));
    for_each_in_tuple<I + 1>(tpl,func);
}

#endif //EOF

And a test program for that:

#include "for_each_in_tuple.h"
#include <iostream>

struct functor
{
    template<typename T>
    void operator () (T&& t)
    {
        std::cout << t << std::endl;
    }
};

int main()
{
    auto tpl = std::make_tuple(1,2.0,"Three");
    for_each_in_tuple(tpl,functor());
    return 0;
}
Share:
33,665
7cows
Author by

7cows

Updated on November 24, 2020

Comments

  • 7cows
    7cows over 3 years

    My question is in the code:

    template<typename... Ts>
    struct TupleOfVectors {
      std::tuple<std::vector<Ts>...> tuple;
    
      void do_something_to_each_vec() {
        //Question: I want to do this:
        //  "for each (N)": do_something_to_vec<N>()
        //How?
      }
    
      template<size_t N>
      void do_something_to_vec() {
        auto &vec = std::get<N>(tuple);
        //do something to vec
      }
    };
    
  • 7cows
    7cows about 11 years
    metaprogramming is eating my brains. not sure where things start, what is calling what, and what/where the end result is. not like regular programming at all. Will take some time to decipher this :)
  • Andy Prowl
    Andy Prowl about 11 years
    @7cows: Of course. In the beginning it is not easy at all, and perhaps this is not exactly the easiest example to begin with, but I'm sure with some practice you will soon grasp it ;)
  • template boy
    template boy about 11 years
    void for_each(T&& t, F f, seq<Is...>): Why does the last argument not have an identifier?
  • template boy
    template boy about 11 years
    @AndyProwl What is its purpose?
  • 7cows
    7cows about 11 years
    Yeah :) Why the auto l variable that isn't used, and why (in the same line) the extra 0 argument?
  • Andy Prowl
    Andy Prowl about 11 years
    The auto l is to allow treating what comes on the right side as an initializer list, which ensures the expanded expressions are evaluated in the correct order. The (f(x), 0) uses the comma operator so that f(x) is evaluated, but the resulting value is discarded, and the value of the expression (f(x), 0) is 0. This is to give all elements of the initializer list the same type (int, here), in order to make it possible deducing the type of the initializer list (initializer_list<int>)
  • 7cows
    7cows about 11 years
    It's pretty brilliant how this manages to call the do_something_to_vec method in correct 0,1,2,... order isn't it? I like it... :)
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont about 11 years
    If your compiler supports it, try void do_in_order() {} template<typename F0, typename Fs...> void do_in_order( F0&& f0, Fs&&... fs ) { std::forward<F0>(f0)(); do_in_order(std::forward<FS>(fs)...); }, which is called like do_in_order([&]{f(std::get<Is>(t));}...); -- you create a variardic set of lambdas which are invoked by do_in_order. Above and beyond the problem that the above hack has holes in it (if f returns a type that overrides operator,, the result is unexpected), this better describes what you are doing. Sadly, many compilers lack the C++11 support required for this...
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont about 11 years
    template<typename Arg> void do_something_to_each_vec_helper() { do_something_to_vec<)(); template<typename Arg, typename...Args> void do_something_to_each_vec_helper() { do_something_to_each_vec_helper<Args...>(); do_something_to_vec<sizeof...(Args)>(); } gets rid of that unused int, but violates DRY (don't repeat yourself) to some degree. Hmm.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont about 11 years
    @VaughnCato really? I thought in the case of an ambiguity there, the one without the parameter pack was picked. Guess I was wrong -- maybe that only applies when it is a variardic set of arguments, and not pure types?
  • Brad Allred
    Brad Allred over 10 years
    this answer was much more helpful to me, and it doesn't generate warnings like some of the others. +1
  • Adi Shavit
    Adi Shavit almost 8 years
    Great piece of code! However, as-is it does not work with in-line lambdas as it expects an l-value func. The function signature should be changed to for_each(const Tuple& tuple, Func&& func), with a Func&& func argument to allow passing a temporary lambda.
  • underscore_d
    underscore_d over 7 years
    Doesn't this lead to the iteration - i.e. calls of do_something() - occurring in an unspecified order, because the parameter pack is expanded within a function call (), wherein arguments have unspecified ordering? That might be very significant; I'd imagine most people would expect the ordering to be guaranteed to occur in the same order as the members, i.e. as the indices to std::get<>(). AFAIK, to get guaranteed ordering in cases like this, the expansion must be done within {braces}. Am I wrong? This answer puts emphasis on such ordering: stackoverflow.com/a/16387374/2757035
  • Guillaume Racicot
    Guillaume Racicot about 7 years
    @underscore_d C++17 guarantees the execution order of function arguments. Since this answer applies to C++17, this is valid.
  • underscore_d
    underscore_d about 7 years
    @GuillaumeRacicot I'm aware of some changes to evaluation order/guarantees in certain contexts, but not in arguments to functions - other than some back-and-forth where left-to-right ordering was considered but rejected. Look at the current draft of the Standard: github.com/cplusplus/draft/blob/master/source/… The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.
  • Baptiste Wicht
    Baptiste Wicht about 7 years
    I agree that this will to undefined iteration order, even with C++17.
  • Jarod42
    Jarod42 over 6 years
    std::apply([](auto&& ...x){ (static_cast<void>(some_function(std::forward<decltype(x)>(x‌​))), ...);} , the_tuple); to guaranty order of evaluation, and allows some_function to return void (and even evil classes with overloaded operator ,).
  • Dev Null
    Dev Null about 6 years
    @Jarod42 since we talk about c++17 here, a fold expression like std::apply([](auto& ...x){(..., some_function(x));}, the_tuple); is better stackoverflow.com/a/45498003/8414561
  • Jarod42
    Jarod42 about 6 years
    @DevNull: Mine also uses fold expression, but uses forward argument and handles class with evil overload comma. Sad than simplicity has some pitfall :-/
  • Dev Null
    Dev Null about 6 years
    @Jarod42 yeah, didn't notice that, my bad; that's why I decided not to use forward to simplify reading :) could you please give an example of "evil overload comma" and problems that it can cause?
  • Jarod42
    Jarod42 about 6 years
    @DevNull: Demo of evil operator comma in action.
  • JHBonarius
    JHBonarius almost 6 years
    So many year later, could the first part be replaced by std::integer_sequence?
  • Olivia Stork
    Olivia Stork almost 4 years
    @JHBonarius yes you are correct! If you're running C++14 or later you can use std::integer_sequence instead of the seq and gen_seq structs.
  • Treviño
    Treviño over 3 years
    Wondering how I could get this to apply only to a function where a tuple is passed though...
  • Zheng Qu
    Zheng Qu over 2 years
    This is IMHO a better solution than the accepted one because its syntax is clearer and the call order is same as the tuple order.
  • user3882729
    user3882729 about 2 years
    Both left and right fold, i.e. (..., some_function(x)) vs. (some_function(x), ...) return the same answer in this case. Are there use cases where one would prefer one over the other?
  • Dev Null
    Dev Null about 2 years
    @user3882729 not that I know of.