目录
9.2.1 Argument-Dependent Lookup(ADL)
9.3.4 using-declaration中的依赖型名称
9 模板中的名称
实现者的角度出发, 就名称而言, C++是一门相当棘手的语言。 譬如 C++语句x*y, 如果x和y都是变量的名称, 那么这个语句代表一个乘积; 但如果x是一个类型的名称, 那么这个语句声明y是一个指向类型为x的实体的指针。
这个小例子说明了C++(与C一样) 是一种上下文相关语言: 对于C++的一个构造, 我们不能脱离它的上下文来理解它。 但这又和模板有哪些联系呢? 事实上, 模板也是一种构造, 它必须处理多种上下文相关信息: (1) 模板出现的上下文; (2) 模板实例化的上下文; (3) 用来实例化模板的模板实参的上下文。 因此, 在C++中, 小心处理各种(上下文的) 名称的做法就不足为奇了。
9.1 名称分类
C++使用了多种多样的方法来对名称进行分类。 为了有助于理解名称的众多术语。 幸运的是, 你只需要熟悉下面两个主要的命名概念, 就可以深入理解大多数模板话题:
(1) 如果一个名称使用域解析运算符(即: : ) 或者成员访问运算符(即 .或->) 来显式表明它所属的作用域, 我们就称该名称为受限名称。 例如, this->count就是一个受限名称, 而count则不是(即使前面没有符号, count实际上引用的也是一个类成员) 。
(2) 如果一个名称(以某种方式) 依赖于模板参数, 我们就称它为依赖型名称。 例如, 如果T是一个模板参数, std::vector<T>::iterator就是一个依赖名称; 但如果T是一个已知的typedef(类型定义, 例如int) , 那么std::vector<T>::iterator就不是一个依赖名称。
9.2 名称查找
受限名称的名称查找是在一个受限作用域内部进行的, 该受限作用域由一个限定的构造所决定。 如果该作用域是一个类, 那么查找范围可以到达它的基类; 但不会考虑它的外围作用域。 下面的例子说明了这些基本原则:
int x;
class B
{
public:
int i;
};
class D : public B {
};
void f(D* pd)
{
pd->i = 3; //找到B::i
D::x = 2; //错误: 并不能找到外围作用域中的::x
}
非受限名称的查找则相反, 它可以(由内到外) 在所有外围类中逐层地进行查找(但在某个类内部定义的成员函数定义中, 它会先查找该类和基类的作用域, 然后才查找外围类的作用域) , 这种查找方式也被称为普通查找。 下面的例子说明普通查找的一些基本概念:
extern int count; //(1)
int lookup_example(int count) //(2)
{
if (count < 0)
{
int count = 1; //(3)
lookup_example(count); //非受限的count将会引用(3)
}
return count + ::count; //第1个(非受限的)count引用(2),第2个(受限的) count引用(1)
}
对于非受限名称的查找, 最近增加了一项新的查找机制——除了前面的普通查找——就是说非受限名称有时可以使用依赖于参数的查找(argument-dependent lookup, ADL) 。 在阐述ADL的细节之前, 让我们先通过前面的max()模板来说明这种机制的动机:
template <typename T>
inline T const& max(T const& a, T const& b){
return a < b ? b : a;
}
假设我们现在要让“在另一个名字空间中定义的类型”使用这个模板函数:
namespace BigMath
{
class BigNumber
{
...
};
bool operator < (BigNumber const&, BigNumber const&);
...
}
using BigMath::BigNumber;
void g(BigNumber const& a, BigNumber const& b)
{
...
BigNumber x = max(a, b);
...
}
问题是 max()模板并不知道 BigMath 名字空间, 因此普通查找也找不到“应用于BigNumber 类型值的 operator<”。 如果没有特殊规则的话,这种限制将会大大减少 C++名字空间中模板的应用。 ADL正是这个特殊规则, 也正是解决这种限制的关键之处。
9.2.1 Argument-Dependent Lookup(ADL)
ADL只能应用于非受限名称。 在函数调用中, 这些名称看起来像是非成员函数。 对于成员函数名称或者类型名称, 如果普通查找能找到该名称, 那么将不会应用ADL。 如果把被调用函数的名称(如max) 用圆括号括起来, 也不会使用ADL。否则, 如果名称后面的括号里面有(一个或多个) 实参表达式, 那么ADL将会查找这些实参的associated class(关联类) 和associatednamespace(关联名字空间) ,稍后给出二者的定义。但从直观上来看, 我们可以认为是: 与给定类型直接相关的所有namespace和class。 例如, 如果某一类型是指向class X的指针, 那么它的associated class和associated
namespace会包含X和X所属的任何class和namespace。
对于给定类型, 对于由associated class和associated namespace所组成的集合的准确定义, 我们可以通过下列规则来确定:
•对于基本类型, 该集合为空集。
•对于指针和数组类型, 该集合是所引用类型(譬如对于指针而言, 它所引用的类型是“指针所指对象”的类型) 的associated class和associated namespace。
•对于枚举类型, associated namespace指的是枚举声明所在的namespace。 对于类成员, associated class指的是它所在的类。
•对于class类型(包含联合类型) , associated class集合包括: 该class类型本身、 它的外围类型、 直接基类和间接基类。 associated namespace 集合是每个 associated class 所在的namespace。 如果这个类是一个类模板实例化体, 那么还包含: 模板类型实参本身的类型、 声明模板的模板实参所在的class和namespace。
•对于函数类型, 该集合包括所有参数类型和返回类型的associated class 和associated namespace。
对于类X的成员指针类型, 除了包括成员相关的associated anmespace和associated calss, 该集合还包括与X相关的associated namespace和associated class。
至此,