Last Updated on 2022年9月30日
名字查找
- 当我们使用一个名字时,编译器就会向上查找名字的声明语句.变量名的查找和函数名的查找结果有一定区别.
- 变量名查找:最终仅会确定唯一的对象,从内到外碰到的第一个名字将被使用.
- 函数名查找:会先确定搜索域,然后按重载规则选择最有匹配. 搜索域包含一般scope和参数scope.
- 常规搜索域确定:从当前作用域开始,逐层向上查找名字声明,名字首次出现的作用域就是常规搜索域
- 对于函数调用的每个实参
arg_i
,其类型decltype(arg_i)
及其所有基类所在的作用域都加入实参类型搜索域 - 函数名和变量名都是名字,彼此可以相互hide.
重载匹配规则
- 函数重载的基础是
传入参数
的类型,和返回类型无关.对于非引用的形参,const不作为重载的依据. - 首先根据实参类型排除不能产生调用的函数,得到一个可行函数列表. 在可行列表中: 类型转换越少越好,在此前提下, 特化的优先于一般的.
- 注意:
- 仅函数/数组向指针的转换是精确匹配,只要类型不同,都被视作类型转换,例如:派生类指针到基类指针,非const对象绑定到到const T &,都是类型转换
- 类型转换彼此没有优先级,从int 到uint并不比从int 到double要优秀.
- 注意: 很多系统中,整数最多提升到
unsigned int
,这意味着以long
为形参的函数很可能不会被重载系统匹配到.
using namespace XX
using namespace X
:对当前作用域而言,相当于将名字空间X
内的名字全部引入某个外层作用域.using namepsace X
所在语句逐层向外,当某个作用域包含X时,将名字插入这个作用域.using namespace X
引入的名字在外层作用域,所以不会和当前作用域的名字产生冲突,我们甚至可以在后续代码中隐藏引入的名子using namespace X
引入的函数名若被查找到,是在逻辑上的插入位置查找到的,而非当前作用域.因此,当前作用域定义的函数不会和using namespace
引入的函数形成重载.
using namespace
的有效区间仍仅限于所在的作用域.- 在类定义内不能使用
using namespace
namespace Top{
namespace A{
int i=0;
}//namespace A
double i=1;
namespace B{
void fun(){
using namespace A;//将A内的名字引入了Top作用域,此时不会产生重定义错误.
++i;//错误,因为使用了i而触发二义性错误
++Top::A::i;//正确,使用Top::A::i;
++Top::i;//正确,使用Top::i
char i;//正确,隐藏之前所有的i;
}
float i=2;//定义B内的i.
}// namespace B
}// namespace Top
涉及模板时的名字查找:
- Dependent name: 对于一个 Qualified name, 如果对于一个名字的解析依赖了非concrete的类型,那么就称之为dependent name
- Unqualified name : 简单的标识符,不涉及
::
,->
,.
- Qualified name: 与上面相反,例如
a.b,A::b,p_a->b
,这里,b
都是Qualified name. - 标准规定: Unqualified name 一定不是 dependent name. (这主要是为了避免写出冗杂的代码. )
- Unqualified name : 简单的标识符,不涉及
- 模板的2-phase名字查找
- Undependent name 一定是在模板定义的位置解析及绑定的. 称之为phase 1
- Dependent name 一定是在模板示例化的位置解析及绑定的. 称之为phase 2
// x的名字解析
// 1. int foo(){return x;}中, x 是unqualified name,进一步,一定是undependent name, 所以一定是在 phase 1 绑定的
// 2. x的查找一定不会进入Bar<T>,因为在Foo<>的定义阶段,Bar<T>仍然是个未知类型.换言之,对x的查找顺序是:foo的局部作用域-> Foo::作用域 -> 外部作用域, Bar<T>被跳过了
// 3. 注意,上面的流程是因为 x 被标准限制了, x只能是 undependent name.
// y的名字解析
// 1. 对于this->y, y是一个 qualified name ,进一步的, y的值依赖于this,而this不是concrete的,所以y是一个dependent name, 是在phase 2 绑定的
// 2. 相比于x,这里对y的查找会额外进入`Bar<T>`,因为在模板实例化阶段,`Bar<T>`已经是一个concrete的类型了.
template<class T>
struct Bar{
int x=0;
int y=1;
};
template<class T>
class Foo: public Bar<T>{
int foo(){return x;} // error
int bar(){return this->y;}// good
};
补充: dependent name的语法歧义
- 对dependent name的解析有两种情况会出现语法歧义, 此时可能需要用户帮助编译器解决歧义.
- 默认情况下,当B是一个dependent name时,
A::B
,a.B
,pa->B
总是被解释为一个值, 假设记作v_foo
A::B * a
被解释为v_foo * a
A::B<x>
或a.B<x>
或pa->B<x>
被解释为(v_foo < x) >
,这将导致一个语法错误,>
运算符的右侧操作数缺失了.- 相应的
A::B<x>(y)
就不会报错了,因为(v_foo < x) > (y)
是一个合法的表达式
- 相应的
- 当A::B为类型或者函数时, 需要通过
typename
或template
帮助编译器解决歧义.- 例如
typename A::B * a
创建了一个指针,指向A::B
类型 - 例如
A::template B<x>(y)
,产生了一个函数调用,调用了A::B<x>
这个函数.
- 例如