una tuple tupla que cpp c++ c++11 tuples

tupla - Cómo iterar sobre un std:: tuple en C++ 11



tuple c++ (3)

Esta pregunta ya tiene una respuesta aquí:

He hecho la siguiente tupla:

¿Quiero saber cómo debo iterar sobre él? Hay tupl_size() , pero al leer la documentación, no entendí cómo utilizarla. También he buscado SO, pero las preguntas parecen estar alrededor de Boost::tuple .

auto some = make_tuple("I am good", 255, 2.1);


Aquí hay un intento de descomponer la iteración sobre una tupla en partes componentes.

Primero, una función que representa hacer una secuencia de operaciones en orden. Tenga en cuenta que muchos compiladores encuentran esto difícil de entender, a pesar de que es legal C ++ 11 por lo que puedo decir:

template<class... Fs> void do_in_order( Fs&&... fs ) { int unused[] = { 0, ( (void)std::forward<Fs>(fs)(), 0 )... } (void)unused; // blocks warnings }

A continuación, una función que toma un std::tuple y extrae los índices necesarios para acceder a cada elemento. Al hacerlo, podemos perfeccionar más adelante.

Como beneficio adicional, mi código es compatible con std::pair y std::array iteration:

template<class T> constexpr std::make_index_sequence<std::tuple_size<T>::value> get_indexes( T const& ) { return {}; }

La carne y las papas:

template<size_t... Is, class Tuple, class F> void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) { using std::get; do_in_order( [&]{ f( get<Is>(std::forward<Tuple>(tup)) ); }... ); }

y la interfaz de cara al público:

template<class Tuple, class F> void for_each( Tuple&& tup, F&& f ) { auto indexes = get_indexes(tup); for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f) ); }

mientras indica que Tuple funciona en std::array s y std::pair s. También reenvía la categoría de valor r / l de dicho objeto al objeto de función que invoca. También tenga en cuenta que si tiene una función gratuita, get<N> en su tipo personalizado, y anule get_indexes , la anterior for_each funcionará en su tipo personalizado.

Como se indicó, muchos compiladores no admiten la orden do_in_order si está ordenada, ya que no les gusta el lambda con paquetes de parámetros sin expandir que se expanden en paquetes de parámetros.

Podemos en línea do_in_order en ese caso

template<size_t... Is, class Tuple, class F> void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) { using std::get; int unused[] = { 0, ( (void)f(get<Is>(std::forward<Tuple>(tup)), 0 )... } (void)unused; // blocks warnings }

Esto no cuesta mucho verbosidad, pero personalmente lo encuentro menos claro. La magia de la sombra de cómo funciona do_in_order está oscurecida al hacerlo en línea en mi opinión.

index_sequence (y las plantillas compatibles) es una característica de C ++ 14 que se puede escribir en C ++ 11. Encontrar dicha implementación en el desbordamiento de pila es fácil. Un top hit actual de Google es una implementación decente de profundidad O (lg (n)) , que si leo los comentarios correctamente puede ser la base para al menos una iteración de la propia gcc make_integer_sequence (los comentarios también señalan más tiempo de compilación) Mejoras en torno a la eliminación de sizeof... llamadas).

Alternativamente podemos escribir:

template<class F, class...Args> void for_each_arg(F&&f,Args&&...args){ using discard=int[]; (void)discard{0,((void)( f(std::forward<Args>(args)) ),0)...}; }

Y entonces:

template<size_t... Is, class Tuple, class F> void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) { using std::get; for_each_arg( std::forward<F>(f), get<Is>(std::forward<Tuple>(tup))... ); }

Lo que evita que el manual se expanda pero se compile en más compiladores. Pasamos el Is través del parámetro auto&&i .

En C ++ 1z también podemos usar std::apply con un objeto de función for_each_arg para eliminar el desorden de índices.


Aquí hay una solución similar y más detallada que la aceptada anteriormente por TC, que quizás sea un poco más fácil de entender (es probable que sea la misma que la de otros miles en la red):

template<typename TupleType, typename FunctionType> void for_each(TupleType&&, FunctionType , std::integral_constant<size_t, std::tuple_size<typename std::remove_reference<TupleType>::type >::value>) {} template<std::size_t I, typename TupleType, typename FunctionType , typename = typename std::enable_if<I!=std::tuple_size<typename std::remove_reference<TupleType>::type>::value>::type > void for_each(TupleType&& t, FunctionType f, std::integral_constant<size_t, I>) { f(std::get<I>(t)); for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, I + 1>()); } template<typename TupleType, typename FunctionType> void for_each(TupleType&& t, FunctionType f) { for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, 0>()); }

Uso (con std::tuple ):

auto some = std::make_tuple("I am good", 255, 2.1); for_each(some, [](const auto &x) { std::cout << x << std::endl; });

Uso (con std::array ):

std::array<std::string,2> some2 = {"Also good", "Hello world"}; for_each(some2, [](const auto &x) { std::cout << x << std::endl; });

DEMO

Idea general: como en la solución de TC, comience con un índice I=0 y vaya hasta el tamaño de la tupla. Sin embargo, aquí no se hace por expansión variad sino individualmente.

Explicación:

  • La primera sobrecarga de for_each se llama si I es igual al tamaño de la tupla. La función entonces simplemente no hace nada y así termina la recursión.

  • La segunda sobrecarga llama a la función con el argumento std::get<I>(t) y aumenta el índice en uno. La clase std::integral_constant es necesaria para resolver el valor de I en tiempo de compilación. std::enable_if SFINAE se usa para ayudar al compilador a separar esta sobrecarga de la anterior, y llamar a esta sobrecarga solo si I es más pequeño que el tamaño de la tupla (en Coliru esto es necesario, mientras que en Visual Studio funciona sin él) .

  • El tercero comienza la recursión con I=0 . Es la sobrecarga que se suele llamar desde el exterior.


EDITAR: También he incluido la idea mencionada por Yakk para admitir adicionalmente std::array y std::pair mediante el uso de un parámetro de plantilla general TupleType lugar de uno especializado para std::tuple<Ts ...> .

Como el tipo TupleType debe deducirse y es una "referencia universal", esto tiene la ventaja de que se obtiene un reenvío perfecto de forma gratuita. La desventaja es que uno tiene que usar otro typename std::remove_reference<TupleType>::type indirecto a través de typename std::remove_reference<TupleType>::type , ya que TupleType también puede ser deducido como un tipo de referencia.


template<class F, class...Ts, std::size_t...Is> void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func, std::index_sequence<Is...>){ using expander = int[]; (void)expander { 0, ((void)func(std::get<Is>(tuple)), 0)... }; } template<class F, class...Ts> void for_each_in_tuple(const std::tuple<Ts...> & tuple, F func){ for_each_in_tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>()); }

Uso:

auto some = std::make_tuple("I am good", 255, 2.1); for_each_in_tuple(some, [](const auto &x) { std::cout << x << std::endl; });

Demo

std::index_sequence y family son características de C ++ 14, pero se pueden implementar fácilmente en C ++ 11 (hay muchas disponibles en SO). Las lambdas polimórficas también son C ++ 14, pero pueden reemplazarse con un functor personalizado.