模板非类型形参的详细阐述
模板非类型形参的详细阐述
关于模板的非类型形参,网上有很多内容,C++primer只有大概一页的阐述,但是都不够清晰详细。下面我尽可能从自己的角度去给大家描述一下非类型形参的相关细节。如果想进一步理解非类型形参以及模板内容可以阅读C++template这本书,在4.1节,8.3.3节,13.2节都有相关解释。
模板除了定义类型参数,我们还可以在模板定义非类型参数。
什么是非类型形参?顾名思义,就是表示一个固定类型的常量而不是一个类型。
先举一个简单的例子(模板类与模板函数都可以用非类型形参)
//例子1:
template<class T,int MAXSIZE> class List{
private:
T elems[MAXSIZE];
public:
Print(){ cout<<"The maxsize of list is"<<MAXSIZE; }
}
List<int,5> list;
list.Print();//打印"The maxsize of list is 5"
这个固定类型是有局限的,只有整形,指针和引用才能作为非类型形参,而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。
这里要强调一点,我们对于非类型形参的限定要分两个方面看
1.对模板形参的限定,即template<>里面的参数
2.对模板实参的限定,即实例化时<>里面的参数
下面逐个解释一下非类型形参的局限
1.浮点数不可以作为非类型形参,包括float,double。具体原因可能是历史因素,也许未来C++会支持浮点数。
2.类不可以作为非类型形参。
3.字符串不可以作为非类型形参
4.整形,可转化为整形的类型都可以作为形参,比如int,char,long,unsigned,bool,short(enum声明的内部数据可以作为实参传递给int,但是一般不能当形参)
5.指向对象或函数的指针与引用(左值引用)可以作为形参
下面解释一下非类型实参的局限
1.实参必须是编译时常量表达式,不能使用非const的局部变量,局部对象地址及动态对象
2.非Const的全局指针,全局对象,全局变量(下面可能有个特例)都不是常量表达式。
3.由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参
备注:常量表达式基本上是字面值以及const修饰的变量
//例子2:
template<class T,int MAXSIZE> class List{
private:
T elems[MAXSIZE];
public:
void Print(){ cout<<"The maxsize of list is "<<MAXSIZE; }
};
const int num1 = 9; ;//全局变量
static int num2= 9; ;//全局变量
const int num3 = 9; ;//局部变量
List<int,num1> list; //正确
List<int,num2> list; //错误
List<int,num3> list; //正确
//再看一个关于指针和字符串比较特别的例子
//例子3:
template<char const* name>
class pointerT{
};
char a[] = "saaa";;//全局变量
char a2[] = "saaa";;//局部变量,写在main函数里面
char *b = "saaa";//全局变量
char *const c = "saaa";//全局变量,顶层指针,指针常量
pointerT<"testVarChar"> p1;//错误
pointerT<a> p2;//正确
pointerT<a2> p22;//错误,局部变量不能用作非类型参数
pointerT<b> p3;//错误,error C2975:“pointerT”的模板参数无效,应为编译时常量表达式
pointerT<c> p4;//错误,error C2970: “c”: 涉及带有内部链接的对象的表达式不能用作非类型参数
//关于指针常量和常量指针可以参考博客
Const用法总结(快速区分指针常量与常量指针)
这里大家可能会有几个疑问
①.到底为什么字符串不能作为实参?
答:我们看到上面p1的模板实参是"testVarChar",然而当我们在另一个编译单元(.cpp文件)同样声明这么一个模板实例时,这两个"testVarChar"的地址可能是不同的,编译器传递给模板时就会传递传递不同的地址,从而导致这两个模板实例是两个不同且不兼容的类型。这就是支持字符串的问题所在。(这里可能更深的涉及模板的实现原理)
②.变量b和c作为模板实参为什么错误不同?
答:首先解释b实参,b在这里看做是一个指针,是一个全局指针,但是他不是一个常量表达式,所以b不对。我们再看看c,c相比于b对了一个const修饰符,表示这个指针是一个常量。然而const是一个比较特别的关键字,他具有内部链接属性(关于内连接参考博客 理解C++的链接:C++内链接与外链接的意义),也就是说仅在定义这个变量的文件内可见,不会造成不同编译单元的混编时的链接错误。
这个特性对于模板来说可是有问题的,就像问题①所描述的,由于每个编译单元可能都有一个c变量,导致在编译时,实例化多个c,而且c的地址还不同,这就造成二个模板的实例是两个不同且不兼容的类型。
③为什么a变量作为实参可以?
答:我看过一些书籍,上面举得例子都是用const修饰不行的情况下在加extern来形成extern constchara[]="saaa";这样形式的语句,extern和const联合使用确实可以压制const的内部属性。
这个a这里可以看做一个数组类型(进一步理解数组与指针的关系可以参考博客
char * itoa(int, char *, int); 第二个参数明明是char*,为什么却又不能是“char*”? )
,那么我们看到他不仅避免了①中的实例化地址不同的问题(因为是全局唯一的),而且还避免了const带来的内部链接问题,所以这一项可能是经过编译器优化过的结果。
转载:https://blog.csdn.net/u012999985/article/details/50780311