template的基本使用在C++笔记(3)中说明,这里去探讨一下template的一些高级用法
@
一、概念
利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板是一种对类型进行参数化的工具;
通常有两种形式:函数模板和类模板;
函数模板针对仅参数类型不同的函数;
类模板针对仅数据成员和成员函数类型不同的类。
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
二、通式
1、函数模板通式
template <class 形参名,class 形参名,...> 返回类型 函数名(参数列表)
{
... //函数体
}
其中template和class是关键字,class可以用typename 关键字代替,在这里typename 和class没区别。
<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为
template <class T> void swap(T& a, T& b){}
当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)
其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)
。而当swap(c,d)
其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b)
,这样就实现了函数的实现与类型无关的代码。
注意:
对于函数模板而言不存在h(int,int)
这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行h(2,3)
这样的调用,或者int a, b; h(a,b)
。
2、类模板通式
template<class 形参名,class 形参名,…> class 类名{
// 类定义...
};
类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如
template<class T> class A{public: T a; T b; T hy(T c, T &d);};
在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。
对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: 'a' uses undefined class 'AA<int> m
。
在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体}
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
template<class T1,class T2> void A<T1,T2>::h(){}
注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
其中,template 是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。类型参数由关键字 class或typename 及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。例:
template<class type,int width>
//type为类型参数,width为非类型参数
class Graphics;
注意:
(1) 如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。
(2) 模板参数名不能被当作类模板定义中类成员的名字。
(3) 同一个模板参数名在模板参数表中只能出现一次。
(4) 在不同的类模板或声明中,模板参数名可以被重复使用。
三、优劣及适用情况
通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。也就是说模板元编程的优势在于:
1.以编译耗时为代价换来卓越的运行期性能(一般用于为性能要求严格的数值计算换取更高的性能)。通常来说,一个有意义的程序的运行次数(或服役时间)总是远远超过编译次数(或编译时间)。
2.提供编译期类型计算,通常这才是模板元编程大放异彩的地方。
模板元编程技术并非都是优点:
1.代码可读性差,以类模板的方式描述算法也许有点抽象。
2.调试困难,元程序执行于编译期,没有用于单步跟踪元程序执行的调试器(用于设置断点、察看数据等)。程序员可做的只能是等待编译过程失败,然后人工破译编译器倾泻到屏幕上的错误信息。
3.编译时间长,通常带有模板元程序的程序生成的代码尺寸要比普通程序的大,
4.可移植性较差,对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。
四、技术细节
模板元编程使用静态C++语言成分,编程风格类似于函数式编程,在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型,不可以使用变量、赋值语句和迭代结构等。被操纵的实体也称为元数据(Metadata),所有元数据均可作为模板参数。
由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。
五、其他范例
// 主模板
template<int N>
struct Fib
{
enum { Result = Fib<N-1>::Result + Fib<N-2>::Result };
};
// 完全特化版
template <>
struct Fib<1>
{
enum { Result = 1 };
};
// 完全特化版
template <>
struct Fib<0>
{
enum { Result = 0 };
};
int main()
{
int i = Fib<10>::Result;
// std::cout << i << std::endl;
}
// 仅声明
struct Nil;
// 主模板
template <typename T>
struct IsPointer
{
enum { Result = false };
typedef Nil ValueType;
};
// 局部特化
template <typename T>
struct IsPointer<T*>
{
enum { Result = true };
typedef T ValueType;
};
// 示例
int main()
{
cout << IsPointer<int*>::Result << endl;
cout << IsPointer<int>::Result << endl;
IsPointer<int*>::ValueType i = 1;
//IsPointer<int>::ValueType j = 1;
// 错误:使用未定义的类型Nil
}
//主模板
template<bool>
struct StaticAssert;
// 完全特化
template<>
struct StaticAssert<true>
{};
// 辅助宏
#define STATIC_ASSERT(exp)\
{ StaticAssert<((exp) != 0)> StaticAssertFailed; }
int main()
{
STATIC_ASSERT(0>1);
}
一、模板形参概述
有三种类型的模板形参:类型形参,非类型形参和模板形参。
二、类型形参
2.1 、类型模板形参
类型形参由关见字class或typename后接说明符构成,如template
2.2、 不能为同一个模板类型形参指定两种不同的类型
比如:
template<class T>void h(T a, T b){}
,语句调用h(2, 3.2)
将出错,因为该语句给同一模板形参T指定了两种类型,第一个实参2把模板形参T指定为int,而第二个实参3.2把模板形参指定为double,两种类型的形参不一致,会出错。(针对函数模板)
注意:上面的结论针对函数模板是正确的,但是不适用于类模板。下面将对类模板的情况进行补充。
当我们声明类对象为:A<int> a
,比如template<class T>T g(T a, T b){}
,语句调用a.g(2, 3.2)
在编译时不会出错,但会有警告,因为在声明类对象的时候已经将T转换为int类型,而第二个实参3.2把模板形参指定为double,在运行时,会对3.2进行强制类型转换为3。当我们声明类的对象为:A<double> a
,此时就不会有上述的警告,因为从int到double是自动类型转换。
验证代码如下:
//TemplateDemo.h
#ifndef TEMPLATE_DEMO_HXX
#define TEMPLATE_DEMO_HXX
template<class T> class A{
public:
T g(T a,T b);
A();
};
#endif
//TemplateDemo.cpp
#include<iostream.h>
#include "TemplateDemo.h"
template<class T> A<T>::A(){}
template<class T> T A<T>::g(T a,T b){
return a+b;
}
void main(){
A<int> a;
cout<<a.g(2,3.2)<<endl;
}
编译结果:
warning C4244: “参数”: 从“double”转换到“int”,可能丢失数据
三、非类型形参
(1)模板的非类型形参
模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参。
(2)非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
(3)模板的非类型形参只能是整型,指针和引用
像double
,String
, String **
这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
(4)调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
(5)注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
(6) 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
(7)sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
(8)当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{
};如果有int b
,这时A<int, b> m
;将出错,因为b不是常量,如果const int b
,这时A<int, b> m
;就是正确的,因为这时b是常量。
(9)非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){}
,若使用h(2)
调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h<int, 3>(2)
这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。
(10) 非类型模板形参的形参和实参间所允许的转换
a、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;
即数组到指针的转换
b、const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m;
即从int *到const int *的转换。
c、提升转换。如:template<int a> class A{}; const short b=2; A<b> m;
即从short到int 的提升转换
d、整值转换。如:template<unsigned int a> class A{}; A<3> m;
即从int 到unsigned int的转换。
e、常规转换。
实例:
//由用户自己亲自指定栈的大小,并实现栈的相关操作
//TemplateDemo.h
#ifndef TEMPLATE_DEMO_HXX
#define TEMPLATE_DEMO_HXX
template<class T,int MAXSIZE> class Stack{//MAXSIZE由用户创建对象时自行设置
private:
T elems[MAXSIZE]; // 包含元素的数组
int numElems; // 元素的当前总个数
public:
Stack(); //构造函数
void push(T const&); //压入元素
void pop(); //弹出元素
T top() const; //返回栈顶元素
bool empty() const{ // 返回栈是否为空
return numElems == 0;
}
bool full() const{ // 返回栈是否已满
return numElems == MAXSIZE;
}
};
template <class T,int MAXSIZE>
Stack<T,MAXSIZE>::Stack():numElems(0){ // 初始时栈不含元素
// 不做任何事情
}
template <class T,int MAXSIZE>
void Stack<T, MAXSIZE>::push(T const& elem){
if(numElems == MAXSIZE){
throw std::out_of_range("Stack<>::push(): stack is full");
}
elems[numElems] = elem; // 附加元素
++numElems; // 增加元素的个数
}
template<class T,int MAXSIZE>
void Stack<T,MAXSIZE>::pop(){
if (numElems <= 0) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
--numElems; // 减少元素的个数
}
template <class T,int MAXSIZE>
T Stack<T,MAXSIZE>::top()const{
if (numElems <= 0) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elems[numElems-1]; // 返回最后一个元素
}
#endif
//TemplateDemo.cpp
#include<iostream.h>
#include <iostream>
#include <string>
#include <cstdlib>
#include "TemplateDemo.h"
int main(){
try {
Stack<int,20> int20Stack; // 可以存储20个int元素的栈
Stack<int,40> int40Stack; // 可以存储40个int元素的栈
Stack<std::string,40> stringStack; // 可存储40个string元素的栈
// 使用可存储20个int元素的栈
int20Stack.push(7);
std::cout << int20Stack.top() << std::endl; //7
int20Stack.pop();
// 使用可存储40个string的栈
stringStack.push("hello");
std::cout << stringStack.top() << std::endl; //hello
stringStack.pop();
stringStack.pop(); //Exception: Stack<>::pop<>: empty stack
return 0;
}
catch (std::exception const& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return EXIT_FAILURE; // 退出程序且有ERROR标记
}
}
一、类模板的默认模板参数原则
1、可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。
2、类模板的类型形参默认值形式为:
template<class T1, class T2=int> class A{};
为第二个模板类型形参T2提供int型的默认值。
3、类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如
template<class T1=int, class T2>class A{};
就是错误的,因为T1给出了默认值,而T2没有设定。
4、在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。比如
template<class T1, class T2=int> class A{public: void h();};
定义方法为
template<class T1,class T2> void A<T1,T2>::h(){}
二、验证上述原则
//定义带默认类型形参的类模板。这里把T2默认设置为int型。
template<class T1,class T2=int> class CeilDemo{
public:
int ceil(T1,T2);
};
//在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。
template<class T1,class T2>
int CeilDemo<T1,T2>::ceil(T1 a,T2 b){
return a>>b;
}
int main(){
CeilDemo<int> cd;
cout<<cd.ceil(8,2.5)<<endl;
return 0;
}
输出2(8右移2位),另外会报一个double转int会丢失信息的warning。
在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型,如果没有省略,可能会依编译器不同有不同的处理方案(之前的vc可能只是报warning),我在vs2012和g++上是报错:
error C4519: 仅允许在类模板上使用默认模板参数
可见这里编译器将这里的默认参数认为是函数模板的。
template<class T1=int,class T2=double,class T3=double> class CeilDemo{
public:
double ceil(T1,T2,T3);
};
template<class T1,class T2,class T3>
double CeilDemo<T1,T2,T3>::ceil(T1 a,T2 b,T3 c){
return a+b+c;
}
void main(){
CeilDemo<> cd;
cout<<cd.ceil(2.5 ,3 ,4)<<endl;
}
输出9
三、测试案例汇总
//类模板非类型形参示例
//模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
//类模板的定义
template<class T>class A{public:T g(T a, T b); A();}; //定义带有一个类模板类型形参T的类A
template<class T1,class T2>class B{public:void g();}; //定义带有两个类模板类型形参T1,T2的类B
//定义类模板的默认类型形参,默认类型形参不适合于函数模板。
template<class T1,class T2=int> class D{public: voidg();}; //定义带默认类型形参的类模板。这里把T2默认设置为int型。
//template<class T1=int, class T2>class E{}; //错误,为T1设了默认类型形参则T1后面的所有形参都必须设置认默值。
//以下为非类型形参的定义
//非类型形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *对象的引用或指
针是正确的。
template<class T1,int a> class Ci{public:void g();}; //定义模板的非类型形参,形参为整型
template<class T1,int &a>class Cip{public:void g();};
template<class T1,A<int>* m> class Cc{public:void g();}; //定义模板的模板类型形参,形参为int型的类A的对象的指针。
template<class T1,double*a>class Cd{public:void g();}; //定义模板的非类型形参,形参为double类型的引用。
class E{}; template<class T1,E &m> class Ce{}; //非类型模板形参为对象的引用。
//以下非类型形参的声明是错误的。
//template<class T1,A m>class Cc{}; //错误,对象不能做为非类型形参,非类型模板形参的类型只能是对象的引用或指针。
//template<class T1,double a>class Cc{}; //错误,非类型模板的形参不能是double类型,可以是double的引用。
//template<class T1,A<int> m>class Cc{}; //错误,非类型模板的形参不能是对象,必须是对象的引用或指针。这条规则对于模板型参
也不例外。
//在类模板外部定义各种类成员的方法,
//typeid(变量名).name()的作用是提取变量名的类型,如int a,则cout<<typeid(a).name()将输出int
template<class T> A<T>::A(){cout<<"class A goucao"<<typeid(T).name()<<endl;} //在类模板外部定义类的构造函数的方法
template<class T> T A<T>::g(T a,T b){cout<<"class A g(T a,T b)"<<endl;} //在类模板外部定义类模板的成员
template<class T1,class T2> voidB<T1,T2>::g(){cout<<"class g f()"<<typeid(T1).name()<<typeid(T2).name()<<endl;}
//在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致
template<class T1,int a> voidCi<T1,a>::g(){cout<<"class Ci g()"<<typeid(T1).name()<<endl;}
template<class T1,int &a> voidCip<T1,a>::g(){cout<<"class Cip g()"<<typeid(T1).name()<<endl;}
//在类外部定义类的成员时,template后的模板形参应与要定义的类的模板形参一致
template<class T1,A<int> *m> voidCc<T1,m>::g(){cout<<"class Cc g()"<<typeid(T1).name()<<endl;}
template<class T1,double* a> voidCd<T1,a>::g(){cout<<"class Cd g()"<<typeid(T1).name()<<endl;}
//带有默认类型形参的模板类,在类的外部定义成员的方法。
//在类外部定义类的成员时,template的形参表中默认值应省略
template<class T1,class T2> voidD<T1,T2>::g(){cout<<"class D g()"<<endl;}
//template<class T1,class T2=int> void D<T1,T2>::g(){cout<<"class D k()"<<endl;} //错误,在类模板外部定义带有默认类型的形
参时,在template的形参表中默认值应省略。
//定义一些全局变量。
int e=2; doubleed=2.2; double*pe=&ed;
A<int> mw; A<int> *pec=&mw; E me;
//main函数开始
int main()
{ // template<class T>void h(){} //错误,模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行。
//A<2> m; //错误,对类模板不存在实参推演问题,类模板必须在尖括号中明确指出其类型。
//类模板调用实例
A<int> ma; //输出"class A goucao int"创建int型的类模板A的对象ma。
B<int,int> mb; mb.g(); //输出"class B g() int int"创建类模板B的对象mb,并把类型形参T1和T2设计为int
//非类型形参的调用
//调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。任何局部对象,局部变量,局部对象的地址,局部
变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能
用作非类型模板形参的实参。
//全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
//调用整型int型非类型形参的方法为名为Ci,声明形式为template<class T1,int a> class Ci
Ci<int,3>//正确,数值R是一个int型常量,输出"class Ci g() int"
const int a2=3; Ci<int,a2> mci1; mci1.g(); //正确,因为a2在这里是const型的常量。输出"class Ci g() int"
//Ci<int,a> mci; //错误,int型变量a是局部变量,不是一个常量表达式。
//Ci<int,e> mci; //错误,全局int型变量e也不是一个常量表达式。
//调用int&型非类型形参的方法类名为Cip,声明形式为template<class T1,int &a>class Cip
Cip<int,e> mcip; //正确,对全局变量的引用或地址是常量表达式。
//Cip<int,a> mcip1; //错误,局部变量的引用或地址不是常量表达式。
//调用double*类型的非类形形参类名为Cd,声明形式为template<class T1,double *a>class Cd
Cd<int,&ed> mcd; //正确,全局变量的引用或地址是常量表达式。
//Cd<int,pe> mcd1; //错误,全局变量指针不是常量表达式。
//double dd=3.3; //错误,局部变量的地址不是常量表达式,不能用作非类型形参的实参
//Cd<int,&e> mcd; //错误,非类型形参虽允许一些转换,但这个转换不能实现。
//调用模板类型形参对象A<int> *的方法类名为Cc,声名形式为template<class T1,A<int>* m> class Cc
Cc<int,&mw> mcc; mcc.g(); //正确,全局对象的地址或者引用是常量表达式
//Cc<int,&ma> mcc; //错误,局部变量的地址或引用不是常量表达式。
//Cc<int,pec> mcc2; //错误,全局对象的指针不是常量表达式。
//调用非类型形参E&对象的引用的方法类名为Ce。声明形式为template<class T1,E &m> class Ce
E me1; //Ce<int,me1> mce1; //错误,局部对象不是常量表达式
Ce<int,me> mce; //正确,全局对象的指针或引用是常量表达式。
//非类型形参的转换示例,类名为Ci
//非类型形参允许从数组到指针,从函数到指针的转换,const修饰符的转换,提升转换,整值转换,常规转换。
const short s=3; Ci<int,s> mci4†//正确,虽然short型和int不完全匹配,但这里可以将short型转换为int型
template的参数列表
1.tamplate参数列表说明
- 如果模板参数列表包含class或typename关键字,这些模板参数可以是任何类型。
- 如果参数是非类型参数,则可以是适当类型的值(后面详细说明)。
(1)typename和class的区别
在template模板编程中,可以有两种写法:
template <typename T>
template <class T>
相同之处:
- 最开始定义定义模板的方法就是
template<class T>
, 但是class毕竟都认为是一个类, 在使用时难免会有些点混淆, 也就定义了typename
来标志参数类型。所以两者从这一层面来讲,没有什么不同,它在template的参数列表中都用于表示该参数可以是一个任意类型。
不同之处:
- 最重要关于 typename可以使用嵌套依赖类型, 也就是类型可以嵌套使用. 这也是两个的不同之处.
举个例子就是:
当A<B<int>> val;
的时候,在模板类A的模板定义中就要使用typename,而不能使用class
typename主要用于对嵌套依赖类型进行提取,而class没有这样的功能。对于模板参数是类的时候, typename能够提取出该类所定义的参数类型。
(2)类型模板参数
如果模板参数列表包含class或typename关键字,这些模板参数可以是任何类型。类型模板参数是我们使用模板的主要目的。
定义多个类型模板参数
当我们写下template<typename T1,typename T2>
这样的代码时,我们在传参的时候T1和T2传递相同的类型也是没有问题的
(3)模板模板参数
模板参数就是模板的参数,我们一般指定为T类型,实际上可以使用任何的名字,例如指定一个Foo的模板参数:
temlate<typename Foo>
Foo calc(const Foo& a, const Foo& b)
{
return a+b;
}
而模板模板参数则是模板的参数又是一个模板,例如:
template<typename T, template<typename U> typename Container>
class XCls
{
private:
Container<T> c;
};
模板的第一个参数是T类型,第二个参数是一个Container,他是一个可以指定一个U类型的变量。
那么如何使用他呢?
template<typename T>
class test
{
private:
T t;
};
int main(void)
{
XCls<std::string, test> mylst1;
return 0;
}
我们可以定义一个模板类,然后将其如上方式传入就可以了。
但是如果传入一个容器呢?比如:list
XCls<string, list> mylst1;
如果编译就会报错。我们分析一波:
将string 和 list传入到类XCls中,然后就会定义一个list
template<typename T>
using Lst = std::list<T, std::allocator<T>>;
XCls<std::string, Lst> mylst2;
// 编译时需要加上std=c++11
使用C++11的using关键字的新功能,来定义一个类型的别名,而且使用在模板的情况下,因此我们编译时要指定std=c++11
然后我们将list的别名Lst传入进入,就可以编译通过。
- 这不是模板模板参数
如果是这么定义的模板参数,还会是模板模板参数吗?
template<typename T, typename Sequence = list<T>>
class stack
{
private:
Sequence c;
};
我们定义了一个stack的模板类,模板参数第一个是T类型,第二个是一个Sequence类型,有一个默认的类型是list
使用方法有两种:
stack<int> s1;
stack<int, deque<int>> s2;
第一种,只指定了第一个模板参数,使用第二个默认的模板参数。
第二种,指定了两个模板参数。
但是!这不是模板模板参数。因为,一旦指定了第一个模板参数,那么第二个参数的类型就会确定,而真正的模板模板参数,第二个模板参数和第一个模板参数的类型是没有关系的,可以指定为第一个模板参数的类型,也可以指定为其他类型。因此,这不是模板模板参数!!!
(4)无类型模板参数
无类型模板参数不能是对象,甚至不能是double或者float。无类型参数仅限于int、enmu、指针和引用。
#include <iostream>
using namespace std;
template <int i>
class Person
{
public:
int aa;
Person():aa(i){};
};
int main()
{
Person<3> per;
cout << per.aa <<endl;
system("pause");
return 0;
}
运行结果:
如果将上面的int替换成double,结果不能通过编译:
有时可能想要允许用户指定一个特定值的元素来初始化空对象,可以使用以下的方法:
template<typename T,const T EMPTY>
class Grid
{
public:
//Omitted for brevity
Grid(const Grid<T,EMPTY>& src);
Grid<T,EMPTY>& operator=( const Grid<T,EMPTY>& rhs);
//...
};
初始值可以是任意的int数,也就是必须是int、enmu、指针和引用的一种。
(5)指针和引用模板参数
指针和引用模板参数必须指向所有翻译单元中都可用的全局变量。对于这些类型的变量,相应的技术术语就是带有外部连接的数据。
使用extern声明即可。
如:
template<typename T ,const T& EMPTY>
class Grid
{...};
extern const int emptyInt=0;
Grid<int,emptyInt> myIntGrid;
对于初始化我们还可以使用“零初始化”即 T().
(6)缺省模板参数
类似于缺省函数参数一样,模板的参数列表也可以设置缺省值,并且同样需要注意的是,缺省值应该从右向左给定依次给定。
#include <iostream>
using namespace std;
template<typename T,typename T2 = char>
class Grid{
public:
T aa;
T2 bb;
};
int main()
{
Grid<int> gr;
gr.aa = 100;
gr.bb = 'a';
cout << gr.aa << " " << sizeof(gr.aa) << endl;
cout << gr.bb << " " << sizeof(gr.bb) << endl;
system("pause");
return 0;
}
结果为:
如果在gr的定义的时候给template第二个参数一个int,结果就是:
template-parameter-list于函数模板
- 调用函数模板不需要特殊的语法,但是如果模板参数不能从函数的参数中导出,则需要尖括号和模板参数。
- 模板参数列表是模板函数使用的参数列表。
template-parameter-list于模板类
template< class T, int i > class MyStack...
- 在这种情况下,模板可以接收一个类型(class T)和一个常量参数(int i),在实例化时模板将使用类型T和常量整数i。
- 在MyStack声明的主体中,必须引用T标识符。
- 模板声明本身不生成代码,它指定一个类或函数家族,当其他代码引用时将生成一个或多个类或函数。
- 模板声明具有全局、命名空间或类作用域。它们不能在函数中声明。
下面的示例演示了使用类型参数T和非类型模板参数i的类模板的声明、定义和实例化。
// template_specifications1.cpp
template <class T, int i> class TestClass
{
public:
char buffer[i];
T testFunc(T* p1 );
};
template <class T, int i>
T TestClass<T,i>::testFunc(T* p1)
{
return *(p1++)
};
// To create an instance of TestClass
TestClass<char, 5> ClassInst;
int main()
{
}
以下代码摘录自《inside the C++ object model》仅供参考:
#include <iostream>
#include <assert.h>
using namespace std;
template <class type,int dim>
class Point3d
{
public:
Point3d();
Point3d(type coords[dim])
{
for(int i = 0; i < dim; i++)
this->_coords[i] = coords[i];
}
type& operator[](int index)
{
assert(index < dim && index >= 0);
return _coords[index];
}
type operator[](int index) const
{
assert(index < dim && index >= 0);
return _coords[index];
}
private:
type _coords[dim];
};
template <class type,int dim>
inline ostream& operator<<(ostream &os,const Point3d<type,dim> &pt)
{
os << "(";
for(int i = 0;i < dim;i++)
os << pt[i] << ",";
os << ")";
return os;
}
int main()
{
int a[5] = {2,3,4,5,6};
Point3d<int,5> pt(a);
cout << pt;
system("pause");
return 0;
};
结果:
借鉴这种写法,我们在传递二维数组时,也可以写template<class type,int dim1,int dim2>
,但是不建议,因为这种情况下要想operator[]运算符使得调用pt[i][j]
的时候可以返回_coords[i][j]
是做不到的,所以要想实现二维数组,可以定义一维数组的template-class,然后嵌套使用。