138 votes

Comment itérer sur les éléments d'un std::tuple ?

Comment itérer sur un tuple (en utilisant C++11) ? J'ai essayé ce qui suit :

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

mais cela ne fonctionne pas :

Erreur 1 : sorry, unimplemented : cannot expand 'Listener ...' into a fixed-length argument list.
Erreur 2 : i ne peut pas apparaître dans une expression constante.

Alors, comment faire pour itérer correctement sur les éléments d'un tuple ?

23voto

pepper_chico Points 2428

Utilisez Boost.Hana et les lambdas génériques :

#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

21voto

Stypox Points 489

Une façon plus simple, intuitive et conviviale de faire cela en C++17, en utilisant 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);
}

Il s'agit d'une récursion au moment de la compilation, similaire à celle présentée par @emsr. Mais celle-ci n'utilise pas SFINAE, donc (je pense) qu'elle est plus facile à compiler.

10voto

Marcin Zawiejski Points 806

Voici une méthode C++17 simple pour itérer sur les éléments d'un tuple avec la bibliothèque standard :

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

Exemple :

#include <iostream>

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

Sortie :

1
a

Ceci peut être étendu pour interrompre conditionnellement la boucle au cas où l'appelant renvoie une valeur (mais fonctionne toujours avec les appelants qui ne renvoient pas une valeur assignable bool, par exemple 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)...);
    }
}

Exemple :

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

Sortie :

1
a
---
1

8voto

Marc Mutz - mmutz Points 10367

Vous devez utiliser la métaprogrammation par modèle, ici présentée avec 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, vous pouvez écrire print_tuple() comme une fonction de modèle variadique à la place.

8voto

user1447257 Points 185

Définissez d'abord quelques aides d'index :

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

Avec votre fonction que vous souhaitez appliquer sur chaque élément du tuple :

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

vous pouvez écrire :

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

Ou si foo renvoie à void utiliser

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

Note : En C++14 make_index_sequence est déjà défini ( http://en.cppreference.com/w/cpp/utility/integer_sequence ).

Si vous avez besoin d'un ordre d'évaluation de gauche à droite, envisagez quelque chose comme ceci :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X