Можно ли использовать boost::foreach с std:: map?
найти 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 ответов:
вы должны использовать:
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
что неправильно. Это указано в документации для каждого.
Я использую библиотека Ex диапазона Boost который реализует некоторые причудливые адаптеры диапазона для итерации по ключам карты или значениям. Например:
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";
конечно можно. Однако хитрость заключается в том, что итератор карты указывает на пару ключа и значения. Это будет выглядеть примерно так:
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; }
это возможно, но это не самый лучший способ сделать что-то (как я уже упоминал несколько раз раньше, for_each почти никогда не бывает, и BOOST_FOREACH только незначительно лучше). Для вашего первого примера, я думаю, вам будет лучше с:
std::copy(numbers.begin(), numbers.end(), std::ostream_iterator<int>(std::cout, " "));
Он работает довольно аналогично с картой, за исключением того, что вы должны определить оператор
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"));
типизация пары карт сбивает с толку. Самый простой способ итерации карты - это кортеж(как и в python):
std::map<int, int> mymap; int key, value; BOOST_FOREACH(boost::tie(key, value), mymap) { ... }
и не волнуйтесь, эти запятые не будут путать препроцессор, потому что я поместил скобки вокруг них.
мне не понравилась идея вынуждать добавлять typedefs каждый раз, когда я хотел использовать 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)
затем вы можете использовать его в коде: #определите 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" );
вы также можете изменить значения: #определите 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" );