web-dev-qa-db-fra.com

Est-il possible d'utiliser boost :: foreach avec std :: map?

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::maps. La documentation ne contient que des exemples avec des types tels que vector ou string.

52
Thomas Bonini

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.

88
GManNickG

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";
20
Manuel

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;
}
3
Fred Larson

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"));
2
Jerry Coffin

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" );
2
marko.ristin

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.

2
Paul Fultz II

Oui:

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

MyMap    myMap;

BOOST_FOREACH(MyMap::value_type loop, myMap)
{ 
       // Stuff
}
1
Martin York

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;
0
Thomas Bonini