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

112

¿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 iterar correctamente sobre los elementos de una tupla?

einpoklum
fuente
2
¿Puedo preguntar cómo se compila en C ++ 0x? No está lanzado ni listo hasta donde yo sé.
Burkhard
5
g ++ contiene soporte experimental de algunas características 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 vuelta en los 90 con una amplia variación de soporte para cosas de vanguardia)
AProgrammer
Estoy usando g ++ versión 4.4 con std = c ++ 0x
9
Esta pregunta necesita una actualización de C ++ 11.
Omnifarious
2
@Omnifarious ahora, necesita una actualización de C ++ 14
pepper_chico

Respuestas:

26

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());
Éric Malenfant
fuente
@ViktorSehr AFAICT no lo hace (al menos en GCC 4.7.2)? ¿Alguien con una pista?
sehe
@ViktorSehr Encontró el problema: un error / omisión hace que el comportamiento de la fusión dependa del orden de las inclusiones , consulte Boost ticket # 8418 para más detalles
vea el
necesita usar boost :: fusion :: tuple en lugar de std :: tuple para que esto funcione.
Marcin
En GCC 8.1 / mingw-64, recibo dos advertencias para el uso de boost :: fusion :: for_each con expresiones lambda estándar: boost / mpl / assert.hpp: 188: 21: advertencia: paréntesis innecesarios en la declaración de 'assert_arg' [-Wparentheses] falló ************ (Pred :: ************ boost / mpl / assert.hpp: 193: 21: advertencia: paréntesis innecesarios en declaración de 'assert_not_arg' [-Wparentheses] falló ************ (boost :: mpl :: not_ <Pred> :: ************
Hossein
129

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>(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 en tiempo de compilación. De hecho, esta idea se utiliza para hacer un printf que sea seguro para tipos como se indica en los papeles de tupla originales.

Esto se puede generalizar fácilmente en una for_eachpara 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);
  }

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

emsr
fuente
5
Gracias por el bonito y sencillo ejemplo. Para los principiantes de C ++ que buscan antecedentes sobre cómo funciona esto, consulte SFINAE y la enable_ifdocumentación .
Faheem Mitha
Esto podría generalizarse fácilmente para que sea genérico for_each. 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 en realidad necesitaba una y creo que sería útil que otros la vieran.
Omnifarious
2
Nota: Es posible que también necesite versiones con const std::tuple<Tp...>&.. Si no tiene la intención de modificar tuplas mientras itera, esas constversiones serán suficientes.
Lethal-guitar
2
No como está escrito. Podría hacer una versión con la indexación invertida - comience en I = sizeof ... (Tp) y cuente hacia atrás. Luego proporcione un número máximo de argumentos explícitamente. También puede hacer una versión que se rompa en un tipo de etiqueta, digamos break_t. Luego, pondría un objeto de ese tipo de etiqueta en su tupla cuando quisiera dejar de imprimir. O puede proporcionar un tipo de parada como parámetro de plantilla. Obviamente, no se podía romper en tiempo de ejecución.
emsr
55

En C ++ 17, puede usar std::applycon expresión de plegado :

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

[Ejemplo en línea sobre Coliru]

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

xskxzr
fuente
1
¿Podría explicar qué está pasando aquí ((std::cout << args << '\n'), ...);:? La lambda se invoca una vez con los elementos de tupla desempaquetados como args, pero ¿qué pasa con los paréntesis dobles?
helmesjo
4
@helmesjo Aquí se expande a una expresión de coma ((std::cout << arg1 << '\n'), (std::cout << arg2 << '\n'), (std::cout << arg3 << '\n')).
xskxzr
Tenga en cuenta que en caso de que desee 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 plegada.
Miral
24

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.

Mohammad Alaggan
fuente
4
¿No conduce esto a que la iteración, es decir, las llamadas de do_something(), se produzcan 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 la ordenación ocurrirá 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 hacerse dentro {braces}. ¿Me equivoco? Esta respuesta pone énfasis en dichos pedidos: stackoverflow.com/a/16387374/2757035
underscore_d
21

Utilice 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

pepper_chico
fuente
4
Por favor, no vayas using namespace boost::fusion(especialmente junto con using namespace std). Ahora no hay forma de saber si eso for_eaches std::for_eachoboost::fusion::for_each
Bulletmagnet
3
@Bulletmagnet esto se hizo por concisión aquí y ADL puede manejar eso sin ningún problema. Además, también es una función local.
pepper_chico
16

C ++ está introduciendo declaraciones de expansión para este propósito. Originalmente iban por buen camino para C ++ 20, pero fallaron por poco debido a la falta de tiempo para revisar la redacción del lenguaje (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;
}
DanielS
fuente
15

Una forma más simple, intuitiva y amigable para el compilador 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>(t) << " ";
    // do things
    if constexpr(I+1 != sizeof...(Tp))
        print<I+1>(t);
}

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

Stypox
fuente
8

Necesita usar la metaprogramación de plantilla, aquí se muestra 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 variada.

Marc Mutz - mmutz
fuente
8

Primero defina algunos ayudantes de í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 su función le gustaría aplicar en cada elemento de tupla:

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

puedes 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 fooregresa void, use

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

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

Si necesita un orden de evaluación de izquierda a derecha, considere 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)>());
}
Philipp H.
fuente
1
Debe emitir el valor de retorno de footo voidantes de invocar operator,para evitar una posible sobrecarga del operador patológico.
Yakk - Adam Nevraumont
7

Aquí hay una forma fácil de C ++ 17 de iterar sobre elementos de tupla con solo una biblioteca 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>

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

Salida:

1
a

Esto se puede extender para romper condicionalmente el bucle en caso de que el invocable devuelva un valor (pero aún funciona con invocables que no devuelven un valor bool asignable, por ejemplo, void):

#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)
    {
        if constexpr (std::is_assignable_v<bool&, std::invoke_result_t<TCallable&&, TArgs&&..., decltype(std::get<Index>(tuple))>>)
        {
            if (!std::invoke(callable, args..., std::get<Index>(tuple)))
                return;
        }
        else
        {
            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>

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

    std::cout << "---\n";

    for_each(items, [](const auto& item) {
        std::cout << item << "\n";
        return false;
    });
}

Salida:

1
a
---
1
Marcin Zawiejski
fuente
5

Si desea utilizar std :: tuple y tiene un compilador de C ++ que admite plantillas variadic, pruebe el siguiente código (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) );
}

boost :: fusion es otra opción, pero requiere su propio tipo de tupla: boost :: fusion :: tuple. ¡Es mejor que nos ciñamos 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 variadas!

sigidagi
fuente
Probé su primera solución, pero falla con esta función en pares. ¿Alguna idea de por qué? Template <typename T, typename U> void addt (par <T, U> 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
Es una pena que esta respuesta esté escrita de manera tan detallada porque creo que la forma de iterar (for_each_impl) es la más elegante de todas las soluciones que he visto.
joki
3

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

#include <tuple>

// ...

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

Otros han mencionado algunas bibliotecas de terceros bien diseñadas a las que puede recurrir. Sin embargo, si está utilizando C ++ sin esas bibliotecas de terceros, el siguiente código puede ayudar.

namespace detail {

template <class Tuple, std::size_t I, class = void>
struct for_each_in_tuple_helper {
  template <class UnaryFunction>
  static void apply(Tuple&& tp, UnaryFunction& f) {
    f(std::get<I>(std::forward<Tuple>(tp)));
    for_each_in_tuple_helper<Tuple, I + 1u>::apply(std::forward<Tuple>(tp), f);
  }
};

template <class Tuple, std::size_t I>
struct for_each_in_tuple_helper<Tuple, I, typename std::enable_if<
    I == std::tuple_size<typename std::decay<Tuple>::type>::value>::type> {
  template <class UnaryFunction>
  static void apply(Tuple&&, UnaryFunction&) {}
};

}  // namespace detail

template <class Tuple, class UnaryFunction>
UnaryFunction for_each_in_tuple(Tuple&& tp, UnaryFunction f) {
  detail::for_each_in_tuple_helper<Tuple, 0u>
      ::apply(std::forward<Tuple>(tp), f);
  return std::move(f);
}

Nota: el código se compila con cualquier compilador compatible con C ++ 11 y mantiene la coherencia con el diseño de la biblioteca estándar:

  1. La tupla no tiene por qué serlo std::tupley, en cambio, puede ser cualquier cosa que admita std::gety std::tuple_size; en particular, std::arrayy std::pairpuede usarse;

  2. La tupla puede ser un tipo de referencia o calificada por cv;

  3. Tiene un comportamiento similar std::for_eachy devuelve la entrada UnaryFunction;

  4. Para usuarios de C ++ 14 (o versión más reciente), typename std::enable_if<T>::typey typename std::decay<T>::typepodría reemplazarse con su versión simplificada, std::enable_if_t<T>y std::decay_t<T>;

  5. Para los usuarios de C ++ 17 (laster versión o), std::tuple_size<T>::valuepodría ser reemplazado con su versión simplificada, std::tuple_size_v<T>.

  6. Para los usuarios de C ++ 20 (o la versión más reciente), la SFINAEfunción podría implementarse con Concepts.

Grafeno
fuente
2

Usar constexpry if constexpr(C ++ 17) esto es bastante simple y directo:

template <std::size_t I = 0, typename ... Ts>
void print(std::tuple<Ts...> tup) {
  if constexpr (I == sizeof...(Ts)) {
    return;
  } else {
    std::cout << std::get<I>(tup) << ' ';
    print<I+1>(tup);
  }
}
Andreas DM
fuente
1

Puede que haya perdido este tren, pero estará aquí para referencia futura.
Aquí está mi construcción basada en esta respuesta y en esta esencia :

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

Luego lo usa 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);

    /* ... */
}

Podría haber margen de mejora.


Según 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);
bit2shift
fuente
1

De todas las respuestas que he visto aquí, aquí y aquí , me gustó más la forma de iterar de @sigidagi. Desafortunadamente, su respuesta es muy detallada, lo que en mi opinión oscurece la claridad inherente.

Esta es mi versión de su solución, que es más concisa y trabaja con std::tuple, std::pairy std::array.

template<typename UnaryFunction>
void invoke_with_arg(UnaryFunction)
{}

/**
 * Invoke the unary function with each of the arguments in turn.
 */
template<typename UnaryFunction, typename Arg0, typename... Args>
void invoke_with_arg(UnaryFunction f, Arg0&& a0, Args&&... as)
{
    f(std::forward<Arg0>(a0));
    invoke_with_arg(std::move(f), std::forward<Args>(as)...);
}

template<typename Tuple, typename UnaryFunction, std::size_t... Indices>
void for_each_helper(Tuple&& t, UnaryFunction f, std::index_sequence<Indices...>)
{
    using std::get;
    invoke_with_arg(std::move(f), get<Indices>(std::forward<Tuple>(t))...);
}

/**
 * Invoke the unary function for each of the elements of the tuple.
 */
template<typename Tuple, typename UnaryFunction>
void for_each(Tuple&& t, UnaryFunction f)
{
    using size = std::tuple_size<typename std::remove_reference<Tuple>::type>;
    for_each_helper(
        std::forward<Tuple>(t),
        std::move(f),
        std::make_index_sequence<size::value>()
    );
}

Demostración: coliru

C ++ 14 std::make_index_sequencese puede implementar para C ++ 11 .

joki
fuente
0

tupla de impulso proporciona funciones de ayuda get_head()y get_tail()por lo que sus funciones de ayuda pueden 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 aquí http://www.boost.org/doc/libs/1_34_0/libs/tuple/doc/tuple_advanced_interface.html

con std::tupleél debería ser similar.

En realidad, desafortunadamente std::tupleno parece proporcionar dicha interfaz, por lo que los métodos sugeridos antes deberían funcionar, o necesitaría cambiar a los boost::tupleque tienen otros beneficios (como los operadores io ya proporcionados). Aunque hay una desventaja de boost::tuplegcc: todavía no acepta plantillas variadas, pero es posible que ya esté arreglado porque no tengo la última versión de boost instalada en mi máquina.

Slava
fuente
0

Me he encontrado con el mismo problema para iterar sobre una tupla de objetos de función, así que aquí hay una solución más:

#include <tuple> 
#include <iostream>

// Function objects
class A 
{
    public: 
        inline void operator()() const { std::cout << "A\n"; };
};

class B 
{
    public: 
        inline void operator()() const { std::cout << "B\n"; };
};

class C 
{
    public:
        inline void operator()() const { std::cout << "C\n"; };
};

class D 
{
    public:
        inline void operator()() const { std::cout << "D\n"; };
};


// Call iterator using recursion.
template<typename Fobjects, int N = 0> 
struct call_functors 
{
    static void apply(Fobjects const& funcs)
    {
        std::get<N>(funcs)(); 

        // Choose either the stopper or descend further,  
        // depending if N + 1 < size of the tuple. 
        using caller = std::conditional_t
        <
            N + 1 < std::tuple_size_v<Fobjects>,
            call_functors<Fobjects, N + 1>, 
            call_functors<Fobjects, -1>
        >;

        caller::apply(funcs); 
    }
};

// Stopper.
template<typename Fobjects> 
struct call_functors<Fobjects, -1>
{
    static void apply(Fobjects const& funcs)
    {
    }
};

// Call dispatch function.
template<typename Fobjects>
void call(Fobjects const& funcs)
{
    call_functors<Fobjects>::apply(funcs);
};


using namespace std; 

int main()
{
    using Tuple = tuple<A,B,C,D>; 

    Tuple functors = {A{}, B{}, C{}, D{}}; 

    call(functors); 

    return 0; 
}

Salida:

A 
B 
C 
D
tmaric
fuente
0

Otra opción sería implementar iteradores para tuplas. Esto tiene la ventaja de que puede utilizar una variedad de algoritmos proporcionados por la biblioteca estándar y bucles for basados ​​en rangos. Un enfoque elegante para esto se explica aquí https://foonathan.net/2017/03/tuple-iterator/ . La idea básica es convertir tuplas en una gama con begin()y end()métodos para proporcionar iteradores. El propio iterador devuelve un std::variant<...>que luego se puede visitar usando std::visit.

Aquí algunos ejemplos:

auto t = std::tuple{ 1, 2.f, 3.0 };
auto r = to_range(t);

for(auto v : r)
{
    std::visit(unwrap([](auto& x)
        {
            x = 1;
        }), v);
}

std::for_each(begin(r), end(r), [](auto v)
    {
        std::visit(unwrap([](auto& x)
            {
                x = 0;
            }), v);
    });

std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
    {
        return acc + std::visit(unwrap([](auto& x)
        {
            return static_cast<double>(x);
        }), v);
    });

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(unwrap([](const auto& x)
        {
            std::cout << x << std::endl;
        }), v);
});

std::for_each(begin(r), end(r), [](auto v)
{
    std::visit(overload(
        [](int x) { std::cout << "int" << std::endl; },
        [](float x) { std::cout << "float" << std::endl; },
        [](double x) { std::cout << "double" << std::endl; }), v);
});

Mi implementación (que se basa en gran medida en las explicaciones en el enlace anterior):

#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H

#include <utility>
#include <functional>
#include <variant>
#include <type_traits>

template<typename Accessor>
class tuple_iterator
{
public:
    tuple_iterator(Accessor acc, const int idx)
        : acc_(acc), index_(idx)
    {

    }

    tuple_iterator operator++()
    {
        ++index_;
        return *this;
    }

    template<typename T>
    bool operator ==(tuple_iterator<T> other)
    {
        return index_ == other.index();
    }

    template<typename T>
    bool operator !=(tuple_iterator<T> other)
    {
        return index_ != other.index();
    }

    auto operator*() { return std::invoke(acc_, index_); }

    [[nodiscard]] int index() const { return index_; }

private:
    const Accessor acc_;
    int index_;
};

template<bool IsConst, typename...Ts>
struct tuple_access
{
    using tuple_type = std::tuple<Ts...>;
    using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;

    template<typename T>
    using element_ref = std::conditional_t<IsConst,
        std::reference_wrapper<const T>,
        std::reference_wrapper<T>>;

    using variant_type = std::variant<element_ref<Ts>...>;
    using function_type = variant_type(*)(tuple_ref);
    using table_type = std::array<function_type, sizeof...(Ts)>;

private:
    template<size_t Index>
    static constexpr function_type create_accessor()
    {
        return { [](tuple_ref t) -> variant_type
        {
            if constexpr (IsConst)
                return std::cref(std::get<Index>(t));
            else
                return std::ref(std::get<Index>(t));
        } };
    }

    template<size_t...Is>
    static constexpr table_type create_table(std::index_sequence<Is...>)
    {
        return { create_accessor<Is>()... };
    }

public:
    static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>{}); 
};

template<bool IsConst, typename...Ts>
class tuple_range
{
public:
    using tuple_access_type = tuple_access<IsConst, Ts...>;
    using tuple_ref = typename tuple_access_type::tuple_ref;

    static constexpr auto tuple_size = sizeof...(Ts);

    explicit tuple_range(tuple_ref tuple)
        : tuple_(tuple)
    {
    }

    [[nodiscard]] auto begin() const 
    { 
        return tuple_iterator{ create_accessor(), 0 };
    }

    [[nodiscard]] auto end() const 
    { 
        return tuple_iterator{ create_accessor(), tuple_size };
    }

private:
    tuple_ref tuple_;

    auto create_accessor() const
    { 
        return [this](int idx)
        {
            return std::invoke(tuple_access_type::table[idx], tuple_);
        };
    }
};

template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)
{
    return r.begin();
}

template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)
{
    return r.end();
}

template <class ... Fs>
struct overload : Fs... {
    explicit overload(Fs&&... fs) : Fs{ fs }... {}
    using Fs::operator()...;

    template<class T>
    auto operator()(std::reference_wrapper<T> ref)
    {
        return (*this)(ref.get());
    }

    template<class T>
    auto operator()(std::reference_wrapper<const T> ref)
    {
        return (*this)(ref.get());
    }
};

template <class F>
struct unwrap : overload<F>
{
    explicit unwrap(F&& f) : overload<F>{ std::forward<F>(f) } {}
    using overload<F>::operator();
};

template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)
{
    return tuple_range<false, Ts...>{t};
}

template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)
{
    return tuple_range<true, Ts...>{t};
}


#endif

También se admite el acceso de solo lectura pasando un const std::tuple<>&a to_range().

Florian Tischler
fuente
0

Ampliando la respuesta de @Stypox, podemos hacer que su solución sea más genérica (C ++ 17 en adelante). Añadiendo un argumento de función invocable:

template<size_t I = 0, typename... Tp, typename F>
void for_each_apply(std::tuple<Tp...>& t, F &&f) {
    f(std::get<I>(t));
    if constexpr(I+1 != sizeof...(Tp)) {
        for_each_apply<I+1>(t, std::forward<F>(f));
    }
}

Entonces, necesitamos una estrategia para visitar cada tipo.

Comencemos con algunos ayudantes (los dos primeros tomados de cppreference):

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class ... Ts> struct variant_ref { using type = std::variant<std::reference_wrapper<Ts>...>; };

variant_ref se utiliza para permitir que se modifique el estado de las tuplas.

Uso:

std::tuple<Foo, Bar, Foo> tuples;

for_each_apply(tuples,
               [](variant_ref<Foo, Bar>::type &&v) {
                   std::visit(overloaded {
                       [](Foo &arg) { arg.foo(); },
                       [](Bar const &arg) { arg.bar(); },
                   }, v);
               });

Resultado:

Foo0
Bar
Foo0
Foo1
Bar
Foo1

Para completar, aquí están mis Bar& Foo:

struct Foo {
    void foo() {std::cout << "Foo" << i++ << std::endl;}
    int i = 0;
};
struct Bar {
    void bar() const {std::cout << "Bar" << std::endl;}
};
Thomas Legris
fuente