这个模板函数是在哪里生成的?可以通过 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]),规则是:
- 任何在模板定义点可见的函数声明都会被考虑。
- ADL 在实例化时发现的任何函数声明都会被考虑。
- 如果在程序的其他地方定义了
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。
以下代码无法在我的 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]),规则是:
- 任何在模板定义点可见的函数声明都会被考虑。
- ADL 在实例化时发现的任何函数声明都会被考虑。
- 如果在程序的其他地方定义了
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);
. 时不匹配 - 标记为
It would compile successfully if this function is removed
的行实际上应该没有区别;出于同样的原因,代码仍然会编译失败。
sin >> n
,发布的代码应该编译失败
结论: 在我看来:
- 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。