66 votes

Itération sur différents types

Étant donné le code suivant :

struct Window{
    void show();
    //stuff
}w1, w2, w3;

struct Widget{
    void show();
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show();
    //stuff
}t1, t2, t3;

Je veux show un tas d'articles :

for (auto &obj : {w3, w4, w5, t1})
    obj.show();

Cependant, cela ne compile pas, car le std::initializer_list<T> dans le for -La boucle ne peut pas déduire T et en fait, il n'y a pas vraiment de T qui conviendrait. Je ne veux pas créer un type d'effacement en raison de la quantité de code nécessaire et de la surcharge inutile du temps d'exécution. Comment puis-je écrire correctement ma boucle pour que le type de obj est déduit pour chaque élément de la liste conceptuelle séparément ?

9 votes

0 votes

@LogicStuff Je ne peux pas vraiment faire que toutes les classes héritent de quelque chose avec un virtual void show() il devrait également fonctionner avec .size() et std-containers.

2 votes

La liste est-elle une liste d'exécution ou de compilation ? Vous pouvez toujours développer plusieurs appels à la même fonction. comme ici

64voto

Nikos Athanasiou Points 7015

En C++17 ou mieux, vous utiliserez expressions pliées pour "parcourir" vos arguments hétérogènes en appliquant la fonction membre :

auto Printer = [](auto&&... args) {
    (args.show(), ...);
};

Printer(w1, w2, w3, w4, w5, w6, t1, t2, t3);

Démo

Vous pouvez lire plus sur ce sujet dans mon blog

7 votes

@RichardHodges On ne peut pas faire plus moderne que ça.

0 votes

Comment cela fonctionne-t-il ? Le lambda est-il équivalent à un foncteur contenant une fonction modèle ?

0 votes

@immibis, c'est exactement ce que c'est.

40voto

Richard Hodges Points 1972

Boost::fusion est génial mais oldskool - il répond aux déficiences de c++03.

L'expansion variadique des modèles de c++11 à la rescousse !

#include <iostream>

struct Window{
    void show() {
        std::cout << "Window\n";
    }
    //stuff
}w1, w2, w3;

struct Widget{
    void show() {
        std::cout << "Widget\n";
    }
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show()
    {
        std::cout << "Toolbar\n";
    }
    //stuff
}t1, t2, t3;

template<class...Objects>
void call_show(Objects&&...objects)
{
    using expand = int[];
    (void) expand { 0, ((void)objects.show(), 0)... };
}

auto main() -> int
{
    call_show(w3, w4, w5, t1);
    return 0;
}

résultat attendu :

Window
Widget
Widget
Toolbar

un autre moyen, plus générique (nécessite c++14) :

// note that i have avoided a function names that look like
// one in the standard library.

template<class Functor, class...Objects>
void for_all(Functor&& f, Objects&&... objects)
{
    using expand = int[];
    (void) expand { 0, (f(std::forward<Objects>(objects)), 0)... };

}

appelé comme ça :

for_all([](auto& thing) { thing.show(); }, w3, w4, w5, t1);

3 votes

C'est drôle comme vous considérez boost::fusion oldskool, pourtant vous utilisez un moulage de style C. Deux poids, deux mesures ?

5 votes

@MaximEgorushkin :) c'est l'une des rares fois où un cast de style c est approprié, mais je peux le modifier pour ne pas en utiliser si nécessaire. Les cast sont là pour supprimer les avertissements du compilateur dans le cas où votre foncteur renvoie une valeur (qui est ensuite inutilisée).

0 votes

Il y a deux camps : ceux qui utilisent des moulages de style C et ceux qui ne le font pas. J'aime le deuxième camp parce qu'il a éliminé une catégorie d'erreurs résultant d'un mauvais jugement lorsqu'un moulage de style C est approprié.

21voto

Maxim Yegorushkin Points 29380

Une autre option consiste à utiliser boost::tuple ou std::tuple et boost::fusion::for_each algorithme :

#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>

boost::fusion::for_each(
    boost::tie(w1, w2, w3, w4, w5, w6, t1, t2, t3), // by reference, not a copy
    [](auto&& t) { t.show(); } 
    );

Juste par curiosité, j'ai comparé le résultat de l'assemblage généré par la méthode de Richard Hodges avec la méthode ci-dessus. Avec gcc-4.9.2 -Wall -Wextra -std=gnu++14 -O3 -march=native le code d'assemblage produit est identique.

0 votes

C'est réconfortant de le savoir. Sur mon installation de clang 7.0 d'apple avec -O3, le compilateur a tout intégré dans une série d'appels à cout::operator<<. c'est-à-dire absolument aucune surcharge. Si boost le fait aussi, c'est un témoignage des gars fantastiques qui maintiennent la bibliothèque.

0 votes

@RichardHodges Je suis d'accord. Facile à utiliser, portable et aussi rapide que les solutions non portables :)))

0 votes

Quelle réponse est non portable ?

15voto

nwp Points 1665

Sur la base de https://stackoverflow.com/a/6894436/3484570 cela fonctionne sans créer une fonction supplémentaire, un boost ou un héritage.

En-tête :

#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(const 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(const std::tuple<Tp...>& t, FuncT f)
  {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
  }

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...>(std::move(t), f);
  }

.cpp :

struct Window{
    void show(){}
    //stuff
}w1, w2, w3;

struct Widget{
    void show(){}
    //stuff
}w4, w5, w6;

struct Toolbar{
    void show(){}
    //stuff
}t1, t2, t3;

int main() {
    for_each(std::tie(w3, w4, w5, t1), [](auto &obj){
        obj.show();
    });
}

4 votes

Vous copiez w3, w4, w5, t1 lors de l'appel make_tuple . il semble que les frais généraux soient trop élevés pour copier des instances juste pour les imprimer. Démo

1 votes

@LorahAttkins Vous avez raison. Heureusement, cela fonctionne également avec std::tie pour que les copies soient évitables. Corrigé.

11voto

GingerPlusPlus Points 851

Window , Widget et Toolbar partagent une interface commune, de sorte que vous pouvez créer une classe abstraite et faire en sorte que d'autres classes en héritent :

struct Showable {
    virtual void show() = 0; // abstract method
};

struct Window: Showable{
    void show();
    //stuff
}w1, w2, w3;

struct Widget: Showable{
    void show();
    //stuff
}w4, w5, w6;

struct Toolbar: Showable{
    void show();
    //stuff
}t1, t2, t3;

Ensuite, vous pouvez créer un tableau de pointeurs vers Showable et itérer sur celui-ci :

int main() {
    Showable *items[] = {&w3, &w4, &w5, &t1};
    for (auto &obj : items)
        obj->show();
}

Voir le fonctionnement en ligne

3 votes

Cela a un coût d'exécution (les appels sont peut-être dévirtualisés), nécessite la modification de toutes les classes et il n'est pas possible de créer une classe de base commune pour chaque fonction. De plus, cela ne fonctionne pas pour les buildins, les variables de membre et les conteneurs std avec .size() . Mais vous avez généralement raison, c'est la solution traditionnelle.

0 votes

Pour la répartition au moment de l'exécution, pourquoi ne pas simplement std::bind les appels aux fonctions membres dans un tableau de std::function<void()> ? Aucun héritage virtuel sur le widget n'est requis.

0 votes

@nwp : Il y a un coût d'exécution minuscule, minuscule, minuscule, microscopique qui disparaît principalement dans les boucles ou lorsque la fonction de démonstration est non triviale. Votre solution a un coût en temps de paiement. Dans une entreprise, c'est dans de nombreux cas la solution la plus coûteuse, tant au niveau de la programmation que de la maintenance. Toutes les solutions ont leurs avantages et leurs inconvénients. Est-ce que tie() fonctionne toujours lorsque le client souhaite disposer d'interfaces utilisateur flexibles, par exemple comme on le voit des millions de fois dans les tableaux de bord typiques ?

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