最近看C++标准中的3.4 name lookup章节,碰巧的是stackoverflow也有人提问,他们对这个问题很疑惑,回答的也并不好。
https://stackoverflow.com/questions/25672745/friend-declarations-is-this-a-bug-in-clang
其中C++标准中$3.4.1章节有一个例子:
typedef int f;
namespace N {
struct A {
friend void f(A &);
operator int();
void g(A a) {
int i = f(a);
// f is the typedef, not the friend
// function: equivalent to int(a)
}
};
}
标准上的注解的意思是f最终是一个typedef, 不会使用friend void f(A&)这个函数。
我自己的理解是:
- 在类中声明的friend函数在普通查找中,是不可见的,除非在类声明之前有正式的声明,也就是说friend声明不能代替正式的声明,这是标准所规定的。既然是不可见的,就不会hide掉那个typedef的f。标准$3.3.10讲的name hiding在这里不起作用。
- 名字查找要是从普通查找开始的,普通查找就是从内到外一层层地找,找到一个名字,立马停止。在这里找到了f是typedef。普通查找完成后才有ADL查找。
- 因为2找到的是typedef,不是一个函数,所以ADL不会介入。ADL是C++11的新规则。如果函数调用是非限定的,例如不是: 某namespace::f(a), 某class::f(a), object_ptr->f(a), 或者object.f(a)之类的调用,并且参数是用户定义类型,就会发起ADL,它把函数调用中的参数所属的namespace也拉进来找这个函数f。所以ADL介入的条件是要么普通查找找不到这个名字或者找到了而且确实是个函数,那么就把参数所属的namespace也拉进来找,最后凑成一个函数集合,做重载解析规则选出最合适的函数。
在这个例子中如果把typedef int f改成 int f()就会发生ADL。普通查找首先会找到int f(),因为是函数,所以用ADL继续找,找到void f(A&),编译器会说"error: void value not ignored as it ought to be"。
这是由于ADL能找到只声明为friend而没有正式声明的函数,标准就是这么规定。并且void f(A&)比int f()更合适,但是void赋值给 i肯定出错,所以编译器报错啦。