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 ?

140voto

emsr Points 4616

J'ai une réponse basée sur Itération sur un 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);
}

L'idée habituelle est d'utiliser la récursion en temps de compilation. En fait, cette idée est utilisée pour faire un printf qui est sans danger pour les types, comme indiqué dans les documents originaux sur les tuple.

Cela peut être facilement généralisé en un for_each pour les tuples :

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

Bien que cela demande ensuite quelques efforts pour avoir FuncT représente quelque chose avec les surcharges appropriées pour chaque type que le tuple peut contenir. Cela fonctionne mieux si vous savez que tous les éléments du tuple partageront une classe de base commune ou quelque chose de similaire.

111voto

xskxzr Points 5160

En C++17, vous pouvez utiliser std::apply avec expression des plis :

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

Un exemple complet d'impression d'un tuple :

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

[Exemple en ligne sur Coliru]

Cette solution permet de résoudre la question de l'ordre d'évaluation dans M. La réponse de Alaggan .

30voto

Daniel Steck Points 193

Le C++ introduit déclarations d'expansion à cette fin. Ils étaient à l'origine sur la bonne voie pour C++20, mais ont manqué de peu l'échéance en raison d'un manque de temps pour la révision du libellé du langage (cf. aquí y aquí ).

La syntaxe actuellement acceptée (voir les liens ci-dessus) est la suivante :

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

28voto

Éric Malenfant Points 10082

Boost.Fusion est une possibilité :

Exemple non testé :

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

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

26voto

M. Alaggan Points 750

En C++17, vous pouvez le faire :

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

Cela fonctionne déjà dans Clang++ 3.9, en utilisant std::experimental::apply.

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