web-dev-qa-db-fra.com

Comment itérer sur un std :: Tuple en C ++ 11

J'ai créé le tuple suivant:

Je veux savoir comment dois-je le répéter? Il y a tupl_size(), mais en lisant la documentation, je n'ai pas compris comment l'utiliser. J'ai aussi la recherche SO, mais les questions semblent être autour de Boost::Tuple.

auto some = make_Tuple("I am good", 255, 2.1);
19
Mostafa Talebi

Voici une tentative de décomposer l'itération d'un Tuple en composants.

Tout d'abord, une fonction qui représente l'exécution d'une séquence d'opérations dans l'ordre. Notez que de nombreux compilateurs trouvent cela difficile à comprendre, bien qu'il soit légal C++ 11 pour autant que je sache:

template<class... Fs>
void do_in_order( Fs&&... fs ) {
  int unused[] = { 0, ( (void)std::forward<Fs>(fs)(), 0 )... }
  (void)unused; // blocks warnings
}

Ensuite, une fonction qui prend un std::Tuple, et extrait les index nécessaires pour accéder à chaque élément. Ce faisant, nous pouvons perfectionner plus tard.

Comme avantage secondaire, mon code prend en charge std::pair et std::array itération:

template<class T>
constexpr std::make_index_sequence<std::Tuple_size<T>::value>
get_indexes( T const& )
{ return {}; }

La viande et les pommes de terre:

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  do_in_order( [&]{ f( get<Is>(std::forward<Tuple>(tup)) ); }... );
}

et l'interface publique:

template<class Tuple, class F>
void for_each( Tuple&& tup, F&& f ) {
  auto indexes = get_indexes(tup);
  for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f) );
}

alors qu'il indique Tuple cela fonctionne sur std::arrayle sable std::pairs. Il transmet également la catégorie de valeur r/l dudit objet à l'objet fonction qu'il invoque. Notez également que si vous avez une fonction gratuite get<N> sur votre type personnalisé, et vous remplacez get_indexes, ce qui précède for_each fonctionnera sur votre type personnalisé.

Comme indiqué, do_in_order tandis que neat n'est pas pris en charge par de nombreux compilateurs, car ils n'aiment pas que lambda avec des packs de paramètres non développés soit développé en packs de paramètres.

Nous pouvons inline do_in_order dans ce cas

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  int unused[] = { 0, ( (void)f(get<Is>(std::forward<Tuple>(tup)), 0 )... }
  (void)unused; // blocks warnings
}

cela ne coûte pas beaucoup de verbosité, mais je le trouve personnellement moins clair. La magie de l'ombre de la façon dont do_in_order les travaux sont obscurcis en le faisant en ligne à mon avis.

index_sequence (et les modèles de prise en charge) est une fonctionnalité C++ 14 qui peut être écrite en C++ 11. Trouver une telle implémentation sur un débordement de pile est facile. Un top hit actuel de Google est ne mise en œuvre décente O(lg(n)) profondeur , qui si je lis correctement les commentaires peut être la base d'au moins une itération du gcc réel make_integer_sequence (les commentaires soulignent également d'autres améliorations au moment de la compilation concernant l'élimination de sizeof... appels).

Alternativement, nous pouvons écrire:

template<class F, class...Args>
void for_each_arg(F&&f,Args&&...args){
  using discard=int[];
  (void)discard{0,((void)(
    f(std::forward<Args>(args))
  ),0)...};
}

Puis:

template<size_t... Is, class Tuple, class F>
void for_each( std::index_sequence<Is...>, Tuple&& tup, F&& f ) {
  using std::get;
  for_each_arg(
    std::forward<F>(f),
    get<Is>(std::forward<Tuple>(tup))...
  );
}

Ce qui évite le développement manuel mais compile sur plus de compilateurs. On passe le Is via le auto&&i paramètre.

En C++ 1z, nous pouvons également utiliser std::apply avec un for_each_arg objet de fonction pour supprimer le violon d'index.

25
template<class F, class...Ts, std::size_t...Is>
void for_each_in_Tuple(const std::Tuple<Ts...> & Tuple, F func, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)func(std::get<Is>(Tuple)), 0)... };
}

template<class F, class...Ts>
void for_each_in_Tuple(const std::Tuple<Ts...> & Tuple, F func){
    for_each_in_Tuple(tuple, func, std::make_index_sequence<sizeof...(Ts)>());
}

Usage:

auto some = std::make_Tuple("I am good", 255, 2.1);
for_each_in_Tuple(some, [](const auto &x) { std::cout << x << std::endl; });

Démo .

std::index_sequence et la famille sont des fonctionnalités C++ 14, mais elles peuvent être facilement implémentées en C++ 11 (il y en a beaucoup disponibles sur SO). Les lambdas polymorphes sont également C++ 14, mais peuvent être remplacés par un foncteur personnalisé.

28
T.C.

Voici une solution similaire et plus verbeuse que celle précédemment acceptée donnée par T.C., qui est peut-être un peu plus facile à comprendre (- c'est probablement la même chose que mille autres sur le net):

template<typename TupleType, typename FunctionType>
void for_each(TupleType&&, FunctionType
            , std::integral_constant<size_t, std::Tuple_size<typename std::remove_reference<TupleType>::type >::value>) {}

template<std::size_t I, typename TupleType, typename FunctionType
       , typename = typename std::enable_if<I!=std::Tuple_size<typename std::remove_reference<TupleType>::type>::value>::type >
void for_each(TupleType&& t, FunctionType f, std::integral_constant<size_t, I>)
{
    f(std::get<I>(std::forward<TupleType>(t)));
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, I + 1>());
}

template<typename TupleType, typename FunctionType>
void for_each(TupleType&& t, FunctionType f)
{
    for_each(std::forward<TupleType>(t), f, std::integral_constant<size_t, 0>());
}

Utilisation (avec std::Tuple):

auto some = std::make_Tuple("I am good", 255, 2.1);
for_each(some, [](const auto &x) { std::cout << x << std::endl; });

Utilisation (avec std::array):

std::array<std::string,2> some2 = {"Also good", "Hello world"};
for_each(some2, [](const auto &x) { std::cout << x << std::endl; });

DÉMO


Idée générale: comme dans la solution de T.C., commencez par un index I=0 Et montez jusqu'à la taille du Tuple. Cependant, ici, cela se fait non pas par expansion variadique, mais une par une.

Explication:

  • La première surcharge de for_each Est appelée si I est égal à la taille du Tuple. La fonction ne fait alors simplement rien et ainsi met fin à la récursivité.

  • La deuxième surcharge appelle la fonction avec l'argument std::get<I>(t) et augmente l'index d'une unité. La classe std::integral_constant Est nécessaire pour résoudre la valeur de I au moment de la compilation. Le truc std::enable_if SFINAE est utilisé pour aider le compilateur à séparer cette surcharge de la précédente, et à appeler cette surcharge uniquement si le I est plus petit que la taille du tuple (sur Coliru cela est nécessaire, alors que dans Visual Studio, cela fonctionne sans).

  • Le troisième démarre la récursivité avec I=0. C'est la surcharge qui est généralement appelée de l'extérieur.




EDIT: J'ai également inclus l'idée mentionnée par Yakk pour supporter en plus std::array Et std::pair En utilisant un général paramètre de modèle TupleType au lieu d'un paramètre spécialisé pour std::Tuple<Ts ...>.

Comme le type TupleType doit être déduit et est une telle "référence universelle", cela a en outre l'avantage que l'on obtient un transfert parfait et gratuit. L'inconvénient est que l'on doit utiliser une autre indirection via typename std::remove_reference<TupleType>::type, Car TupleType peut également être déduit comme type de référence.

11
davidhigh