使用 ostream_iterator 将 STL 映射的内容写入输出流

Writing contents of an STL Map to output stream using ostream_iterator

我有一个 map<string, int> 对象,我想使用 ostream_iterator 将其内容写入屏幕或文件。我重载了输出运算符 (operator<<),以便它可以用于将类型 pair<const string, int> 的对象写入输出流,但是当我尝试编译代码时,我收到以下错误消息:

error: no match for ‘operator<<’ (operand types are ‘std::ostream_iterator<std::pair<const std::__cxx11::basic_string, int> >::ostream_type’ {aka ‘std::basic_ostream’} and ‘const std::pair<const std::__cxx11::basic_string, int>’)
207 | *_M_stream << __value;

我最终使用 for_each 函数来编写内容,但我很好奇是否可以使用流迭代器来完成这项工作。 这是代码的摘录:

typedef map<string, int>::value_type map_value_type;

ostream &operator<<(ostream &out, const map_value_type &value) {
  out << value.first << " " << value.second;
  return out;
}

int main() {
  map<string, int> m;

  // code to fill the map

  // The following works with no problem
  for_each(m.begin(), m.end(), [](const map_value_type &val) { cout << val << endl; });

  // This line will not compile
  copy(m.begin(), m.end(), ostream_iterator<map_value_type>(cout, "\n"));
}

奇怪的是,当我强制编译器给出上面 operator<< 函数中使用的参数的完整类型名称时,它们与错误消息中提到的类型完全匹配,但由于某种原因编译器无法识别使用它。我正在使用带有 -std=gnu++17 标志的 g++ (Ubuntu 9.3.0-17ubuntu1~20.04),但是 Visual Studio 编译器(cl.exe 版本 19.29.30140)将给出同样的错误。

我也为 operator<< 尝试了以下方法但没有成功:

ostream &operator<<(ostream &out, const pair<string, int> &val);
ostream &operator<<(ostream &out, const pair<const string, int> &val);
ostream &operator<<(ostream &out, pair<const string, int> &val);
ostream &operator<<(ostream &out, pair<string, int> val);
ostream &operator<<(ostream &out, pair<const string, int> val);

template <typename key, typename value>
ostream &operator<<(ostream &out, const pair<key, value> &val) { ... }

上面提到的所有函数都适用于 for_each 方法,但是 none 它们适用于 ostream_iterator

我错过了什么?!

std::ostream_iterator 在内部使用 <<

当它为一个类型实例化时,<< 将仅通过从实例化点开始的 argument-dependent 查找找到 operator<< 重载,而不是通过正常的非限定名称查找。

对于类型 pair<const string, int>map<string, int> 的元素类型),考虑用于 argument-dependent 查找的命名空间仅为 ::std,因为 pairstring在其中定义。您在全局命名空间中的重载将不会被考虑。

如果你有一个像 pair<MyClass, int> 这样的类型,其中 MyClass 是你在全局范围内定义的 class,重载将起作用,因为那样的话全局命名空间范围将是作为模板参数的关联命名空间的参数依赖查找的一部分 MyClass.

使用 lambda 的版本有效,因为它也从 lambda 的定义点进行正常的非限定查找,这会在全局命名空间中找到重载。

不幸的是,据我所知,对于不依赖于自定义类型的标准库容器专业化,没有 standard-conform 重载 operator<< 的方法,因此它将通过 ADL 找到,例如通过 std::ostream_iterator.

Standard-conform 是问题所在,因为标准禁止将 operator<< 的重载添加到命名空间 std,否则技术上可以解决问题。