c++ - 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;
}
解决方案
根本问题是全局命名空间中的这个函数签名:
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])时,规则是:
- 考虑在模板定义点可见的任何函数声明。
- 将考虑 ADL 在实例化时找到的任何函数声明。
- 如果
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 >> n
bar(sin, n);
所以我推测该错误可能是重载运算符名称查找规则的错误应用——这与非运算符名称略有不同,但与此代码示例没有任何关系。
要深入了解这些规则的基本原理和 MSVC 的行为,请参阅这篇出色的文章。
推荐阅读
- regex - 正则表达式从 CSV 中删除未转义的引号
- javascript - 如何索引对象属性?
- sql - 为什么在动态 SQL 中出现“必须声明标量变量”错误?
- docker - Docker Compose:标志值生成器无效:
- javascript - 实例方法不在NodeJS中的JS模块范围内?
- java - 如何将参数传递给 FeignRequestInterceptor?
- jberet - 在输入文件末尾执行的侦听器
- linux - CentOS 中未设置 Bash 脚本变量
- python - Keras fit_generator() 用于长信号
- ios - 大型项目的 SwiftUI 预览超时