boost::foreach可以与std::map一起使用吗?

52

我觉得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。文档中只有使用像 vectorstring 等类型的示例。


1
这并不是完全重复,但请参见此处:https://dev59.com/AXRB5IYBdhLWcg3w77on#461908#461908 - Michael Kristofik
8个回答

88

你需要使用:

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

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

这是因为该宏期望两个参数。当您尝试内联pair定义时,会引入第二个逗号,使得该宏变成了三个参数。预处理器不理解任何C++结构,它只知道文本。

因此,当您使用BOOST_FOREACH(pair<int, int>, map)时,预处理器将这三个参数视为该宏的参数:

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

这是错误的。在for-each文档中已经提到了这一点。


将其改为 pair<const int, int> - UncleBens
1
最后一次编辑引入了一些错误信息。由于最后两个示例无法编译,因此不存在未定义的行为。std::map 保护其键本身:如果您有 map<Key, Value>,则值类型为 pair<const Key, Value>。请注意,它使键类型成为 const。 - UncleBens
你能再编辑一下你的回答吗?我不小心取消了我的点赞,现在除非你编辑,否则我无法重新投票 =p 编辑:哦,很酷,我自己编辑了它,然后它就起作用了 :) +1 再次给予! - Andreas Bonini
注意,在C++11中,您也可以只需执行BOOST_FOREACH(const auto& myPair, map) - Claudiu

20

我使用Boost的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
当然可以。不过需要注意的是,map迭代器指向键值对。代码可能如下所示:
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;
}

我尝试过使用BOOST_FOREACH(int i, map)BOOST_FOREACH(pair<int, int>, map)等方法。你能否提供一个可行的示例? - Andreas Bonini
3
有人可能会提到BOOST_FOREACH是一个,因此无法正确处理pair模板中的逗号。这就是为什么每个人都建议使用typedef的原因。 - UncleBens
@UncleBens:我认为typedef使代码看起来更加整洁,即使宏可以处理逗号(不确定是否可以)。 - Fred Larson
2
逗号对于预处理器来说只有一个含义——参数分隔符。它不知道模板的存在,也不知道在<>之间的逗号不会引入另一个参数。 - UncleBens
@UncleBens:是的,看起来你是正确的。但是我的意思不在于使用typedef。我认为这才是OP一直以来遇到的问题,虽然当我想出示例代码时我无从知晓。 - Fred Larson

2

这是可能的,但这并不是做事情的最佳方式(正如我之前多次提到的那样,for_each 几乎从来不是最好的选择,而 BOOST_FOREACH 只是略微好一些)。对于你的第一个示例,我认为你最好使用:

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

它与地图的工作方式非常相似,只是您需要为其定义operator<<,因为没有已经定义好的:

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
+1. 我同意你的观点,Jerry。尽管有些人可能会争论说定义操作符(哎呀,你这里打错字了!)比使用BOOST_FOREACH更麻烦。 - Fred Larson
@Fred:他们可以争辩,而且在极小的程度上,这甚至是真的。然而,做好工作通常比仅仅编写一些勉强能用的东西要更费力(至少在前期)。 - Jerry Coffin

2

我不喜欢每次想要在一个 map 上使用 foreach 循环时都被迫添加 typedef,所以这里是我基于 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
如果你不喜欢typedef,那就使用#define!? - benathon

2

将map pair定义为typedef有些令人困惑。最简单的遍历map的方法是使用元组(就像在Python中一样):

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

不用担心,那些逗号不会让预处理器混淆,因为我在它们周围放置了括号。

这个做法的缺点是复制了映射的值。如果不是一个原始类型,那么这可能会很昂贵。 - balki

1

是的:

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

MyMap    myMap;

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

0
在C++0x中,你可以更容易地做到:
map<int, string> entries;
/* Fill entries */

foreach(auto i, entries)
   cout << boost::format("%d = %s\n") % i.first % i.second;

1
根据维基百科,foreach 语法更像是 Java 的 foreach for(int& x : my_array) { x *= 2; } - João Portela
我认为海报假设了旧的做法 #include<boost/foreach.hpp#define foreach BOOST_FOREACH - hannes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接