首页 > 未分类 > 为什么不能在 for_each 中使用 std::cout << boost::lambda::_1 << std::endl;

为什么不能在 for_each 中使用 std::cout << boost::lambda::_1 << std::endl;

C++ 11 已经原生支持了 lambda 表达式。如果真的需要标题中的写法,请考虑使用原生的 lambda 表达式。

虽然说在 for_each(begin, end, functor) 中,functor 不能使用
std::cout << boost::lambda::_1 << std::endl
的写法,但是毫无疑问,使用
std::cout << boost::lambda::_1 << "\n"
是没有问题的。

这个初看起来可能很奇怪。实际上原因是这样的。

在我的 MSVC++ 2012 RC 的 STL 中,std::endl 的定义是这样的(经过简化):

ostream& endl(ostream& os) {
  os.put('\n');
  os.flush();
  return os;
}

wostream& endl(wostream& os) {
  os.put('\n');
  os.flush();
  return os;
}

请注意这儿 endl 是被重载了的(实际上还有更多重载版本,这儿只列出来两个,可以说明问题即可。)。

而 std::basic_ostream (std::ostream / std::wostream 实际上是对 std::basic_ostream 实例化的结果)中对 operator << 有这么一个重载版本(经过简化):

basic_ostream<x, y> operator << (basic_ostream<x, y>& (__cdecl *func)(basic_ostream<x, y>&)) {
  return (func(*this));
}
这样,当碰到 std::cout << std::endl; 就根据 std::cout 的类型把 wchar_t 等等的 std::endl 否定掉,这样就只有 char 类型的 std::endl 可选。 于是调用了 std::endl 这个函数,然后输出换行并刷新缓冲区。 而到了 std::cout << boost::lambda::_1 << std::endl; 这儿,情况就完全不一样了。 首先,为了能够让 std::cout << boost::lambda::_1 生成一个 functor 来让 for_each 接受它,boost 库有类似这样的重载(经过简化):
lambda_functor<...> operator << (ostream& os, lambda_functor<...>& arg) {
  ...
}
其中 lambda_functor 正是 boost::lambda::_1 的类型。 这个重载版本实际上是返回了一个 lambda_functor_base 类型的对象,因此 std::cout << boost::lambda::_1; 实际上会构造一个 lambda_functor_base 类型的对象传给 for_each,这样如下的代码就可以编译通过了: std::for_each(begin, end, std::cout << boost::lambda::_1); 那么 boost::lambda 中那个重载版本的 operator << 究竟构造成了什么样的对象呢?是这样的:
boost::lambda::lambda_functor<
  boost::lambda::lambda_functor_base<
    boost::lambda::bitwise_action<
      boost::lambda::leftshift_action
    >,
    boost::tuples::tuple<
      std::basic_ostream<char, std::char_traits<char>> &,
      boost::lambda::lambda_functor<boost::lambda::placeholder<1>>,
      boost::tuples::null_type,
      boost::tuples::null_type,
      boost::tuples::null_type,
      boost::tuples::null_type,
      boost::tuples::null_type,
      boost::tuples::null_type,
      boost::tuples::null_type,
      boost::tuples::null_type
  
    >
  >
>
很遗憾,它没有针对 ostream 所使用的 std::endl 的类型,也就是: std::basic_ostream<char, std::char_traits<char>>& (endl_type)(std::basic_ostream<char, std::char_traits<char>>&) 专门重载 operator <<,所以编译器就无从得知该用哪个 std::endl,进而产生了编译错误。 当然,如果我们告诉编译器我们需要用的 std::endl 是哪个版本,那么也就不会出现问题了。就像这样(我这儿的 ostream 所使用的 std::endl 是 std::ostream& (*)(std::ostream&) 类型的): std::for_each(begin, end, std::cout << boost::lambda::_1 << (std::ostream& (*)(std::ostream&))std::endl); 如果从技术上,单独针对 std::cout << boost::lambda::_1 << std::endl 这样的语句做一个重载版本,我觉得应该是可行的,但是一方面,在实际使用中还有类似于 std::cout << boost_lambda::_1 << 1 << ... << std::endl 这种语句,另外一方面据说 GCC 下面 std::endl 是作为一个模板函数实现的,所以要把所有情况全部处理掉可能就会存在问题。 另外,如何把 ostream 使用的 std::endl 跟 wostream 使用的 std::endl 区分开也是一个问题。 当然,现在 C++ 11 有了原生的 lambda 支持,这么写就可以了:
std::for_each(begin, end, [] (type value) {
  std::cout << value << std::endl;
}

  1. 本文目前尚无任何评论.