有三种模板参数(形参):
(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>通常而言,模板的模板参数的参数名称(如上面的例子)并不会在后面被用到。因此,该参数也经常被省略不写,即没有命名。例如,前面的 Adaptation模板的例子可以这样声明:
class Lexer {
static char storage[5];
Buf<char, &Lexer<Buf>::storage[0]> buf;
…
};
template <template<typename T> class List>
class Node {
static T* storage; // 错误:模板的模板参数的参数在这里不能被使用
…
};
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;
参考:http://hi.baidu.com/b1ghost/blog/item/daf6b7445f10392fcffca392.html
模板实参是指:在实例化模板时,用来替换模板参数的值。我们可以使用下面几种不同的机制来确定这些值。
类型实参:
模板的类型实参是一些用来指定模板类型参数的值。我们平时使用的大多数类型都可以被用作模板的类型实参,但有两种情况例外:
1. 局部类和局部枚举(换句话说,指在函数定义内部声明的类型)不能作为模板的类型实参。
2. 未命名的class类型或者未命名的枚举类型不能作为模板的类型实参(然而,通过typedef声明给出的未命名类和枚举是可以作为模板类型实参的)。
下面的例子很好地说明了这两种例外情况:
通常而言,尽管其他的类型都可以用作模板实参,但前提是该类型替换模板参数之后获得的构造必须是有效的。template <typename T> class List {
…
};typedef struct {
double x, y, z;
} Point;typedef enum { red, green, blue } *ColorPtr;
int main()
{
struct Association // 局部类型
{
int* p;
int* q;
};
List<Association*> error1; // 错误:模板实参中使用了局部类型
List<ColorPtr> error2; // 错误:模板实参中使用了未命名的类型,因为typedef定义
// 的是*ColorPtr,不是ColorPtr
List<Point> ok; // 正确:通过使用typedef定义的未命名类型
}
tempalte <typename T>非类型实参:
void clear (T p)
{
*p = 0; // 要求单目运算符*可以用于类型T
}
int main()
{
int a;
clear(a); // 错误:int类型并不支持但不运算符*
}
非类型模板实参是那些替换非类型参数的值。这个值必须是以下几种中的一种:
1. 某一个具有正确类型的非类型模板参数
2. 一个编译期整型常数(或枚举值)。这只有在参数类型和值的类型能够进行匹配,或者值的类型可以隐式地转换为参数类型(例如,一个char值可以作为int参数的实参)的前提下,才是合法的。
3. 前面有单目运算符&(即取址)的外部变量或者函数的名称。对于函数或数据变量,&运算符可以省略。这类模板实参可以匹配指针类型的非类型参数。
4. 对于引用类型的非类型模板参数,前面没有&运算符的外部变量和外部函数也是可取的。
5. 一个指向成员的指针常量。换句话说,类似&C::m的表达式,其中C是一个class类型,m是一个非静态成员(成员变量或者函数)。这类实参只能匹配类型为“成员指针”的非类型参数。
当实参匹配“指针类型或者引用类型的参数”时,用户定义的类型转换(例如单参数的构造函数和重载类型转换运算符)和由派生类到基类的类型转换,都是不会被 考虑的;即使在其他情况下,这些隐式类型转换时有效的,但在这里都是无效的。隐式类型转换的唯一应用只能是:给实参加上关键字const或者volatile。
下面是一些有效的非类型模板实参的例子:
tempalte <typename T, T nontype_parm>模板实参的一个普遍约束是:在程序创建的时候,编译器或者链接器要能够确定实参的值。如果实参的值要等到程序运行时才能够确定(譬如,局部变量的地址),就不符合“模板是在程序创建的时候进行实例化”的概念了。
class C;
C<int, 33>* c1; // 整型
int a;
C<int*, &a>* c2; // 外部变量地址
void f();
void f(int);
C<void(*)(int), f>* c3; // 函数名称。在这个例子中,重载解析会选择f(int),f前面的&隐式省略了
class X {
public:
int n;
static bool b;
};
C<bool&, X::b>* c4; // 静态类成员是可取的变量(和函数)名称
C<int X::*, &X::n>* c5; // 指向成员的指针常量
template <typename T>
void templ_func();
C<void(), &templ_func<double> >* c6; // 函数模板实例同时也是函数
另一方面,有些常值不能作为有效的非类型实参,这也许会令人觉得很诧异。这些常值包括:
· 空指针常量
· 浮点型值
· 字符串
有关字符串的一个问题就是:两个完全等同的字符串可以存储在两个不同的地址中。在此,我们用一种(很笨的)解决方法来表达需要基于字符串进行实例化的模板:引入一个额外的变量来存储这个字符串。
template <char const* str>
class Message;
extern char const hello[] = "Hello World!";
Message<hello>* hello_msg;
可以看到,我们使用了关键字extern。因为如果不使用这个关键字,上面的const数组变量具有内部链接。下面给出一些错误的例子:
template <typename T, T nontype_parm>
class C;
class Base {
public:
int i;
} base;
class Derived: public Base {
} derived_obj;
C<Base*, &derived_obj>* err1; // 错误,这里不会考虑派生类到基类的类型转换
C<int&, base.i>* err2; // 错误,域运算符(.)后面的变量不会被看成变量
int a[10];
C<int*, &a[0]>* err3; // 错误,单一数组元素的地址并不是可取的
模板的模板实参:
“模板的模板实参”必须是一个类模板,它本身具有参数,该单数必须精确匹配它“所替换的模板的模板参数”本身的参数。在匹配过程中,“模板的模板实参”的 缺省模板实参将不会考虑(但是如果“模板的模板参数”具有缺省实参,那么模板的实例化过程是会考虑模板的模板参数的缺省实参的)。
这段看起来很绕,看下面例子能更易理解。
#include <list>这里的问题是:标准库中的 std::list模板具有两个参数,它的第2个参数(我们称之为内存配置器 allocator)具有一个缺省值;但当我们匹配 std::list和 Container参数时,事实上并不会考虑这个缺省值(即认为缺省值并不存在)。
// List 的声明:
// namespace std {
// template <typename T, typename Allocator = allocator<T> >
// class list;
// }
tempalte <typename T1, typename T2,
tempalte<typename> class Container>
// Container期望的是只具有一个参数的模板
class Relation {
public:
…
private:
Container<T1> dom1;
Container<T2> dom2;
};
int main()
{
Relation<int, double, std::list> rel; // 错误,std::list是一个具有两个参数的模板
…
}
有时,我们可以通过给模板的模板参数添加一个具有缺省值的参数,来解决这个问题。在前面的例子中,我们可以这样改写Relation模板:
#include <memory>显然,这并不是一个令人满意的解决方案,但它可以让标准容器模板得到使用。另外我们注意到了一个事实:从语法上讲,只有关键字 class才能被用来声明模板的模板参数;但是这并不意味着只有用关键字 class声明的类模板才能作为它的替换实参。实际上,“ struct模板”、“ union模板”都可以作为模板的模板参数的有效实参。这和我们前面所提到的事实很相似:对于用关键字 class声明的模板类型参数,我们可以用(满足约束的)任何类型作为它的替换实参。
tempalte <typename T1, typename T2,
tempalte<typename T, typename = std::allocator<T> > class Container>
// Container现在就能够接受一个标准容器模板了
class Relation {
public:
…
private:
Container<T1> dom1;
Container<T2> dom2;
};
参考:http://hi.baidu.com/b1ghost/blog/item/daf6b74458043e2fcffca38e.html