2010-01-20 8 views
51

Je trouve boost::foreach très utile car cela me permet d'économiser beaucoup d'écriture. Par exemple, disons que je veux imprimer tous les éléments dans une liste:Est-il possible d'utiliser boost :: foreach avec std :: map?

std::list<int> numbers = { 1, 2, 3, 4 }; 
for (std::list<int>::iterator i = numbers.begin(); i != numbers.end(); ++i) 
    cout << *i << " "; 

boost :: foreach rend le code ci-dessus beaucoup plus simple:

std::list<int> numbers = { 1, 2, 3, 4 }; 
BOOST_FOREACH (int i, numbers) 
    cout << i << " "; 

beaucoup mieux! Cependant, je n'ai jamais trouvé un moyen (si c'est possible) de l'utiliser pour std::map s. La documentation contient uniquement des exemples avec des types tels que vector ou string.

+1

Ce n'est pas exactement un double, mais voir ici: http://stackoverflow.com/questions/461507/how-to-use-boostforeach-with-a-boostptrmap/461908 # 461908 –

Répondre

88

Vous devez utiliser:

typedef std::map<int, int> map_type; 
map_type map = /* ... */; 

BOOST_FOREACH(const map_type::value_type& myPair, map) 
{ 
    // ... 
} 

La raison étant que la macro attend deux paramètres. Lorsque vous essayez d'intégrer la définition de paire, vous introduisez une deuxième virgule, ce qui rend les paramètres de la macro trois à la place. Le préprocesseur ne respecte aucune construction C++, il ne connaît que le texte.

Alors, quand vous dites BOOST_FOREACH(pair<int, int>, map), le préprocesseur voit ces trois arguments pour la macro:

1. pair<int
2. int>
3. map

Ce qui est faux. Ceci est mentioned dans la documentation pour chaque.

+0

Faites que 'pair '. – UncleBens

+1

La dernière modification introduit de la désinformation. Il n'y a pas de comportement indéfini, puisque les deux derniers exemples ne seront pas compilés. 'std :: map' protège sa clé elle-même: si vous avez' map 'alors le type de valeur est' pair '. Notez qu'il fait le type de clé const. – UncleBens

+0

Pouvez-vous modifier à nouveau votre réponse? J'ai annulé accidentellement mon upvote et maintenant il ne me fera pas le refaire à moins que vous n'éditiez = p EDIT: oh, cool, je l'ai édité moi-même et ça a fonctionné :) +1 regiven! –

3

Bien sûr, vous pouvez. L'astuce est, cependant, qu'un itérateur de carte pointe vers une paire de clé et de valeur. Il ressemblerait à quelque chose comme ceci:

typedef std::map<std::string, int> MapType; 
MapType myMap; 

// ... fill the map... 

BOOST_FOREACH(MapType::value_type val, myMap) 
{ 
    std::cout << val.first << ": " << val.second << std::endl; 
} 
+0

Eh bien, j'ai essayé avec 'BOOST_FOREACH (int i, carte)', 'BOOST_FOREACH (paire , carte)', etc. Pouvez-vous poster un exemple de travail? –

+3

Quelqu'un pourrait mentionner que 'BOOST_FOREACH' est une * macro *, et donc il ne peut pas traiter correctement la virgule dans le modèle de paire. C'est la raison pour laquelle tout le monde suggère un typedef. – UncleBens

+0

@UncleBens: Je pense que le typedef donne l'impression d'être beaucoup plus propre, même si la macro peut gérer la virgule (je ne sais pas si c'est possible). –

1

Oui:

typedef std::map<std::string,int> MyMap; 

MyMap myMap; 

BOOST_FOREACH(MyMap::value_type loop, myMap) 
{ 
     // Stuff 
} 
2

Il est possible, mais ce n'est pas vraiment la meilleure façon de faire les choses (comme je l'ai mentionné quelques fois auparavant, presque jamais est for_each et BOOST_FOREACH est seulement un peu mieux). Pour votre premier exemple, je pense que vous seriez mieux avec:

std::copy(numbers.begin(), numbers.end(), 
      std::ostream_iterator<int>(std::cout, " ")); 

Il fonctionne assez de même avec une carte, sauf que vous devez définir l'opérateur < < pour elle, car il n'y a pas déjà défini :

typedef map<std::string, int>::value_type vt; 

std::ostream &operator<<(std::ostream &os, vt &v) { 
    return os << v.first << ": " << v.second; 
} 

... et encore une fois, std::copy fait le travail tout à fait bien:

std::copy(mymap.begin(), mymap.end(), 
      std::ostream_iterator<vt>(std::cout, "\n")); 
+1

+1. Je suis d'accord avec vous, Jerry, bien que certains pourraient argumenter que devoir définir l'opérateur (oups, vous avez une faute de frappe là-bas!) Est plus difficile que le BOOST_FOREACH. –

+0

@Fred: Ils peuvent argumenter que, et dans une mesure extrêmement minime, c'est même vrai. Encore une fois, faire le bon travail est souvent un peu plus de travail (au moins à l'avance) que de simplement pirater quelque chose qui fonctionne. –

20

J'utilise Boost's Range Ex library qui diablotin Fournit des adaptateurs de gamme sophistiqués pour l'itération sur des clés ou des valeurs de carte. Par exemple:

map<int, string> foo; 
foo[3] = "three"; 
foo[7] = "seven"; 

BOOST_FOREACH(i, foo | map_keys) 
    cout << i << "\n"; 


BOOST_FOREACH(str, foo | map_values) 
    cout << str << "\n"; 
0

En C++ 0x vous pouvez plus facilement faire:

map<int, string> entries; 
/* Fill entries */ 

foreach(auto i, entries) 
    cout << boost::format("%d = %s\n") % i.first % i.second; 
+1

En s'accompagnant de wikipedia, la syntaxe foreach ressemble plus à java foreach 'for (int & x: my_array) {x * = 2; } ' –

+0

Je crois que l'affiche a supposé l'ancienne pratique de' #include hannes

2

Typedefing une paire de carte est source de confusion.La façon la plus simple pour itérer une carte est un tuple (comme en python):

std::map<int, int> mymap; 
int key, value; 
BOOST_FOREACH(boost::tie(key, value), mymap) 
{ 
    ... 
} 

Et ne vous inquiétez pas, ces virgules ne confondra pas le préprocesseur parce que je mis entre parenthèses autour d'eux.

+0

Cela a un inconvénient de copier la valeur de la carte. Cela peut être coûteux si ce n'est pas une primitive. – balki

2

Je n'aimais pas l'idée d'être obligé d'ajouter typedefs chaque fois que je voulais utiliser un foreach sur une carte. Voici donc ma mise en œuvre sur la base du code foreach boost:

#ifndef MUNZEKONZA_FOREACH_IN_MAP 

#include <boost/preprocessor/cat.hpp> 
#define MUNZEKONZA_FOREACH_IN_MAP_ID(x) BOOST_PP_CAT(x, __LINE__) 

namespace munzekonza { 
namespace foreach_in_map_private { 
inline bool set_false(bool& b) { 
    b = false; 
    return false; 
} 

} 
} 

#define MUNZEKONZA_FOREACH_IN_MAP(key, value, map)       \ 
for(auto MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it) = map.begin();  \ 
     MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it) != map.end();)  \ 
for(bool MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) = true;  \ 
     MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) &&    \ 
     MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it) != map.end();   \ 
     (MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue)) ?    \ 
     ((void)++MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it)) :   \ 
     (void)0)                \ 
    if(munzekonza::foreach_in_map_private::set_false(       \ 
      MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue))) {} else \ 
    for(key = MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it)->first;   \ 
     !MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue);    \ 
     MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) = true)  \ 
    if(munzekonza::foreach_in_map_private::set_false(       \ 
      MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue))) {} else \ 
    for(value = MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it)->second;  \ 
     !MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue);    \ 
     MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) = true)   

Ensuite, vous pouvez l'utiliser dans votre code: #define foreach_in_map MUNZEKONZA_FOREACH_IN_MAP

std::map<int, std::string> mymap; 
mymap[0] = "oi"; 
mymap[1] = "noi"; 

std::map<int, std::string> newmap; 

foreach_in_map(int key, const std::string& value, mymap) { 
    newmap[key] = value; 
} 

ASSERT_EQ(newmap.size(), 2); 
ASSERT_EQ(newmap.count(0), 1); 
ASSERT_EQ(newmap.count(1), 1); 
ASSERT_EQ(newmap.at(0), "oi"); 
ASSERT_EQ(newmap.at(1), "noi"); 

Vous pouvez également modifier les valeurs: #define foreach_in_map MUNZEKONZA_FOREACH_IN_MAP

std::map<int, std::string> mymap; 

mymap[0] = "oi"; 
mymap[1] = "noi"; 

std::map<int, std::string> newmap; 

foreach_in_map(int key, std::string& value, mymap) { 
    value = "voronoi" + boost::lexical_cast<std::string>(key); 
} 

ASSERT_EQ(mymap.size(), 2); 
ASSERT_EQ(mymap.count(0), 1); 
ASSERT_EQ(mymap.count(1), 1); 
ASSERT_EQ(mymap.at(0), "voronoi0"); 
ASSERT_EQ(mymap.at(1), "voronoi1"); 
+2

si vous n'aimez pas typedefs, utilisez simplement #define!? – portforwardpodcast