¿Cómo puedes iterar sobre los elementos de un std::tuple?

8 minutos de lectura

¿Cómo puedo iterar sobre una tupla (usando C++ 11)? Intenté lo siguiente:

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

pero esto no funciona:

Error 1: lo siento, no implementado: no se puede expandir ‘Oyente…’ en una lista de argumentos de longitud fija.
Error 2: no puedo aparecer en una expresión constante.

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

  • ¿Puedo preguntar, cómo se compila en C++ 0x? No está lanzado ni listo hasta donde yo sé.

    – Burkhard

    29 de julio de 2009 a las 6:09

  • g++ contiene compatibilidad experimental con algunas funciones de C++0X, incluidas plantillas variadas, desde la versión 4.3. Otros compiladores hacen lo mismo (con diferentes conjuntos de características, si desea usarlos en producción, está de regreso en los 90 con una amplia variación de soporte para cosas de última generación)

    – Un programador

    29 de julio de 2009 a las 7:10

  • Estoy usando g ++ versión 4.4 con std = c ++ 0x

    1521237

    30 de julio de 2009 a las 10:33

  • Esta pregunta necesita una actualización de C++ 11.

    – Omnifaro

    3 de febrero de 2013 a las 4:04

  • @Omnifarious ahora, necesita una actualización de C++14

    – pepper_chico

    19 de abril de 2014 a las 15:38

¿Como puedes iterar sobre los elementos de un stdtuple
emsr

Tengo una respuesta basada en iterar sobre una tupla:

#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>
    print<I + 1, Tp...>
  }

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

  print
}

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

Esto se puede generalizar fácilmente en un for_each para 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>
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

Aunque esto requiere un poco de esfuerzo para tener FuncT representar algo con las sobrecargas apropiadas para cada tipo que pueda contener la tupla. Esto funciona mejor si sabe que todos los elementos de la tupla compartirán una clase base común o algo similar.

  • Gracias por el buen ejemplo simple. Para los principiantes de C++ que buscan antecedentes sobre cómo funciona esto, consulte SFINAE y enable_if documentación.

    – Fahim Mitha

    12 de febrero de 2012 a las 5:28

  • Esto podría generalizarse fácilmente para ser un genérico for_each. De hecho, lo hice yo mismo. 🙂 Creo que esta respuesta sería más útil si ya estuviera generalizada.

    – Omnifaro

    3 de febrero de 2013 a las 11:27

  • Allí, agregué la generalización porque en realidad necesitaba una, y creo que sería útil para que otros la vieran.

    – Omnifaro

    4 de febrero de 2013 a las 7:06

  • Nota: Es posible que también necesite versiones con const std::tuple<Tp...>&.. Si no tiene la intención de modificar las tuplas durante la iteración, esas const las versiones serán suficientes.

    – guitarra letal

    6 de junio de 2013 a las 19:17

  • No como está escrito. Podría hacer una versión con la indexación invertida: comience en I = tamaño de … (Tp) y cuente hacia atrás. A continuación, proporcione un número máximo de argumentos de forma explícita. También podría hacer una versión que se rompiera en un tipo de etiqueta, digamos break_t. Luego, colocaría un objeto de ese tipo de etiqueta en su tupla cuando quisiera detener la impresión. O podría proporcionar un tipo de parada como parámetro de plantilla. Obviamente no se podía romper en tiempo de ejecución.

    – emsr

    12 de diciembre de 2014 a las 19:29

En C ++ 17, puede usar std::apply con expresión de pliegue:

std::apply([](auto&&... args) {((/* args.dosomething() */), ...);}, the_tuple);

Un ejemplo completo para imprimir una tupla:

#include <tuple>
#include <iostream>

int main()
{
    std::tuple t{42, 'a', 4.2}; // Another C++17 feature: class template argument deduction
    std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}

[Online Example on Coliru]

Esta solución resuelve el problema del orden de evaluación en la respuesta de M. Alaggan.

  • ¿Podría explicar lo que está sucediendo aquí: ((std::cout << args << '\n'), ...); ? La lambda se invoca una vez con los elementos de tupla desempaquetados como argspero ¿qué pasa con los paréntesis dobles?

    – helmesjo

    19 de enero de 2019 a las 22:17

  • @helmesjo Se expande a una expresión de coma ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n')) aquí.

    – xskxzr

    20 de enero de 2019 a las 3:37


  • Tenga en cuenta que en caso de que quiera hacer cosas que no son legales en una expresión de coma (como declarar variables y bloques), puede poner todo eso en un método y simplemente llamarlo desde dentro de la expresión de coma doblada.

    – Miral

    27 de febrero de 2020 a las 7:00

1647552193 511 ¿Como puedes iterar sobre los elementos de un stdtuple
daniel stek

C++ está introduciendo declaraciones de expansión para este propósito. Originalmente iban por buen camino para C++20, pero no pasaron el corte por poco debido a la falta de tiempo para revisar la redacción del idioma (ver aquí y aquí).

La sintaxis acordada actualmente (consulte los enlaces anteriores) es:

{
    auto tup = std::make_tuple(0, 'a', 3.14);
    template for (auto elem : tup)
        std::cout << elem << std::endl;
}

¿Como puedes iterar sobre los elementos de un stdtuple
Éric Malenfant

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

En C++ 17 puedes hacer esto:

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

Esto ya funciona en Clang++ 3.9, usando std::experimental::apply.

  • ¿No conduce esto a la iteración, es decir, llamadas de do_something() – Ocurriendo 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 se produzca en el mismo orden que los miembros, es decir, como los índices para 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

    9 oct 2016 a las 14:37


Use Boost.Hana y lambdas genéricas:

#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

  • ¿No conduce esto a la iteración, es decir, llamadas de do_something() – Ocurriendo 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 se produzca en el mismo orden que los miembros, es decir, como los índices para 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

    9 oct 2016 a las 14:37


Una forma más simple, intuitiva y fácil de compilar de hacer esto en C++17, usando if constexpr:

// prints every element of a tuple
template<size_t I = 0, typename... Tp>
void print(std::tuple<Tp...>& t) {
    std::cout << std::get<I>
    // do things
    if constexpr(I+1 != sizeof...(Tp))
        print<I+1>
}

Esta es una recursividad en tiempo de compilación, similar a la presentada por @emsr. Pero esto no usa SFINAE, así que (creo) es más fácil de compilar.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad