运算符重载<<从 std::ostream_iterator 调用时未找到?

Overload of operator<< not found when called from std::ostream_iterator?

本文关键字:lt 调用 ostream 重载 std 运算符 iterator      更新时间:2023-10-16

程序

// main.cpp
#include <iostream>
#include <utility>
#include <algorithm>
#include <iterator>
#include <map>
template<typename t1, typename t2>
std::ostream& operator<<(std::ostream& os, const std::pair<t1, t2>& pair)
{
  return os << "< " << pair.first << " , " << pair.second << " >";
}
int main() 
{
  std::map<int, int> map = { { 1, 2 }, { 2, 3 } };
  std::cout << *map.begin() << std::endl;//This works
  std::copy(
    map.begin(),
    map.end(),
    std::ostream_iterator<std::pair<int,int> >(std::cout, " ")
  ); //this doesn't work
}

产生错误

no match for ‘operator<<’ (operand types are ‘std::ostream_iterator<std::pair<int, int> >::ostream_type {aka std::basic_ostream<char>}’ and ‘const std::pair<int, int>’)

我猜这是不工作,因为我的过载是不可用的std::copy,但为什么呢?

说明

由于operator<<命名空间std内部以不限定的方式调用(更具体地说在std::ostream_iterator内部),并且所有涉及的参数也在同一命名空间中声明,因此将只搜索命名空间std以查找潜在的匹配。


独创性的解决方案

namespace std {
  template<typename t1, typename t2>
  std::ostream& operator<<(std::ostream& os, const std::pair<t1, t2>& pair)
  {
     return os << "< " << pair.first << " , " << pair.second << " >";
  }
}

注意:您只能专门化包含用户定义类型的模板在namespace std中,因此根据标准,上面的代码片段可能是病态的(如果std::pair<T1,T2>不是用户声明的类型,请参阅本讨论)。


详细说明

下面我们有命名空间N,这将帮助我们模拟你对命名空间std的使用,以及当编译器试图为给定类型找到合适的重载时会发生什么。

名称空间N

namespace N {
  struct A { };
  struct B { };
  void func (A value) { std::cout << "A"; }
  template<class T>
  void call_func (T value) { func (value); }
}

main.cpp

void func (N::B value) {
  std::cout << "B";
}

<一口>

int main() {
  N::A a;
  N::B b;
  func (a);         // (1)
  func (b);         // (2)
  N::call_func (a); // (3a)
  N::call_func (b); // (3b)
}
指出

:

  1. 在不知道参数相关查找的情况下,人们可能会对编译器能够找到使(1)工作所需的合适的重载感到惊讶。

    ADL规定,在函数调用中使用非限定名称时,不仅要搜索当前名称空间以查找合适的重载,还要搜索参数的名称空间;这就是编译器找到N::func的方式,尽管我们没有明确地写。

  2. 我们在当前命名空间中有一个合适的重载;

……

为什么(3a)可以编译,而(3b)会导致一个讨厌的诊断?

当我们实例化模板N::call_func<T>时,它将尝试将T类型的参数传递给名为func非限定函数。

由于name-lookup规则规定,如果从不合格的名称调用函数,将搜索当前命名空间和所涉及的参数的命名空间以寻找合适的匹配,因此,如果T是在命名空间N中声明的类型,则将仅搜索命名空间N

N::AN::B都是在命名空间N中声明的,因此编译器将不会在中搜索任何其他作用域来找到合适的重载;

虽然这个问题已经得到了回答,但我只想补充一下,有一种更好的方法可以在不滥用复制功能的情况下打印地图。还有一个变换函数更适合做这样的事情。我重写了您的示例,以给您提示如何使用transform函数将映射转换为字符串并将它们打印到std::cout:

#include<iostream>
#include<map>
#include<algorithm>
#include<sstream>
template<typename t1, typename t2>
 std::ostream& operator<<(std::ostream& os, const std::pair<t1, t2>& pair)
 {
     return os << "< " << pair.first << " , " << pair.second << " >";
 }
std::string toString(const std::pair<int, int>& pair) {
    std::ostringstream str;
    str << "<" << pair.first << ", " << pair.second << ">";
    return str.str();
}
int main()
 {
  std::map<int, int> map = { std::make_pair(1, 2), std::make_pair(2, 3)};
  std::cout << *map.begin() << std::endl;//This works
  std::transform(map.begin(), map.end(),
  std::ostream_iterator<std::string>(std::cout, "n"), toString);
}