2009-05-22 5 views
3

Il arrive souvent que j'ai besoin d'itérer sur une liste de chaînes dans mon code C++.Y a-t-il un moyen facile d'itérateur sur une liste statique de chaînes en C++?

Dans les langues comme Perl, cela est facile:

foreach my $x ("abc", "xyz", "123") {.... } 

Dans le passé, ce que je l'ai fait en C++

const char* strs[] = { "abc", "xyz", "123" }; 
for (int i=0; i<sizeof(strs)/sizeof(const char*); i++) { 
    const char *str = strs[i]; 
    ... 

Si je possède déjà un conteneur STL, je peux utiliser BOOST_FOREACH

std::vector<std::string> strs; 
BOOST_FOREACH(std::string str, strs) { 
    ... 

J'ai essayé de créer une macro pour combiner tous ces concepts mais sans succès.

Je voudrais être en mesure d'écrire du code comme ceci:

SPECIAL_STRING_FOREACH(const char* str, {"abc", "xyz", "123"}) { 
    ... 
} 

Sûrement quelqu'un cuit cela avant.

Répondre

9

Voici ma tentative. Malheureusement, il s'appuie sur des macros variadic qui est une fonctionnalité C99/C++ 1x. Mais travaille dans GCC. Notez que vous pouvez également effectuer une itération avec une variable de référence pour éviter toute copie inutile. En voici un utilisant boost.preprocessor et la syntaxe (a)(b)..., compilant le même code après l'étape de pré-traitement.

#define SEQ_FOR_EACH(D, SEQ)           \ 
    if(bool c = false) ; else           \ 
     for(boost::remove_reference<boost::function_traits<void(D)> \ 
       ::arg1_type>::type _t[] = { BOOST_PP_SEQ_ENUM(SEQ) }; \ 
      !c; c = true)            \ 
      BOOST_FOREACH(D, _t) 

int main() { 
    SEQ_FOR_EACH(std::string &v, ("hello")("doctor")) { 
     std::cout << v << std::endl; 
    } 
} 

L'astuce consiste à assembler un type de fonction qui a pour paramètre la variable de dénombrement, et d'obtenir le type de ce paramètre. Puis boost::remove_reference va supprimer toute référence. Première version utilisée boost::decay. Mais il convertirait aussi les tableaux en pointeurs, ce que je trouve n'est pas ce que l'on veut parfois. Le type résultant est ensuite utilisé comme type d'élément de tableau.

Pour une utilisation dans les modèles où la variable énumérateur a un type dépendant, vous devrez utiliser une autre macro qui place typename avant boost::remove_reference et boost::function_traits. Pourrait le nommer SEQ_FOR_EACH_D (D == dépendant).

+0

+1 pour un effort héroïque :) –

0

Au lieu de cela:

for (int i=0; i<sizeof(strs)/sizeof(const char*); i++) 
{ 
    const char *str = strs[i]; 
    ... 

Considérez ceci:

for (const char *ptr = strs[0], 
    *end = strs[sizeof(strs)/sizeof(const char*)]; 
    ptr < end; ++ptr) 
{ 
    ... 

Vous trouverez peut-être cette forme plus facile à macro-iser; dans tous les cas, la variable ptr simule un itérateur.

Sûrement quelqu'un a cuisiné ça avant.

Je doute qu'ils devraient. Une boucle for est idiomatique et facile à lire (surtout si vous connaissez la taille de la matrice), les macros définies par l'utilisateur ne sont pas standard.

+0

Je préfère "sizeof (strs)/sizeof (* strs)" car cela évitera des problèmes si vous changez le type de données. –

+0

C'est vrai (bien que je dirais "sizeof (strs)/sizeof (strs [0])"); Je copiais et je collais son implémentation sans bien le lire. Je citerais aussi cela, par exemple "const size_t sizeof_strs = ... etc ..." sur la ligne immédiatement après la définition du tableau, de sorte que je puisse utiliser l'identificateur const à la place de l'expression longue dans les instructions suivantes, comme la boucle for. – ChrisW

4

Quelque chose comme ceci:


void func(const char* s) { /* ... */ } 

const char* array[] = { "abc", "xyz", "123" }; 
std::for_each(array, array + 3, func); 

Vous pouvez également jeter un oeil à boost::array.

+0

I) S'il vous plaît donner le code pour calculer le 3! II) Inefficace à cause de l'indirection du pointeur de fonction! III) Aucun avantage sur boost :: foreach ou boucles C++ régulières! – Dario

+1

Oh, allez, regardez d'autres réponses pour calculer la longueur de tableau statique, la fonction pourrait être remplacée par une instance de foncteur, l'avantage n'est pas d'inclure les en-têtes Boost, c'est-à-dire travailler avec la bibliothèque standard uniquement. , for_each EST une boucle C++. –

2

Cou pourrait utiliser le va_arg hack pour créer une fonction qui renvoie une collection itérables (notez que c'est vraiment un hack!)

Le nouveau C++ - normes (C++ 0x) fournira un moyen plus pratique de initialisation (initializer lists)

Une autre possibilité serait d'utiliser boost::assignment en combinaison avec FOREACH.

Notez que BOOST::FOREACH est également applicable aux baies!

+0

+1 pour avoir mentionné C++ 0x – lothar

5

Notez que traitant tableau C de chaîne est plus facile si vous marquez la fin du tableau:

const char* strs[] = { "abc", "xyz", "123", NULL }; 
for (int i=0; strs[i] != NULL i++) { 
    ... 
} 
0

Cela devrait le faire (je ne l'ai pas testé, donc il pourrait y avoir quelques fautes de frappe)

#define STR_ARRAY_FOREACH(I) const char* Items[] = I; for(const char *item = Items[0], *end = strs[ sizeof(Items)/sizeof(const char*) ]; item < end; ++item) 

Ensuite, utilisez un objet dans la boucle:

STR_ARRAY_FOREACH({ "abc", "xyz", "123" }) 
{ 
    cout << item << "\n"; 
} 
+0

Le préprocesseur n'aime pas la liste. Un simple test: #define M1 (L) L M1 ({1,2,3,4}) je reçois ce de g ++ macro "M1" a passé 4 arguments, mais prend juste 1. Je ne J'en sais assez sur l'écriture de macro pour résoudre ce problème. – mmccoo

1

Faire une macro qui retourne la taille du tableau aide ici.

#define N_ELEMS(a) (sizeof(a)/sizeof((a)[0])) 

Ensuite, votre code d'origine ne semble pas si mauvais.

for(int i = 0; i < N_ELEMS(strs); ++i) { 
    ... 
} 

C'est un bon idiome pour itérer sur n'importe quel tableau statique, pas seulement des tableaux de chaînes.

0

J'essayerais simplement d'utiliser BOOST_FOREACH directement sur la baie. Le documentation semble penser que cela fonctionnerait.

En fait, c'est le cas. Il doit être deux lignes, mais cela fonctionne:

const char * myarray[] = {"abc", "xyz", "123"}; 
BOOST_FOREACH (const char *str, myarray) { 
    std::cout << "Hello " << str << std::endl; 
} 
Questions connexes