Je trouve boost :: foreach très utile car cela me fait économiser beaucoup d'écriture. Par exemple, disons que je veux imprimer tous les éléments d'une liste:
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 << " ";
Bien mieux! Cependant, je n'ai jamais trouvé de moyen (si c'est possible) de l'utiliser pour std::map
s. La documentation ne contient que des exemples avec des types tels que vector
ou string
.
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'insérer la définition de paire, vous introduisez une deuxième virgule, ce qui rend la macro trois paramètres à la place. Le préprocesseur ne respecte aucune construction C++, il ne connaît que le texte.
Ainsi, lorsque 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. C'est mentionné dans la documentation pour chaque.
J'utilise la bibliothèque Range Ex de Boost qui implémente des adaptateurs de gamme sophistiqués pour itérer sur les clés ou les valeurs de la 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";
Sûr que vous pouvez. L'astuce est, cependant, qu'un itérateur de carte pointe vers une paire de clés et de valeurs. Cela 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;
}
C'est possible, mais ce n'est pas vraiment la meilleure façon de faire les choses (comme je l'ai déjà mentionné à plusieurs reprises, for_each ne l'est presque jamais, et BOOST_FOREACH n'est que légèrement meilleur). Pour votre premier exemple, je pense que vous feriez mieux avec:
std::copy(numbers.begin(), numbers.end(),
std::ostream_iterator<int>(std::cout, " "));
Cela fonctionne de manière assez similaire avec une carte, sauf que vous devez définir l'opérateur << pour cela, car il n'y en a pas déjà un 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 très bien:
std::copy(mymap.begin(), mymap.end(),
std::ostream_iterator<vt>(std::cout, "\n"));
Je n'aimais pas l'idée d'être obligé d'ajouter des typedefs chaque fois que je voulais utiliser un foreach sur une carte. Voici donc mon implémentation basée sur le boost foreach code:
#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" );
Taper une paire de cartes est source de confusion. La façon la plus simple d'itérer une carte est avec 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 perturberont pas le préprocesseur car j'ai placé des parenthèses autour d'eux.
Oui:
typedef std::map<std::string,int> MyMap;
MyMap myMap;
BOOST_FOREACH(MyMap::value_type loop, myMap)
{
// Stuff
}
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;