1. 模板模板参数 (Template template parameter)
(1)模板模板参数:即模板的参数也是一个模板。
(2)声明格式形如:
template<typename T, template<typename U> class Container> //参数Container本身是一个模板,其参数为U类型。 class XCLS { private: Container<T> c; };
2. 模板模板参数的实参匹配
(1)实参与Container模板的参数必须完全匹配
template<typename T> using Lst = std::list<T, std::allocator<T>>; XCLS<string, Lst> mylist; //这里的实参不能传入list,因为list模板有两个参数,与Container模板(只有一个参数)不匹配,可以用using来重定义来解决该问题。
(2)当不需要使用模板参数时,参数名可以省略不写。如
template<typename T2, template<typename> class CONT2> //注意,第2个typname后面没有参数名。
【编程实验】用不同容器实现的栈
//test1.cpp
#include <iostream> #include <deque> #include <vector> //1. 不使用using的模板模板参数(注意:第2个参数的类型是模板,而不是一般的类型!!!) // 由于第2个参数CONT是一个容器类的模板,这个模板本身要求两个参数ELEM和ALLOC。所以 // 必须在第2个参数CONT(模板模板参数,也用template声明)中完整的写出,如下: template<typename T, template<typename ELEM, typename ALLOC = std::allocator<ELEM> > class CONT = std::deque> class Stack { private: CONT<T> elems; //CONT<T, allocator<T> > //deque<T, allocator<T> > public: void push(const T& x){elems.push_back(x);} void pop(){elems.pop_back();} T& top(){return elems.back();} bool empty() const {return elems.empty();} //缺省赋值运算符要求=两边具有相同的元素类型。通过定义一个模板形式的赋值运算符, //元素类型不同的两个栈就可实现相互赋值,如: //Stack<int> is1, is2; //Stack<double> ds3; //... //is1 = is2; // OK: 具有相同类型的栈 //is3 = is1; // ERROR:两边栈的类型不同 template<typename T2, template<typename ELEM2, typename ALLOC = std::allocator<ELEM2> > class CONT2> Stack<T, CONT>& operator=(const Stack<T2, CONT2>& rhs) { if((void*)this == (void*)&rhs) return *this; //避免自赋值 Stack<T2, CONT2> tmp(rhs); elems.clear(); while(!tmp.empty()) { elems.push_front(tmp.top()); tmp.pop(); } return *this; } }; //2. 使用using优化模板模板参数(注意:第2个参数的类型是模板,而不是一般的类型!!!) template<typename T> using vec = std::vector<T, std::allocator<T>>; template<typename T> using deq = std::vector<T, std::allocator<T>>; template<typename T, template<typename> class CONT> class Stack2 { private: CONT<T> elems; //CONT<T, allocator<T> > //deque<T, allocator<T> > public: void push(const T& x){elems.push_back(x);} void pop(){elems.pop_back();} T& top(){return elems.back();} bool empty() const {return elems.empty();} //缺省赋值运算符要求=两边具有相同的元素类型。通过定义一个模板形式的赋值运算符, //元素类型不同的两个栈就可实现相互赋值 template<typename T2, template<typename> class CONT2> Stack2<T, CONT>& operator=(const Stack2<T2, CONT2>& rhs) { if((void*)this == (void*)&rhs) return *this; //避免自赋值 Stack2<T2, CONT2> tmp(rhs); elems.clear(); while(!tmp.empty()) { elems.push_front(tmp.top()); tmp.pop(); } return *this; } }; int main() { Stack2<int, vec> stack1; //注意,第2个参数为模板 for(int i=0; i<10;i++){ stack1.push(i); } Stack2<double, vec> stack2; stack2 = stack1; while(!stack2.empty()){ std::cout << stack2.top() << std::endl; stack2.pop(); } return 0; }
3. 模板模板参数+using的妙用
(1)实验要求:写一个能测试各种容器效率的函数或类,传入的参数为类型!
(2)程序的进化
①最初的想法:利用普通函数来实现(天方夜谭)
//最原始的想法:利用普通函数实现 void test_moveable(Container cntr, T elem) { //传入的是对象cntr,却要拿它的类型做文章。(天方夜谭:无法获得Container类型!) Container<T> c; //插入大量的元素 for(long i=0; i<SIZE; ++i) { c.insert(c.end(), T()); //同样elem是对象,无法获得T类型 } output_static_data(T()); //打印T的静态数据 //测试大量元素的拷贝构造 Container<T> c1(c); ... //测试大量元素的移动拷贝构造 Container<T> c2(std::move(c)); ... c1.swap(c2); } //函数调用:接口很漂亮,但无法实现这个目的。因为函数只能传入 //对象或变量,而不能传入类型! test_moveable(list, MyString); //error,不能传入类型! test_moveable(list, MyStrNoMove);
②改进的做法:利用函数模板实现(天方夜谭)
//进化的做法:利用函数模板实现 template<typename Container, typename T> void test_moveable(Container cntr, T elem) { //由于Container是个普通的模板参数,而不是模板模板参数。因此 //不能像下列方式被使用! Container<T> c; //typename Container<T> c; 也不行! //插入大量的元素 for(long i=0; i<SIZE; ++i) { c.insert(c.end(), T()); } output_static_data(T()); Container<T> c1(c); Container<T> c2(std::move(c)); c1.swap(c2); } //函数调用:注意传入的是对象。list()为演示之用,实际上无法这样产生对象。 test_moveable(list(), MyString()); //error,不能传入类型! test_moveable(list(), MyStrNoMove());
③不完美方案:利用迭代器与萃取机
//不完美的进化:利用迭代器与萃取机(可以解决问题,但不完美!因为用到了stl中的迭代器, //这也限制了该函数只能用来测试stl标准库中的容器类) template<typename Container> void test_moveable(Container c) { //由于STL库中的迭代器都包含了元素的类型,可以使用traits萃取出来 typedef typename iterator_traits<typename Container::iterator>::value_type Valtype; //插入大量的元素 for(long i=0; i<SIZE; ++i) { c.insert(c.end(), Valtype()); //利用元素类型产生对象! } output_static_data(*(c.begin());//注意: output_static_data(const T& obj) Container<T> c1(c); Container<T> c2(std::move(c)); c1.swap(c2); } //函数调用:不完美,函数的接口变了。我们希望的是接入类型,而不是对象! test_moveable(list<MyString>()); test_moveable(list<MyStrNoMove>()); test_moveable(vector<MyString>()); test_moveable(vector<MyStrNoMove>());
④终极方案:利用模板模板参数与模板别名
//完美方案(利用模板模板参数+模板别名) //(Container参数是一个模板!注意其声明部分) template<typename T, template<typename> class Container> class XCls { Container<T> c; //注意,Container本身是一个模板,只有一个参数! //而stl中容器类有两个参数(如vector<T, allocator<T>>), //会出现参数不匹配!如何解决:见调用时using的使用! public: XCls(){ for(long i=0; i<SIZE; ++i) { c.insert(c.end(), T()); } output_static_data(T()); Container<T> c1(c); Container<T> c2(std::move(c)); c1.swap(c2); } }; //注意,template不能定义在函数内部! template <typename T> using vec = std::vector<T, std::allocator<T>>; template <typename T> using lst = std::list<T, std::allocator<T>>; //调用(理想的接口:传入类型!) XCls<MyString, vec> c1; //不能写成XCls<MyString, vector> cl,因为Container与 //vector的模板的参数不匹配。 XCls<MyString, lst> c2;