2009-07-29 23 views
54

¿Cómo puedo iterar sobre una tupla (usando C++ 11)? He intentado lo siguiente, pero eso no funciona:iterar sobre la tupla

for(int i=0; i<std::tuple_size<T...>::value; ++i) 
    std::get<i>(my_tuple).do_sth(); 

de error 1: Lo sentimos, no se han aplicado: no se puede ampliar ‘Listener ...’ en una lista de argumentos de longitud fija.
Error 2: no puedo aparecer en una expresión constante.

Entonces, ¿cómo puedo iterar correctamente sobre los elementos de una tupla?

+2

¿Puedo preguntar, cómo se compila en C++ 0x? Por lo que yo sé, no está publicado ni está listo. – Burkhard

+5

g ++ contiene soporte experimental de algunas características de C++ 0X, incluidas plantillas variadic, desde la versión 4.3. Otros compiladores hacen lo mismo (con diferentes conjuntos de características, si quieres usarlos en producción, estás de vuelta en los 90 con una amplia variedad de soporte para las cosas más avanzadas) – AProgrammer

+0

¿Qué compilador usas? –

Respuesta

21

Boost.Fusion es una posibilidad:

ejemplo no probado:

struct DoSomething 
{ 
    template<typename T> 
    void operator()(T& t) const 
    { 
     t.do_sth(); 
    } 
}; 

tuple<....> t = ...; 
boost::fusion::for_each(t, DoSomething()); 
+5

¿funciona con std :: tuple? –

+0

@ViktorSehr AFAICT no (al menos en GCC 4.7.2)? ¿Alguien con una pista? – sehe

+0

@ViktorSehr Encontrado el problema: un error/omisión hace que el comportamiento de la fusión dependa del orden de los incluidos, consulte ** [Boost ticket # 8418] (https://svn.boost.org/trac/boost/ticket/8418) ** para obtener más detalles, – sehe

9

Es necesario utilizar metaprogramming plantilla, aquí mostrado con Boost.Tuple:

#include <boost/tuple/tuple.hpp> 
#include <iostream> 

template <typename T_Tuple, size_t size> 
struct print_tuple_helper { 
    static std::ostream & print(std::ostream & s, const T_Tuple & t) { 
     return print_tuple_helper<T_Tuple,size-1>::print(s, t) << boost::get<size-1>(t); 
    } 
}; 

template <typename T_Tuple> 
struct print_tuple_helper<T_Tuple,0> { 
    static std::ostream & print(std::ostream & s, const T_Tuple &) { 
     return s; 
    } 
}; 

template <typename T_Tuple> 
std::ostream & print_tuple(std::ostream & s, const T_Tuple & t) { 
    return print_tuple_helper<T_Tuple,boost::tuples::length<T_Tuple>::value>::print(s, t); 
} 

int main() { 

    const boost::tuple<int,char,float,char,double> t(0, ' ', 2.5f, '\n', 3.1416); 
    print_tuple(std::cout, t); 

    return 0; 
} 

En C++ 0x, puede escribir print_tuple() como una función de plantilla variadic lugar.

1

tupla de impulso proporciona funciones de ayuda get_head() y get_tail() por lo que sus funciones de ayuda puede tener este aspecto:

inline void call_do_sth(const null_type&) {}; 

template <class H, class T> 
inline void call_do_sth(cons<H, T>& x) { x.get_head().do_sth(); call_do_sth(x.get_tail()); } 

como se describe en aquí http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

con std::tuple debe ser similar.

En realidad, desafortunadamente std::tuple no parece proporcionar dicha interfaz, por lo que los métodos sugeridos anteriormente deberían funcionar, o tendría que cambiar a boost::tuple que tiene otros beneficios (como los operadores ya provistos). Aunque hay una desventaja de boost::tuple con gcc, aún no acepta plantillas variadic, pero eso ya puede estar arreglado ya que no tengo instalada la última versión de boost en mi máquina.

4

Si desea utilizar std :: tuple y tiene un compilador de C++ que admita plantillas variadic, intente con el código siguiente (probado con g ++ 4.5). Esta debería ser la respuesta a tu pregunta.

#include <tuple> 

// ------------- UTILITY--------------- 
template<int...> struct index_tuple{}; 

template<int I, typename IndexTuple, typename... Types> 
struct make_indexes_impl; 

template<int I, int... Indexes, typename T, typename ... Types> 
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...> 
{ 
    typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type; 
}; 

template<int I, int... Indexes> 
struct make_indexes_impl<I, index_tuple<Indexes...> > 
{ 
    typedef index_tuple<Indexes...> type; 
}; 

template<typename ... Types> 
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...> 
{}; 

// ----------- FOR EACH ----------------- 
template<typename Func, typename Last> 
void for_each_impl(Func&& f, Last&& last) 
{ 
    f(last); 
} 

template<typename Func, typename First, typename ... Rest> 
void for_each_impl(Func&& f, First&& first, Rest&&...rest) 
{ 
    f(first); 
    for_each_impl(std::forward<Func>(f), rest...); 
} 

template<typename Func, int ... Indexes, typename ... Args> 
void for_each_helper(Func&& f, index_tuple<Indexes...>, std::tuple<Args...>&& tup) 
{ 
    for_each_impl(std::forward<Func>(f), std::forward<Args>(std::get<Indexes>(tup))...); 
} 

template<typename Func, typename ... Args> 
void for_each(std::tuple<Args...>& tup, Func&& f) 
{ 
    for_each_helper(std::forward<Func>(f), 
        typename make_indexes<Args...>::type(), 
        std::forward<std::tuple<Args...>>(tup)); 
} 

template<typename Func, typename ... Args> 
void for_each(std::tuple<Args...>&& tup, Func&& f) 
{ 
    for_each_helper(std::forward<Func>(f), 
        typename make_indexes<Args...>::type(), 
        std::forward<std::tuple<Args...>>(tup)); 
} 

impulso :: fusión es otra opción, pero requiere su propio tipo tupla: impulso :: :: fusión tupla. ¡Mejor nos apeguemos al estándar! Aquí hay una prueba:

#include <iostream> 

// ---------- FUNCTOR ---------- 
struct Functor 
{ 
    template<typename T> 
    void operator()(T& t) const { std::cout << t << std::endl; } 
}; 

int main() 
{ 
    for_each(std::make_tuple(2, 0.6, 'c'), Functor()); 
    return 0; 
} 

el poder de las plantillas variadic!

+0

Intenté su primera solución, pero falla con esta función en pares. ¿Alguna idea de por qué? Template void addt (par p) { cout << p.first + p.second << endl; } int main (int argc, char * argv []) { cout << "Hola" << endl; for_each (make_tuple (2,3,4), [] (int i) {cout << i << endl;}); for_each (make_tuple (make_pair (1,2), make_pair (3,4)), addt); return 0; } – user2023370

90

que tienen una respuesta basada en Iterating over a Tuple:

#include <tuple> 
#include <utility> 
#include <iostream> 

template<std::size_t I = 0, typename... Tp> 
inline typename std::enable_if<I == sizeof...(Tp), void>::type 
    print(std::tuple<Tp...>& t) 
    { } 

template<std::size_t I = 0, typename... Tp> 
inline typename std::enable_if<I < sizeof...(Tp), void>::type 
    print(std::tuple<Tp...>& t) 
    { 
    std::cout << std::get<I>(t) << std::endl; 
    print<I + 1, Tp...>(t); 
    } 

int 
main() 
{ 
    typedef std::tuple<int, float, double> T; 
    T t = std::make_tuple(2, 3.14159F, 2345.678); 

    print(t); 
} 

La idea habitual es utilizar la recursividad compilar tiempo. De hecho, esta idea se usa para hacer un printf que sea seguro como se indica en los documentos originales de la tupla.

Esto se puede generalizar fácilmente en un for_each de tuplas:

#include <tuple> 
#include <utility> 

template<std::size_t I = 0, typename FuncT, typename... Tp> 
inline typename std::enable_if<I == sizeof...(Tp), void>::type 
    for_each(std::tuple<Tp...> &, FuncT) // Unused arguments are given no names. 
    { } 

template<std::size_t I = 0, typename FuncT, typename... Tp> 
inline typename std::enable_if<I < sizeof...(Tp), void>::type 
    for_each(std::tuple<Tp...>& t, FuncT f) 
    { 
    f(std::get<I>(t)); 
    for_each<I + 1, FuncT, Tp...>(t, f); 
    } 

A pesar de esto, entonces requiere un poco de esfuerzo para tener FuncT representan algo con las sobrecargas apropiadas para cada tipo de la tupla puede contener. Esto funciona mejor si sabes que todos los elementos de tupla compartirán una clase base común o algo similar.

+4

Gracias por el buen ejemplo simple. Para los principiantes de C++ que buscan antecedentes sobre cómo funciona esto, consulte [SFINAE] (http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) y ['enable_if' documentation] (http://en.cppreference.com/w/ cpp/types/enable_if). –

+0

Esto podría ser fácilmente generalizado para ser un 'for_each' genérico. De hecho, lo hice yo mismo. :-) Creo que esta respuesta sería más útil si ya estuviera generalizada. – Omnifarious

+4

Allí, agregué la generalización porque realmente la necesitaba, y creo que sería útil que otros la vieran. – Omnifarious

19

Uso Boost.Hana y lambdas genéricos:

#include <tuple> 
#include <iostream> 
#include <boost/hana.hpp> 
#include <boost/hana/ext/std/tuple.hpp> 

struct Foo1 { 
    int foo() const { return 42; } 
}; 

struct Foo2 { 
    int bar = 0; 
    int foo() { bar = 24; return bar; } 
}; 

int main() { 
    using namespace std; 
    using boost::hana::for_each; 

    Foo1 foo1; 
    Foo2 foo2; 

    for_each(tie(foo1, foo2), [](auto &foo) { 
     cout << foo.foo() << endl; 
    }); 

    cout << "foo2.bar after mutation: " << foo2.bar << endl; 
} 

http://coliru.stacked-crooked.com/a/27b3691f55caf271

+1

+1 para usar C++ 14 lambdas polimórficas – nurettin

+1

Por favor, no vaya 'using namespace boost :: fusion' (especialmente junto con' using namespace std'). Ahora no hay forma de saber si ese 'for_each' es' std :: for_each' o 'boost :: fusion :: for_each' – Bulletmagnet

+2

@Bulletmagnet, esto se hizo por rigurosidad aquí y ADL puede manejar eso sin ningún problema. Además, también es función local. –

6

En primer lugar definir algunos ayudantes índice:

template <size_t ...I> 
struct index_sequence {}; 

template <size_t N, size_t ...I> 
struct make_index_sequence : public make_index_sequence<N - 1, N - 1, I...> {}; 

template <size_t ...I> 
struct make_index_sequence<0, I...> : public index_sequence<I...> {}; 

Con la función que le gustaría aplicar en cada elemento de la tupla:

template <typename T> 
/* ... */ foo(T t) { /* ... */ } 

puede escribir:

template<typename ...T, size_t ...I> 
/* ... */ do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) { 
    std::tie(foo(std::get<I>(ts)) ...); 
} 

template <typename ...T> 
/* ... */ do_foo(std::tuple<T...> &ts) { 
    return do_foo_helper(ts, make_index_sequence<sizeof...(T)>()); 
} 

o si vuelve foovoid, utilizan

std::tie((foo(std::get<I>(ts)), 1) ...); 

Nota: En C++ 14 make_index_sequence ya está definido (http://en.cppreference.com/w/cpp/utility/integer_sequence).

Si usted no necesita una orden de evaluación de izquierda a derecha, consideran algo como esto:

template <typename T, typename ...R> 
void do_foo_iter(T t, R ...r) { 
    foo(t); 
    do_foo(r...); 
} 

void do_foo_iter() {} 

template<typename ...T, size_t ...I> 
void do_foo_helper(std::tuple<T...> &ts, index_sequence<I...>) { 
    do_foo_iter(std::get<I>(ts) ...); 
} 

template <typename ...T> 
void do_foo(std::tuple<T...> &ts) { 
    do_foo_helper(ts, make_index_sequence<sizeof...(T)>()); 
} 
+0

Debería arrojar el valor de retorno de 'foo' a' void' antes de invocar 'operator,' para evitar una posible sobrecarga del operador patológico. – Yakk

1

que podría haber perdido este tren, pero esto va a estar aquí para referencia futura.
Aquí es mi constructo basado en este answer y en esta gist:

#include <tuple> 
#include <utility> 

template<std::size_t N> 
struct tuple_functor 
{ 
    template<typename T, typename F> 
    static void run(std::size_t i, T&& t, F&& f) 
    { 
     const std::size_t I = (N - 1); 
     switch(i) 
     { 
     case I: 
      std::forward<F>(f)(std::get<I>(std::forward<T>(t))); 
      break; 

     default: 
      tuple_functor<I>::run(i, std::forward<T>(t), std::forward<F>(f)); 
     } 
    } 
}; 

template<> 
struct tuple_functor<0> 
{ 
    template<typename T, typename F> 
    static void run(std::size_t, T, F){} 
}; 

A continuación, lo utilizan de la siguiente manera:

template<typename... T> 
void logger(std::string format, T... args) //behaves like C#'s String.Format() 
{ 
    auto tp = std::forward_as_tuple(args...); 
    auto fc = [](const auto& t){std::cout << t;}; 

    /* ... */ 

    std::size_t some_index = ... 
    tuple_functor<sizeof...(T)>::run(some_index, tp, fc); 

    /* ... */ 
} 

No puede haber espacio para mejoras.


De acuerdo con el código de OP, se convertiría en esto:

const std::size_t num = sizeof...(T); 
auto my_tuple = std::forward_as_tuple(t...); 
auto do_sth = [](const auto& elem){/* ... */}; 
for(int i = 0; i < num; ++i) 
    tuple_functor<num>::run(i, my_tuple, do_sth); 
12

En C++ 17 se puede hacer esto:

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

Esto ya funciona en Clang ++ 3.9, utilizando std :: experimental :: aplicar

+0

¿Esto no conduce a la iteración, es decir, llamadas de 'do_something()', que ocurren 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 el pedido tuviera lugar en el mismo orden que los miembros, es decir, como los índices de 'std :: get <>()'. AFAIK, para obtener pedidos garantizados en casos como este, la expansión debe hacerse dentro de '{llaves}'. ¿Me equivoco? Esta respuesta pone énfasis en tal ordenamiento: http://stackoverflow.com/a/16387374/2757035 –

1

Así es un fácil C++ 17 manera de iterar sobre artículos tupla con biblioteca solo estándar:

#include <tuple>  // std::tuple 
#include <functional> // std::invoke 

template < 
    size_t Index = 0, // start iteration at 0 index 
    typename TTuple, // the tuple type 
    size_t Size = 
     std::tuple_size_v< 
      std::remove_reference_t<TTuple>>, // tuple size 
    typename TCallable, // the callable to bo invoked for each tuple item 
    typename... TArgs // other arguments to be passed to the callable 
> 
void for_each(TTuple&& tuple, TCallable&& callable, TArgs&&... args) 
{ 
    if constexpr (Index < Size) 
    { 
     std::invoke(callable, args..., std::get<Index>(tuple)); 

     if constexpr (Index + 1 < Size) 
      for_each<Index + 1>(
       std::forward<TTuple>(tuple), 
       std::forward<TCallable>(callable), 
       std::forward<TArgs>(args)...); 
    } 
} 

Ejemplo:

#include <iostream> 

std::tuple<int, char> items; 
for_each(items, [](const auto& item) { 
    std::cout << item << "\n"; 
}); 
0

En MSVC STL hay una función _For_each_tuple_element (no documentado):

#include <tuple> 

// ... 

std::tuple<int, char, float> values{}; 
std::_For_each_tuple_element(values, [](auto&& value) 
{ 
    // process 'value' 
});