Tupla de plantilla: llamar a una función en cada elemento

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
  }
};

preguntado el 05 de mayo de 13 a las 18:05

Posible duplicado de iterar sobre tupla -

7 Respuestas

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 plantilla de clase):

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)>());
}

Puede utilizar el 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());
}

Aquí hay una ejemplo vivo.

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 ejemplo vivo.

Noticias

Si estás usando C ++ 14 or later, you can replace the seq y gen_seq classes above with std::integer_sequence al igual que:

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)>());
}

Si estás usando C ++ 17 or later you can do this (from este comentario a continuación):

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

Respondido 31 Jul 20, 16:07

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 :) - 7 vacas

@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 ;) - Andy merodea

void for_each(T&& t, F f, seq<Is...>): Why does the last argument not have an identifier? - chico de plantilla

Las 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>) - Andy merodea

So many year later, could the first part be replaced by std::integer_sequence? - JHBonarius

En C ++ 17 puedes hacer esto:

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

Dado que some_function has suitable overloads for all the types in the tuple.

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

Respondido 12 Feb 19, 02:02

¿No conduce esto a la iteración, es decir, llamadas de do_something() - ocurre en un orden no especificado, porque el paquete de parámetros se expande dentro de una llamada de función (), donde los argumentos tienen un orden no especificado? Eso podría ser muy significativo; Me imagino que la mayoría de la gente esperaría que se garantice que el orden ocurra en el mismo orden que los miembros, es decir, como los índices std::get<>(). AFAIK, para obtener pedidos garantizados en casos como este, la expansión debe realizarse dentro de {braces}. ¿Me equivoco? Esta respuesta pone énfasis en tal orden: stackoverflow.com/a/16387374/2757035 - subrayado_d

@underscore_d C++17 guarantees the execution order of function arguments. Since this answer applies to C++17, this is valid. - Guillaume Racicot

@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 considerado 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. - subrayado_d

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 regresar void (and even evil classes with overloaded operator ,). - Jarod42

@Jarod42 since we talk about c++17 here, a fold expression like std::apply([](auto& ...x){(..., some_function(x));}, the_tuple); es mejor stackoverflow.com/a/45498003/8414561 - Desarrollador nulo

Además de nuestras localidaded en la respuesta 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);

(ejemplo vivo).

Porque de otra manera order of evaluation of function arguments is unspecified.

Respondido 15 Abr '19, 06:04

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. - Zheng Qu

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? - user3882729

@user3882729 not that I know of. - Desarrollador nulo

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 parámetro de plantilla para 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.

contestado el 05 de mayo de 13 a las 22:05

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... :) - 7 vacas

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

@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? - Yakk - Adam Nevraumont

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;
}

contestado el 06 de mayo de 13 a las 11:05

this answer was much more helpful to me, and it doesn't generate warnings like some of the others. +1 - brad allred

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") );
}

Respondido 26 Jul 16, 10:07

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), Con un Func&& func argument to allow passing a temporary lambda. - Adi Shavit

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;
    });
}

contestado el 08 de mayo de 20 a las 00:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.