有三种模板参数(形参):
(1)类型参数(这是使用得最多的)
(2)非类型参数
(3)模板的模板参数
类型参数:
类型参数是通过关键字typename或者class引入。关键字后面必须是一个简单的标识符,后面用逗号来隔开下一个参数声明,等号代表接下来的是缺省模板实参,一个封闭的尖括号(>)表示参数化子句的结束。
在模板声明内部,类型参数的作用类似于typedef名称。例如,如果T是一个模板参数,就不能使用诸如class T等形式的修饰名称,即使T是一个要被class类型替换的参数也不可以。
template <typename Allocator>
class List {
class Allocator* allocator; // Error
friend class Allocator; // Error
…
};
非类型参数:
非类型参数表示的是:在编译期或链接期可以确定的常值(模板的模板参数也不属于类型模板参数,但讨论非类型模板参数时,并不考虑模板的模板参数)。这种参数的类型必须是下面的一种:
1. 整型或者枚举类型
2. 指针类型(包含普通对象的指针类型、函数指针类型、指向成员的指针类型)
3. 引用类型(指向对象或者指向函数的引用都是允许的)
所有其他的类型现今都不允许作为非类型参数使用(但在将来很可能会增加浮点数类型)。在某些情况下,非模板参数的声明也可以使用关键字typename:
template <typename T, typename T::Allocator* Allocator>
class List;
这两个参数的区别在于:第1个typename后面是一个简单标识符T,而第2个typename后面是一个受限的名称(即是一个包含双冒号 :: 的名称),它表示Allocator是属于T的一个子类型。
函数和数组类型也可以被指定为非模板参数,但要把它们先隐式地转换为指针类型,这种转型也成为decay:
template <int buf[5]> class Lexer; // buf实际上是一个int* 类型
template <int* buf> class Lexer; // 这是上面的重新声明
非类型模板参数的声明和变量的声明很相似,但它们不能具有static、mutable等修饰符;只能具有const和volatile[1]限定符。但如果这两个限定符限定的如果是最外层的参数类型,编译器将会忽略它们:
template <int const length> class Buffer; //
const在这里是没用的,被忽略了
tempalte <int length> class Buffer; // 和上面等价
最后,非类型模板参数只能是右值:它们不能被取址,也不能被赋值。
模板的模板参数:
模板的模板参数是代表类模板的占位符。它的声明和类模板的声明很类似,但不能使用关键字struct和union:
template <template<typename X> class C> //正确
void f(C<int>* p);
template <template<typename X> struct C> //错误
void f(C<int>* p);
template <template<typename X> union C> //错误
void f(C<int>* p);
在它们声明的作用域中,模板的模板参数的用法和类模板的用法很类似。模板的模板参数的参数(如下面的A)可以具有缺省模板实参。显然,只有在调用时没有指定该参数的情况下,才会应用缺省模板实参:
template <template<typename T, typename A = MyAllocator> class Container>
class Adaptation {
Container<int> storage; // 隐式等同于 Container<int, MyAllocator>
…
};
对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明使用。下面的假设例子说明了这一点:
template <template<typename T, T*> class Buf>
class Lexer {
static char storage[5];
Buf<char, &Lexer<Buf>::storage[0]> buf;
…
};
template <template<typename T> class List>
class Node {
static T* storage; // 错误:模板的模板参数的参数在这里不能被使用
…
};
通常而言,模板的模板参数的参数名称(如上面的例子)并不会在后面被用到。因此,该参数也经常被省略不写,即没有命名。例如,前面的
Adaptation模板的例子可以这样声明:
template <template<typename, typename = MyAllocator> class Container>
class Adaptation {
Container<int> storage; // 隐式等同于 Container<int, MyAllocator>
…
};
缺省模板实参:
现今,只有类模板声明才能具有缺省模板实参。任何类型的模板参数都可以拥有一个缺省实参,只要该缺省实参能够匹配这个参数就可以。显然,缺省实参不能依赖于自身的参数;但可以依赖于前面的参数:
template <typename T, typename Allocator = allocator<T> >
class List; // 就是说,allocator<T>不能依赖于本身参数Allocator,但是能依赖于前面参数T
与缺省的函数调用参数的约束一样,对于任一个模板参数,只有在之后的模板参数都提供了缺省实参的前提下,才能具有缺省模板实参。后面的缺省值通常是在同个模板声明中提供的,但也可以在前面的模板声明中提供。下面的例子说明了这一点:
template <typename T1, typname T2, typename T3,
typename T4 = char, typename T5 = char>
class Quintuple; // 正确
template <typename T1, typname T2, typename T3 = char,
typename T4, typename T5>
class Quintuple; // 正确,根据前面的模板声明,T4和T5已经具有缺省值了
template <typename T1 = char, typname T2, typename T3,
typename T4, typename T5>
class Quintuple; // 错误,T1不能具有缺省实参,因为T2还没有缺省实参
另外,缺省实参不能重复声明:
template <typename T = void>
class Value;
template <typename T = void>
class Value; // 错误,重复出现的缺省实参
----------------------------------------------------------------------------------------------------------------------------------
[1] 关于volatile,它的作用在于限定一个对象可以被外部进程(操作系统、硬件或并发进程等)改变,声明的语法如下:
int volatile nVint;