以下代码无法在我的 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);
无法通过参数相关查找找到。由于所有参数都在 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]),规则为:
- 考虑模板定义时可见的任何函数声明。
- 考虑 ADL 在实例化时找到的任何函数声明。
- 如果有
extern
程序中其他地方定义的函数如果在实例化时具有可见声明,则 ADL 会发现这些函数,并且这些额外的声明会影响重载解析,则程序具有未定义的行为(无需诊断)。<
(注意:我的这个答案的第一个版本错误地引用了规则 3,因此得出了错误的结论):
所以查找sin >> n
从调用 load(sin, mon);
实例化成功是因为成员函数 std::istream::operator>>(int&)
被发现。 (搜索还找到了 A&
版本,但重载解析选择了成员函数)。
查找 sin >> n
时出现问题实例化load(sin, poly);
。
根据规则 1,operator>>(std::istream&, A&)
被发现。 (这个函数稍后会被重载解析丢弃,但在这个阶段我们只是执行名称查找)。
根据规则2,ADL命名空间列表为:std
。所以这一步会找到std::operator>>
(各种重载),但不是::operator>>(istream&, Mon&);
因为它不在命名空间 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
和sin >> n
至bar(sin, n);
那么 gcc 和 msvc 正确地拒绝这两个版本。 gcc 甚至给出了与 clang 非常相似的错误消息。
所以我推测该错误可能是 overloaded operator name lookup rules 的错误应用-- 与非操作符名称略有不同,但与此代码示例没有任何关系。
有关这些规则的基本原理和 MSVC 行为的深入文章,请参阅 this excellent article .
关于c++ - 这个模板函数是在哪里生成的呢?可以通过 g++ 编译,但不能在 Visual Studio 中编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56412168/