[C++ Template]深入模板--深入模板基础

目录

8 深入模板基础

8.1 参数化声明

8.1.1 虚成员函数

8.1.2 模板的链接

8.2 模板参数

8.2.1 类型参数

8.2.2 非类型参数

8.2.3 模板的模板参数

8.2.4 缺省模板实参

8.3 模板实参

8.3.1 函数模板实参

8.3.2 类型实参

8.3.3 非类型实参

8.3.4 模板的模板实参

8.3.5 实参的等价性

8.4 友元

8.4.1 友元函数

8.4.2 友元模板


 

8 深入模板基础

在这一章里, 我们将深入回顾之前所提到的一些基础知识: 模板的声明、 模板参数的约束以及模板实参的约束等。
 

8.1 参数化声明

C++现今支持两种基本类型的模板: 类模板和函数模板,这个分类实际上还包含成员模板。这些模板的声明和普通类与普通函数的声明很相似, 唯一的区别就是模板声明需要引入一个参数化子句:

template<...parameters here...>

联合(UNION)模板也是允许的(往往被看作类模板的一种):

template 
union AllocChunk 
{
	T object;
	unsigned char bytes[sizeof(T)];
};

和普通函数声明一样, 函数模板声明也可以具有缺省调用实参:

template 
void report_top(Stack const&, int number = 10);

template 
void fill(Array*, T const& = T());//对于基本类型,T()为0

后一个声明说明了: 缺省调用实参可以依赖于模板参数。 显然, 当fill()函数被调用时, 如果提供了第2个函数调用参数的话, 就不会实例化这个缺省实参。 这同时说明了: 即使不能基于特定类型T来实例化缺省调用实参, 也可能不会出现错误。 例如:

class Value {
public:
	Value(int); //不存在缺省构造函数
};

void init(Array* array)
{
	Value zero(0);
	fill(array, zero); //正确: 没有使用 =T()
	fill(array); //错误: 使用了=T(), 但当T =Value时缺省构造函数无效
}

除了两种基本类型的模板之外, 还可以使用相似的符号来参数化其他的3种声明。 这3种声明分别都有与之对应的类模板成员的定义:
(1) 类模板的成员函数的定义。
(2) 类模板的嵌套类成员的定义。
(3) 类模板的静态数据成员的定义。

 

8.1.1 虚成员函数

成员函数模板不能被声明为虚函数。 这是一种需要强制执行的限制。因为虚函数调用机制的普遍实现都使用了一个大小固定的表, 每个虚函数都对应表的一个入口。然而, 成员函数模板的实例化个数, 要等到整个程序都翻译完毕才能够确定, 这就和表的大小(是固定的) 发生了冲突。相反, 类模板的普通[6]成员可以是虚函数, 因为当类被实例化之后, 它们的个数是固定的

template 
class Dynamic 
{
public:
	virtual ~Dynamic(); //OK: 每个Dynamic只对应一个析构函数
	template 
	virtual void copy(T2 const&);
	//错误: 在确定Dynamic实例的时候, 并不知道copy()的个数
};

 

8.1.2 模板的链接

每个模板都必须有一个名字, 而且在它所属的作用域下, 该名字必须是唯一的; 除非函数模板可以被重载 。 特别是, 类模板不能和另外一个实体共享一个名称, 这一点和class类型是不同的:

int C;
class C;//正确: 类名称和非类名称位于不同的名字空间

int X;
template 
class X; //错误: 和变量X冲突

struct S;
template 
class S; //错误, 和struct S冲突

模板名字是具有链接的, 但它们不能具有C链接。 但我们在大多数情况下所说的是标准的链接, 同时也存在非标准的链接, 它们可以具有一个依赖于实现的含义(然而, 我们还没发现有用于支持非标准模板名字链接的编译器实现):

extern"C++"template 
void normal(); //这是缺省情况, 上面的链接规范可以不写

extern"C"template 
void invalid(); //错误的: 模板不能具有C链接

extern"Xroma"template 
void xroma_link(); //非标准的, 但某些编译器将来可能支持与Xroma语言的链接兼容性

模板通常具有外部链接。 唯一的例外就是前面有 static 修饰符的名字空间作用域下的函数模板

template 
void external(); //作为一个声明, 引用位于其他文件的、 具有
//相同名称的实体; 即引用位于其他文件的external()函数模板, 也称前置声明

template 
static void internal(); //与其他文件中具有相同名称的模板没有关系即不是外部链接

因此我们知道(由于外部链接) : 不能在函数内部声明模板。

 

8.2 模板参数

现今存在3种模板参数:
(1) 类型参数(它们是使用得最多的) 。
(2) 非类型参数。
(3) 模板的模板参数。

从前面知道, 模板声明要引入参数化子句, 模板参数就是在该子句中声明的。 这类声明可以把模板参数的名称省略不写(就是说, 在后面不会引用该名称的前提下) :

template  //省略不写。
class X;

显然, 如果在模板声明后面需要引用参数名称, 那么这些参数名称是一定要写上的。 另外, 在同一对尖括号内部, 位于后面的模板参数声明可以引用前面的模板参数名称(但前面的不能引用后面的)

template  class Buf>//声明中都使用了第1个参数T
class Structure;


8.2.1 类型参数

类型参数是通过关键字typename或者class引入的: 它们两者几乎是等同的。 关键字后面必须是一个简单的标识符, 后面用逗号来隔开下一个参数声明, 等号(=) 代表接下来的是缺省模板实参, 一个封闭的尖括号(>) 表示参数化子句的结束。

在模板声明内部, 类型参数的作用类似于typedef(类型定义) 名称。 例如, 如果T是一个模板参数, 就不能使用诸如class T等形式的修饰名称, 即使T是一个要被class类型替换的参数也不可以:

template 
class List 
{
	class Allocator* allocator; //错误
	...
}

 

8.2.2 非类型参数

非类型参数表示的是: 在编译期或链接期可以确定的常值。 这种参数的类型(换句话说, 就是这些常值的类型) 必须是下面的一种:
•整型或者枚举类型。
•指针类型(包含普通对象的指针类型、 函数指针类型、 指向成员的指针类型) 。
•引用类型(指向对象或者指向函数的引用都是允许的) 。

所有其他的类型现今都不允许作为非类型参数使用(但是在将来很可能会增加浮点数类型) 。或许会令你惊讶的是, 在某些情况下, 非模板参数的声明也可以使用关键字typename:

template  //非类型参数
class List;

这两种参数的区分很容易: 第 1 个 typename 的后面是一个简单标识符 T, 而第 2 个typename的后面是一个受限的名称。

函数和数组类型也可以被指定为非模板参数, 但要把它们先隐式地转换为指针类型, 这种转型也称为decay

template class Lexer; //buf实际上是一个int*类型
template class Lexer; //正确: 这是上面的重新声明

非类型模板参数的声明和变量的声明很相似, 但它们不能具有static、 mutable等修饰符; 只能具有const和volatile限定符。 但如果这两个限定符限定的如果是最外层的参数类型, 编译器将会忽略它们:

template class Buffer;//这里的const是没用的, 被忽略了
template class Buffer; //和上面是等同的

最后, 非类型模板参数只能是右值: 它们不能被取址, 也不能被赋值。

 

8.2.3 模板的模板参数

模板的模板参数是代表类模板的占位符。 它的声明和类模板的声明很类似, 但不能使用关键字struct和union:

template  class C> //正确
void f(C* p);
template  struct C> //错误
void f(C* p);
template  union C> //错误
void f(C* p);

模板的模板参数的参数(如下面的A) 可以具有缺省模板实参。 显然, 只有在调用时没有指定该参数的情况下, 才会应用缺省模板实参:

template