c++ - ¿Es posible usar boost:: foreach con std:: map?
(8)
En C ++ 0x puedes hacer más fácilmente:
map<int, string> entries;
/* Fill entries */
foreach(auto i, entries)
cout << boost::format("%d = %s/n") % i.first % i.second;
Encuentro boost::foreach muy útil ya que me ahorra mucha escritura. Por ejemplo, digamos que quiero imprimir todos los elementos en una lista:
std::list<int> numbers = { 1, 2, 3, 4 };
for (std::list<int>::iterator i = numbers.begin(); i != numbers.end(); ++i)
cout << *i << " ";
boost :: foreach hace que el código anterior sea mucho más simple:
std::list<int> numbers = { 1, 2, 3, 4 };
BOOST_FOREACH (int i, numbers)
cout << i << " ";
¡Mucho mejor! Sin embargo, nunca encontré la forma (si es posible) de usarlo para std::map
s. La documentación solo tiene ejemplos con tipos tales como vector
o string
.
Es posible, pero en realidad no es la mejor manera de hacer las cosas (como ya he mencionado algunas veces, ya que casi nunca, y BOOST_FOREACH solo es marginalmente mejor). Para su primer ejemplo, creo que estaría mejor con:
std::copy(numbers.begin(), numbers.end(),
std::ostream_iterator<int>(std::cout, " "));
Funciona de manera similar con un mapa, excepto que tiene que definir el operador << para ello, ya que no hay uno ya definido:
typedef map<std::string, int>::value_type vt;
std::ostream &operator<<(std::ostream &os, vt &v) {
return os << v.first << ": " << v.second;
}
... y una vez más, std::copy
hace el trabajo bastante bien:
std::copy(mymap.begin(), mymap.end(),
std::ostream_iterator<vt>(std::cout, "/n"));
Necesitas usar:
typedef std::map<int, int> map_type;
map_type map = /* ... */;
BOOST_FOREACH(const map_type::value_type& myPair, map)
{
// ...
}
La razón es que la macro espera dos parámetros. Cuando intenta alinear la definición de par, introduce una segunda coma, lo que hace que la macro tenga tres parámetros. El preprocesador no respeta ninguna construcción de C ++, solo conoce el texto.
Entonces, cuando dice BOOST_FOREACH(pair<int, int>, map)
, el preprocesador ve estos tres argumentos para la macro:
1. pair<int
2. int>
3. map
Cuál está mal. Esto se menciona en la documentación de cada uno.
No me gustó la idea de tener que agregar typedefs cada vez que quería usar un foreach en un mapa. Así que aquí está mi implementación basada en el código de impulso foreach:
#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)
Luego puede usarlo en su código: #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" );
También puede cambiar los valores: #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" );
Sí:
typedef std::map<std::string,int> MyMap;
MyMap myMap;
BOOST_FOREACH(MyMap::value_type loop, myMap)
{
// Stuff
}
Seguro que puede. El truco es, sin embargo, que un iterador de mapa apunta a un par de la clave y el valor. Se vería algo como esto:
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;
}
Typedefing un par de mapas es confuso. La forma más simple de iterar un mapa es con una tupla (como en python):
std::map<int, int> mymap;
int key, value;
BOOST_FOREACH(boost::tie(key, value), mymap)
{
...
}
Y no se preocupe, esas comas no confundirán al preprocesador porque coloqué paréntesis a su alrededor.
Utilizo la biblioteca Range Ex de Boost que implementa algunos adaptadores de gama elegantes para iterar sobre claves o valores de mapa. Por ejemplo:
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";