我观察到一种奇怪的行为,我无法解释。以下代码(实际代码的简化版本)在 gcc 11.4 中正确编译,并且 MyStruct 的 read() 方法中的参数“val”使用转换构造函数隐式转换为 Dummy。
#include <iostream>
namespace secret_impl_space
{
template <typename T>
struct MyStruct
{
T val;
MyStruct(T v): val(v) {}
// the val below may be implicitly converted to Dummy if operator >> is missing from T
virtual std::istream& read(std::istream& i) { i >> val; return i; }
};
struct Dummy { template<typename T> Dummy(const T& v){}; };
inline std::istream& operator>>(std::istream& i, const Dummy& v)
{
std::cout << "Using Dummy" << std::endl; return i;
}
}
struct A {int a;};
int main()
{
A aa{1};
secret_impl_space::MyStruct<A>* test (new secret_impl_space::MyStruct<A>(aa));
return 0;
}
然而,我发现较新的 gcc 版本(从 12 开始)会出现以下编译错误(经 godbolt 确认):
no match for ‘operator>>’ (operand types are ‘std::istream’ {aka ‘std::basic_istream<char>’} and ‘A’)
最奇怪的是,如果我执行以下两件事之一,代码可以在任何gcc 版本上正确编译:
- 删除命名空间“secret_impl_space”
- 从 read() 方法中删除虚拟说明符。
有人能解释一下这种现象吗?我真的很困惑。
注意:只是为了给读者一些背景信息,在原始代码中,MyStruct 是类型擦除容器(如 boost::any)的实现部分 - 这就是为什么它有一个虚拟 >> 方法,用于重载类型擦除基接口中的方法。定义 Dummy 类的整个想法是允许将类型擦除容器也用于某些没有 >> 运算符的类型 - 生成运行时警告而不是编译器错误。在我看来这非常糟糕,但我没有写这个,当我发现问题时它已经存在了。出于某种原因(羞耻?),所有这些机制都“隐藏”在命名空间内。
这无论如何都不应该起作用。根据 ADL 规则(又称错误的 Koenig 规则),对于模板上下文中尚未声明匹配函数的函数调用,名称查找将推迟到实例化时。
std::basic_istream<char>
在实例化时,对于带有参数且<global namespace>::A
仅包含命名空间的调用std
,secret_impl_space
应该考虑进行查找,考虑未声明的转换或在全局命名空间(其中定义了 A)中查找是不正确的。查找发生在实例化时。错误列出了这些类型作为推理方式。对于模板的虚函数
在 C++11 之前,这是实现定义的。否则,模板的内联成员函数的实例化点与 ODR 使用点位于同一位置(对于未使用的函数,可能根本不会发生这种情况)。发布代码中的运算符定义位于这两者之间的“间隙”内。
operator=
可以通过将 Dummy 的声明移到的声明上方来修复该代码MyStruct
。它可以作为原型出现:行为的改变可能是由于修复了 ADL 实施中的一些缺陷。
原始代码也被 clang 拒绝。某些 MSVC 版本可能会接受它,因为从全局命名空间开始的查找行为不符合要求。