有时,让模板参数本身成为模板是很有用的。下面我们以stack类作为例子,来说明模板的模板参数的问题
在stack的例子中,如果要使用一个和缺省值不同的内部容器,程序员必须两次指定元素类型。也就是说,为了指定内部容器的类型,你需要同时传递容器的类型和它所含元素的类型。如下:
Stack<int, std::vector<int>> vStack; // 使用vector的int栈
然而,借助于模板的模板参数,你可以只指定容器的类型而不需要指定所含元素的类型,就可以声明这个Stack类模板:
Stack<int, std::vector> vStack; // 使用vector的int栈
为了获得这个特性,必须把第2个模板参数指定为模板的模板参数,那么,stack的声明如下:
template <typename T,
template <typename ELEM> class CONT = std::deque >
class Stack {
private:
CONT<T> elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
不同之处在于,第二个模板参数现在被声明为一个类模板:
template<typename ELEM> class CONT;
缺省值也从std::deque<T>
变成了std::deque
。在使用时,第二个参数必须是一个类模板,并且由第一个模板参数传递进来的类型实例化:
CONT<T> elems;
可以使用类模板内部的任何类型来实例化模板的模板参数
一般来说,可以使用typename来替换class。然而,上面的CONT是为了定义一个类,因此只能使用class
。因此,下面的程序是正确的:
template <typename T,
template <typename ELEM> class CONT = std::deque >
class Stack {
而下面的程序是错误的:
template <typename T,
template <typename ELEM> typename CONT = std::deque >
class Stack {
由于在这里我们并不会用到模板的模板参数的模板参数(也就是上面的ELEM),所以可以省略不写:
template <typename T,
template <typename> class CONT = std::deque >
class Stack {
另外,还必须对成员函数的声明进行相应的修改,你必须把第二个模板参数指定为模板的模板参数;这同样适用于成员函数的实现,例如:
template<typename T, template<typename>class CONT>
vois Stack<T, CONT>::push(T const & elem){
elems.push_back(elem);
}
另外:函数模板并不支持模板的模板参数
完整的例子:
template <typename T,
template <typename ELEM> class CONT = std::deque >
class Stack {
private:
CONT<T> elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
};
template <typename T, template <typename> class CONT>
void Stack<T,CONT>::push (T const& elem)
{
elems.push_back(elem); // append copy of passed elem
}
template<typename T, template <typename> class CONT>
void Stack<T,CONT>::pop ()
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back(); // remove last element
}
template <typename T, template <typename> class CONT>
T Stack<T,CONT>::top () const
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back(); // return copy of last element
}
int main()
{
try {
Stack<int> intStack; // stack of ints
Stack<float> floatStack; // stack of floats
// manipulate int stack
intStack.push(42);
intStack.push(7);
// manipulate float stack
floatStack.push(7.7);
// print float stack
std::cout << floatStack.top() << std::endl;
floatStack.pop();
std::cout << floatStack.top() << std::endl;
floatStack.pop();
std::cout << floatStack.top() << std::endl;
floatStack.pop();
}
catch (std::exception const& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
// stack for ints using a vector as an internal container
Stack<int,std::vector> vStack;
//...
vStack.push(42);
vStack.push(7);
std::cout << vStack.top() << std::endl;
vStack.pop();
}
模板的模板参数匹配
如果你尝试使用新版本的Stack,你会获得一个错误信息:缺省值std::deque和模板的模板参数CONT不匹配。问题在于:模板的模板实参(这里是std::deque)是一个具有参数A的模板,它将替换模板的模板参数(这里是CONT),而模板的模板参数是一个具有参数B的模板:匹配过程要求参数A和参数B必须完全匹配,然而在这里,我们并没有考虑模板的模板实参的缺省模板参数,从而也就使B中缺少了这些缺省参数值,当然就不能获取精确的匹配
在这里,问题在于标准库的std::deque模板还具有另一个参数:即第2个参数(也就是内存分配器allocator),它有一个缺省值,但是在匹配std::deque的参数和CONT的参数时,我们并没有考虑这个缺省值。
解决方法:重写类的声明,让CONT的参数期望是具有两个模板参数的容器:
template <typename T,
template<typename ELEM,
typename ALLOC = std::allocator<ELEM>>
class CONT = std::deque>
class Stack{
private :
CONT<T>elem;
}
同样,可以省去ALLOC不写,因为没有用到它:
现在,Stack模板(包括为了能够在不同元素类型之间的栈之间相互赋值而定义的成员模板)的最终版本如下:
#ifndef STACK_HPP
#define STACK_HPP
#include <deque>
#include <stdexcept>
#include <memory>
template <typename T,
template <typename ELEM,
typename = std::allocator<ELEM> >
class CONT = std::deque>
class Stack {
private:
CONT<T> elems; // 保存元素的容器
public:
void push(T const&); //压入元素
void pop(); //弹出元素
T top() const; //返回栈顶元素
bool empty() const { //返回栈是否为空
return elems.empty();
}
// 使用元素类型为T2的栈对原栈赋值
template<typename T2,
template<typename ELEM2,
typename = std::allocator<ELEM2>
>class CONT2>
Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);
};
template <typename T, template <typename,typename> class CONT>
void Stack<T,CONT>::push (T const& elem)
{
elems.push_back(elem); //附加传入元素的拷贝
}
template<typename T, template <typename,typename> class CONT>
void Stack<T,CONT>::pop ()
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back(); // 删除末尾元素
}
template <typename T, template <typename,typename> class CONT>
T Stack<T,CONT>::top () const
{
if (elems.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems.back(); //返回末端元素的拷贝
}
template <typename T, template <typename,typename> class CONT>
template <typename T2, template <typename,typename> class CONT2>
Stack<T,CONT>&
Stack<T,CONT>::operator= (Stack<T2,CONT2> const& op2)
{
if ((void*)this == (void*)&op2) { // 赋值给自己吗?
return *this;
}
Stack<T2,CONT2> tmp(op2); //创建一个赋值的拷贝
elems.clear(); // 删除现有元素
while (!tmp.empty()) { //拷贝所有元素
elems.push_front(tmp.top());
tmp.pop();
}
return *this;
}
#endif // STACK_HPP
使用:
int main()
{
try {
Stack<int> intStack;
Stack<float> floatStack;
intStack.push(42);
intStack.push(7);
floatStack.push(7.77);
floatStack = intStack;
std::cout << floatStack.top() << "\n";
floatStack.pop();
std::cout << floatStack.top() << "\n";
floatStack.pop();
std::cout << floatStack.top() << "\n";
floatStack.pop();
} catch (std::exception const & ex) {
std::cerr << "Exception:" << ex.what() << "\n";
}
Stack<int, std::vector> vStack;
vStack.push(42);
vStack.push(7);
std::cout << "vector: " <<vStack.top() << "\n";
}