有时,让模板参数本身成为模板是很有用的,我们将继续以stack类模板作为例子,来说明模板的模板参数的用途。在Stack的例子中,如果要使用一个和缺省值不同的内部容器,程序员必须两次指定元素类型。也就是说,为了指定内部容器的类型,你需要同时传递容器的类型和它所含元素的类型。如下:
Stack
然而,借助于模板的模板参数,你可以只指定容器的类型而不需要指定所含元素的类型,就可以声明这个Stack类模板:
Stack
为了获得这个特性,你必须把第2个模板参数指定为模板的模板参数。那么,stack的声明应该如下:
//stack7decl.h
#ifndef STACK7DECL_H
#define STACK7DECL_H
template class CONT = std::deque>
class Stack {
private:
CONT elems; //
public:
void push(T const &elem);//添加元素
void pop();//删除元素
T top() const;//返回栈顶元素
bool empty() const {//返回是否为空栈
return elems.empty();
}
};
#endif // STACK7DECL_H
不同之处在于,第2个模板参数现在被声明为一个类模板:
template
缺省值也从std::deque
CONT
这也是这个例子比较特别的地方:使用第1个模板参数作为第2个模板参数的实例化类型。一般地,你可以使用类模板内部的任何类型来实例化模板的模板参数。我们前面提过:作为模板参数的声明,通常可以使用typename来替换关键字class。然而,上面的CONT是为了定义一个类,因此只能使用关键字class。因此,下面的程序是正确的:
template
class Stack {
...
};
而下面的程序却是错误的:
template
class Stack {
...
};
由于在这里我们并不会用到“模板的模板参数”的模板参数(即上面的ELEM),所以你可以把该名称省略不写:
template
class Stack {
...
};
另外,还必须对成员函数的声明进行相应的修改。你必须把第2个模板参数指定为模板的模板参数;这同样适用于成同函数的实现。例如,成员函数push()的实现如下:
template
void Stack
{
elems.push_back(elem); //把elem的拷贝附加到末端
}
还有一点需要知道:函数模板并不支持模板的模板参数。
//stack7.h
#ifndef STACK7_H
#define STACK7_H
#include
#include
#include "stack7decl.h"
template class CONT>
void Stack::push(T const &elem)
{
elems.push_back(elem); //添加一个元素
}
template class CONT>
void Stack::pop()
{
if(elems.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back();
}
template class CONT>
T Stack::top() const
{
if(elems.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back();
}
#endif // STACK7_H
//stack7test.cpp
#include
#include
#include
#include
#include "stack7.h"
int main()
{
try {
Stack intStack;
Stack floatStack;
intStack.push(42);
intStack.push(5);
floatStack.push(9.8f);
std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
floatStack.pop();
std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
floatStack.pop();
std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
floatStack.pop();
} catch (std::exception const &ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
Stack vStack;
vStack.push(42);
vStack.push(7);
std::cout << "vStack.top()===" << vStack.top() << std::endl;
vStack.pop();
return 0;
}
模板的模板实参匹配
如果你尝试使用新版本的Stack,你会获得一个错误信息:缺省值std::deque和模板的模板参数CONT并不匹配。
对于这个结果,你或许会觉得很这诧异,但问题在于:模板的模板实参(譬如这里的std::deque)是一个具有参数A的模板,它将替换模板的模板参数(譬如这里的CONT),而模板的模板参数是一个具有参数B的模板:匹配过程要求参数A和参数B必须完全匹配;然而在这里,我们并没有考虑模板的模板实参数缺省模板参数,从而也就使B缺少了这些缺省参数值 ,当前就不能获得精确的匹配。在这个例子中,问题在于标准库中的std::deque模板还具有另一个参数:即第2个参数(也就是所谓的内存分配器allocator),它有一个缺省值,但在匹配std::deque的参数和CONT的参数时,我们并没有考虑这个缺省值。然而,解决办法总是有的,我们可以重写类的声明,让CONT的参数期待的是具有两个模板参数的容器:
template
class Stack {
private:
CONT
...
};
同样,你可以略去ALLOC不写,因为实现中不会用不对劲它。
现在,Stack模板(包括为了能够在不同元素类型的栈之间实现相互赋值而定义的成员模板)的最终版本应该如下:
//stack8.h
#ifndef STACK8_H
#define STACK8_H
#include
#include
#include
template >
class CONT = std::deque>
class Stack {
private:
CONT elems;
public:
void push(T const &elem);
void pop();
T top() const;
bool empty() const {
return elems.empty();
}
template>
class CONT2>
Stack& operator= (Stack const &elem);
};
template class CONT>
void Stack::push(T const &elem)
{
elems.push_back(elem);
}
template class CONT>
void Stack::pop()
{
if(elems.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back();
}
template class CONT>
T Stack::top() const
{
if(elems.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back();
}
template class CONT>
template class CONT2>
Stack&
Stack::operator= (Stack const &op2)
{
if((void*)this == (void*)&op2){
return *this;
}
Stack tmp(op2);
elems.clear();
while(!tmp.empty()) {
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
#endif // STACK8_H
//stack8test.cpp
#include
#include
#include
#include
#include "stack8.h"
int main()
{
try {
Stack intStack;
Stack floatStack;
intStack.push(42);
intStack.push(9);
floatStack.push(7.9f);
floatStack = intStack;
std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
floatStack.pop();
std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
floatStack.pop();
std::cout << "floatStack.top()===" << floatStack.top() << std::endl;
floatStack.pop();
} catch (std::exception const &ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
Stack vStack;
vStack.push(42);
vStack.push(8);
std::cout << "vStack.top()=======" << vStack.top() << std::endl;
vStack.pop();
return 0;
}
模板的模板参数是要求编译器符合标准的新特性之一;因此,这个程序可以作为评价你的编译器模板特性方面符合标准的尺度。
关于更深入的讨论和这方面的例子,详见8.2.3小节和15.1.6小节。