通过这个示例程序,我在g ++和clang中观察到了不同的行为
foo.h中:
#include <iostream>
namespace Bar
{
class Foo
{
public:
Foo(int x) : _x(x)
{}
int x() const
{
return _x;
}
private:
int _x;
};
}
std::ostream& operator <<(std::ostream& os, const Bar::Foo* foo);
Run Code Online (Sandbox Code Playgroud)
Foo.cpp中
#include <Foo.h>
using namespace std;
ostream& operator <<(ostream& os, const Bar::Foo* foo)
{
return os << foo->x();
}
Run Code Online (Sandbox Code Playgroud)
main.cpp中
#include <iostream>
using namespace std;
template<typename T>
void print(const T& t)
{
cout << t << endl;
}
#include <Foo.h>
int main(int argc, char** argv)
{
Bar::Foo* foo = new Bar::Foo(5);
print(foo);
}
Run Code Online (Sandbox Code Playgroud)
使用clang ++和g ++进行编译会产生不同的结果:
air:~ jose$ clang++ Foo.cpp main.cpp -I.
air:~ jose$ ./a.out
0x7ff9e84000e0
air:~ jose$ g++ Foo.cpp main.cpp -I.
air:~ jose$ ./a.out
5
Run Code Online (Sandbox Code Playgroud)
哪一个是正确的,为什么?
Dav*_*eas 13
在这种特殊情况下,clang ++是正确的.
问题是如何在模板内执行查找print
.在print
调用内部的表达式operator<<
是依赖的.从属名称的名称解析在14.6.4中处理:
在解析依赖名称时,会考虑以下来源的名称:
- 在模板定义点可见的声明.
- 来自实例化上下文(14.6.4.1)和定义上下文中与函数参数类型相关联的名称空间的声明.
在您的情况下,操作符的声明在模板定义时是不可见的,因为之后包含头,并且它不存在于函数参数的任何关联命名空间中(即::std
for ::std::ostream
和::Bar
for ::Bar::Foo*
),所以它不会被发现.
现在,有一个重载::std
需要a void*
,并且可以通过Argument Dependent Lookup找到.的::Bar::Foo*
将被转换为一个void*
和地址将被打印.
也就是说,在符合标准的编译器中.
我忘了在这里添加这个,只留在评论中,但这很重要:
始终在包含它们所应用的类型的同一名称空间中定义应用于类型的运算符.让Argument Dependent Lookup让你觉得它很神奇.它专门用于服务于此特定目的,使用它.