首页 > 解决方案 > Where is this template function generated? Can compile by g++ but not in visual studio

问题描述

The following code can not compile in my visual studio 2019. But if I delete the first overloading of >> it would compile.

The code can compile by g++ which confused me. I guess it is about different locations where the template function is generated by the compiler?

Error message: error C2679: binary '>>': no operator found which takes a right-hand operand of type '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;
}

标签: c++templatesgccvisual-c++argument-dependent-lookup

解决方案


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

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

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


首先,请注意语法sin >> n转换为同时执行operator>>(sin, n)sin.operator>>(n)组合所有结果,如此处的完整描述

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

对于此功能:

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,因此得出了错误的结论):

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

sin >> n查找实例化的 by会出现问题load(sin, poly);

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

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

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

所以正确的行为是:

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

结论:在我看来:

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

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

所以我推测该错误可能是重载运算符名称查找规则的错误应用——这与非运算符名称略有不同,但与此代码示例没有任何关系。

要深入了解这些规则的基本原理和 MSVC 的行为,请参阅这篇出色的文章


推荐阅读