这个模板函数是在哪里生成的?可以通过 g++ 编译,但不能在 visual studio 中编译

Where is this template function generated? Can compile by g++ but not in visual studio

以下代码无法在我的 visual studio 2019 中编译。但是如果我删除 >> 的第一个重载,它会编译。

代码可以用g++编译,这让我很困惑。估计是编译器生成模板函数的位置不同吧?

错误消息:错误 C2679:二进制“>>”:未找到采用 'std::vector<int,std::allocator<_T>>'

类型右操作数的运算符
#include <iostream>
#include <vector>
typedef std::vector<int> Mon;  // ordered
typedef std::vector<Mon> Poly;  // ordered

class A {};

// It would compile successfuly if this function is removed
std::istream& operator>>(std::istream& sin, A& a)
{
    return sin;
}

template <typename Container>
void load(std::istream& sin, Container& cont)
{
    typename Container::value_type n;
    sin >> n;
}

std::istream& operator>>(std::istream& sin, Mon& mon)
{
    load(sin, mon);
    return sin;
}

std::istream& operator>>(std::istream& sin, Poly& poly)
{
    load(sin, poly);
    return sin;
}

int main()
{
    return 0;
}

潜在的问题是全局命名空间中的这个函数签名:

std::istream& operator>>(std::istream& sin, std::vector<int>& mon);

无法通过 argument-dependent 查找找到。由于所有参数都在 std 中,ADL 仅搜索 std 而不是全局命名空间。
为避免此类问题,您可以遵循一条经验法则:不要以 ADL 无法找到的方式重载运算符。 (推论:你不应该试图让 vector<Foo> v; cin >> v; 工作)。


首先,请注意语法 sin >> n 转换为同时执行 operator>>(sin, n)sin.operator>>(n) 并组合所有结果,as described in full here.

问题中的代码与this question非常相似,我将在那里总结最佳答案的发现。

对于这个函数:

template <typename Container>
void load(std::istream& sin, Container& cont)
{
    typename Container::value_type n;
    sin >> n;
}

特别是当查找 operator>>(sin, n) 时,operator>> 是一个 从属名称 因为它是一个函数调用的名称,其参数类型取决于模板参数。

当名称查找应用于从属函数名称时(参考:[temp.dep.candidate]),规则是:

  1. 任何在模板定义点可见的函数声明都会被考虑。
  2. ADL 在实例化时发现的任何函数声明都会被考虑。
  3. 如果在程序的其他地方定义了 extern 个函数,如果它们在实例化时有一个可见的声明,ADL 会发现这些函数,并且这些额外的声明会影响重载解析,然后该程序有未定义的行为(不需要诊断)。

(注意:我的这个答案的第一个版本错误地引用了规则 3,因此得出了错误的结论):

因此从调用 load(sin, mon); 实例化的 sin >> n 的查找成功,因为找到了成员函数 std::istream::operator>>(int&)。 (搜索也找到了A&版本但是重载解析选择了成员函数)。

问题出现在 load(sin, poly); 实例化的 sin >> n 的查找中。

根据规则1,找到operator>>(std::istream&, A&)。 (这个函数稍后会被重载决议丢弃,但在这个阶段我们只是执行名称查找)。

根据规则 2,ADL 命名空间列表为:std。所以这一步会找到 std::operator>>(各种重载),但不会找到 ::operator>>(istream&, Mon&);,因为它不在命名空间 std.

规则 3 不适用,因为 namespace std 中没有任何重载可以接受 Mon.

所以正确的行为是:

  • 由于在实例化 load(sin, poly);.
  • 时不匹配 sin >> n,发布的代码应该编译失败
  • 标记为It would compile successfully if this function is removed的行实际上应该没有区别;出于同样的原因,代码仍然会编译失败。

结论: 在我看来:

  • clang 8.0.0 行为正确。
  • msvc 正确拒绝发布的代码,但错误地接受修改后的版本。
  • gcc 9.1.0 错误地接受两个版本;

我注意到,如果我们将 operator>> 更改为 bar 并将 sin >> n 更改为 bar(sin, n);,那么 gcc 和 msvc 会正确地拒绝这两个版本。 gcc 甚至给出了与 clang 非常相似的错误消息。

所以我推测该错误可能是 overloaded operator name lookup rules 的错误应用——它与 non-operator 名称略有不同,但与此代码示例没有任何关系。

有关这些规则的基本原理和 MSVC 行为的 in-depth 文章,请参阅 this excellent article