Template tuple - calling a function on each element
Asked Answered
B

7

53

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
  }
};
Befoul answered 5/5, 2013 at 17:53 Comment(1)
Possible duplicate of iterate over tupleHertz
M
51

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);
Majuscule answered 5/5, 2013 at 17:55 Comment(9)
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 :)Befoul
@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 ;)Majuscule
void for_each(T&& t, F f, seq<Is...>): Why does the last argument not have an identifier?Untaught
@AndyProwl What is its purpose?Untaught
Yeah :) Why the auto l variable that isn't used, and why (in the same line) the extra 0 argument?Befoul
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>)Majuscule
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...Entomostracan
So many year later, could the first part be replaced by std::integer_sequence?Manufacturer
@Manufacturer 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.Scherzo
M
46

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.

Modernity answered 8/5, 2016 at 13:7 Comment(10)
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: https://mcmap.net/q/17480/-template-tuple-calling-a-function-on-each-elementNianiabi
@Nianiabi C++17 guarantees the execution order of function arguments. Since this answer applies to C++17, this is valid.Benzoyl
@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.Nianiabi
I agree that this will to undefined iteration order, even with C++17.Torey
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 ,).Maje
@Maje since we talk about c++17 here, a fold expression like std::apply([](auto& ...x){(..., some_function(x));}, the_tuple); is better https://mcmap.net/q/17480/-template-tuple-calling-a-function-on-each-elementDecoction
@DevNull: Mine also uses fold expression, but uses forward argument and handles class with evil overload comma. Sad than simplicity has some pitfall :-/Maje
@Maje 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?Decoction
@DevNull: Demo of evil operator comma in action.Maje
Wondering how I could get this to apply only to a function where a tuple is passed though...Fanelli
D
44

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.

Decoction answered 4/8, 2017 at 3:53 Comment(4)
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.Sommelier
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?Beitz
@Beitz not that I know of.Decoction
@Beitz Left and right folds are the same when using the comma operator. They resolve to (some_function(arg1), some_function(arg2)), some_function(arg3) and some_function(arg1), (some_function(arg2), some_function(arg3)) respectively, which are equivalent.Buschi
R
8

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.

Ringdove answered 5/5, 2013 at 20:34 Comment(3)
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... :)Befoul
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.Entomostracan
@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?Entomostracan
M
6

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;
}
Marmara answered 6/5, 2013 at 10:50 Comment(1)
this answer was much more helpful to me, and it doesn't generate warnings like some of the others. +1Monroy
D
2

I was testing with tuples and metaprograming and found the current thread. I think my work can inspire someone else although I like the solution of @Andy.

Anyway, just get fun!

#include <tuple>
#include <type_traits>
#include <iostream>
#include <sstream>
#include <functional>

template<std::size_t I = 0, typename Tuple, typename Func>
typename std::enable_if< I != std::tuple_size<Tuple>::value, void >::type
for_each(const Tuple& tuple, Func&& func)
{
    func(std::get<I>(tuple));
    for_each<I + 1>(tuple, func);
}

template<std::size_t I = 0, typename Tuple, typename Func>
typename std::enable_if< I == std::tuple_size<Tuple>::value, void >::type
for_each(const Tuple& tuple, Func&& func)
{
    // do nothing
}


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

template<typename... Params>
void test(Params&& ... params)
{
    int sz = sizeof...(params);
    std::tuple<Params...> values(std::forward<Params>(params)...);
    for_each(values, print() );
}


class MyClass
{
public:
    MyClass(const std::string& text) 
        : m_text(text)
    {
    }

    friend std::ostream& operator <<(std::ostream& stream, const MyClass& myClass)
    {
        stream << myClass.m_text;
        return stream;
    }

private:
    std::string m_text;
};


int main()
{
    test(1, "hello", 3.f, 4, MyClass("I don't care") );
}
Darn answered 3/10, 2014 at 14:57 Comment(1)
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.Chelate
A
0

Boost mp11 has this functionality:

#include <iostream>
#include <string>
#include <boost/mp11.hpp>

using namespace std;
using boost::mp11::tuple_for_each;

std::tuple t{string("abc"), 47 };

int main(){
    tuple_for_each(t,[](const auto& x){
        cout << x + x << endl;
    });
}
Ascot answered 7/5, 2020 at 23:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.