Можно ли использовать boost :: foreach с std :: map? - PullRequest
52 голосов
/ 20 января 2010

Я считаю boost :: foreach очень полезным, поскольку это экономит мне много времени. Например, скажем, я хочу напечатать все элементы в списке:

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

boost :: foreach значительно упрощает приведенный выше код:

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

Намного лучше! Однако я так и не нашел способ (если это вообще возможно) использовать его для std::map с. В документации есть только примеры с типами, такими как vector или string.

Ответы [ 8 ]

88 голосов
/ 20 января 2010

Вам необходимо использовать:

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

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

Причина в том, что макрос ожидает два параметра. Когда вы пытаетесь встроить определение пары, вы вводите вторую запятую, вместо этого задаете для макроса три параметра. Препроцессор не уважает никакие конструкции C ++, он знает только текст.

Итак, когда вы говорите BOOST_FOREACH(pair<int, int>, map), препроцессор видит эти три аргумента для макроса:

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

Что не так. Это упоминается в документации для каждого.

20 голосов
/ 20 января 2010

Я использую Библиотека Boost's Range Ex , в которой реализованы некоторые модные адаптеры диапазона для перебора ключей или значений карты.Например:

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";
3 голосов
/ 20 января 2010

Конечно, вы можете. Однако хитрость в том, что итератор карты указывает на пару ключа и значения. Это будет выглядеть примерно так:

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;
}
2 голосов
/ 15 августа 2012

Мне не нравилась идея принудительного добавления typedef каждый раз, когда я хотел использовать foreach на карте. Итак, вот моя реализация, основанная на коде boost 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)        

Тогда вы можете использовать его в своем коде: #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" );

Вы также можете изменить значения: #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 голосов
/ 24 января 2012

Тип определения пары карт сбивает с толку. Самый простой способ итерации карты - это кортеж (как в python):

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

И не волнуйтесь, эти запятые не перепутают препроцессор, потому что я поставил вокруг них скобки.

2 голосов
/ 20 января 2010

Это возможно, но на самом деле это не лучший способ сделать что-то (как я уже упоминал несколько раз ранее, for_each почти никогда не бывает, а BOOST_FOREACH только немного лучше) Для вашего первого примера, я думаю, вам будет лучше:

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

С картой она работает примерно так же, за исключением того, что для нее нужно определить оператор <<, поскольку она еще не определена: </p>

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

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

... и еще раз, std::copy делает работу довольно хорошо:

std::copy(mymap.begin(), mymap.end(), 
          std::ostream_iterator<vt>(std::cout, "\n"));
1 голос
/ 20 января 2010

Да

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

MyMap    myMap;

BOOST_FOREACH(MyMap::value_type loop, myMap)
{ 
       // Stuff
}
0 голосов
/ 15 ноября 2010

В C ++ 0x вам будет проще:

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

foreach(auto i, entries)
   cout << boost::format("%d = %s\n") % i.first % i.second;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...