如有兴趣了解更多请关注我的个人博客https://07xiaohei.com/
本文介绍模版的各种特殊情况:
(一)非类型模板参数(通常不应用于函数模版中):
-
模板的非类型形参是内置类型形参,模板参数不是一个类型而是一个具体的值,且值是常量表达式,因此调用非类型模板形参的实参必须是一个常量表达式(因此模板代码不能修改参数的值,也不能使用参数的地址)。
例如template<class T, int a> class B{}; 其中int a就是非类型的模板形参。
通常能被传入的常量表达式包括全局变量的地址或引用,全局对象的地址或引用const类型变量,sizeof的表达式的结果以及const int 整型变量。
-
形参只能是整型,枚举,指针和引用,如double,string等是不可以的,但是可以使用double&或者string*等类型是允许的。
-
当一个模板被实例化时,非类型参数被一个用户提供的或者编译器推断出的值所代替。正因为模板在编译阶段编译器为我们生成一个对应的版本,所以其值应该能够编译时确定,那么他应该是一个常量或者常量表达式。
-
非类型模板形参的形参和实参间所允许的转换
- 允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换。
- const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
- 提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int的提升转换。
- 整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。
- 常规转换。略。
#include<iostream>
using namespace std;
//定义一个栈,用数组实现,MAXSIZE为非类型模版类参数,其类型确定
template<typename T, int MAXSIZE>
class Stack
{
private:
int REALSIZE;
//这个非类型模版类参数可以在类中使用其名称
T elems[MAXSIZE];
public:
Stack() :REALSIZE(0) {}
bool empty() const { return REALSIZE == 0; }
bool full() const { return REALSIZE == MAXSIZE; }
void push(const T&);
void pop();
T& top();
const T& top() const;
};
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T& elem)
{
if (full())
throw std::out_of_range("Stack<>::push(): full stack");
elems[REALSIZE++] = elem;
}
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
if (!empty())
REALSIZE--;
else
throw std::out_of_range("Stack<>::pop(): empty stack");
}
template<typename T, int MAXSIZE>
T& Stack<T, MAXSIZE>::top()
{
if (empty())
throw std::out_of_range("Stack<>::top(): empty stack");
return elems[REALSIZE - 1];
}
template<typename T, int MAXSIZE>
const T& Stack<T, MAXSIZE>::top() const
{
if (empty())
throw std::out_of_range("Stack<>::top(): empty stack");
return elems[REALSIZE - 1];
}
//double类型,类类型不允许作为非类型模版类参数使用
//template<typename T, double MAXSIZE,string name>
//class A{};
//double引用类型,类指针类型允许作为非类型模版类参数使用
//枚举也可以,但不再演示
template<typename T,double& MAXSIZE,string*name>
class A
{
private:
double REALSIZE;
T elems[MAXSIZE];
string names;
public:
A(double d):REALSIZE(d) { names = *name; }
};
template<typename T, const string* p>
class B {};
template<typename T, unsigned int ui>
class C {};
double alld = 10.0;
double& alldd = alld;
string alls = "帅";
string* allss = &alls;
string s[3] = { "aaaaaa","bbbbbb","cccccc" };
int main()
{
//传入常量表达式
const int i = 5;
Stack<int, 10> s1;
Stack<string, i> s3;
//sizeof也可以
Stack<double, sizeof(double)> s2;
//int ii = 5;
//Stack<string, ii> s4; //错误,不能传入非常量变量
//double d = 10.0;
//double& dd = d;
//string s = "帅";
//string* ss = &s;
//A<int, dd, ss>; //错误,不能传入局部变量,非常量变量
//可以使用全局引用和全局变量的局部地址传入
A<int, alld, &alls> a1(10);
//A<int, alld, allss>; //错误,不能传入全局指针变量
//可以传入全局数组,会将其转换为指针传入
A<double, alld, s> a2(3.14);
//可以将string*提升为const string*
B<int, &alls> b;
//可以将short int提升为int
const short bbb = 2;
Stack<char, bbb>;
//可以将int提升为unsigned int
const int n = 9;
C<int, n>;
return 0;
}
(二)默认模板类型形参:
在c++11新规则中,可以为类模板或者函数模版的类型形参提供默认值。
形式为:template<class T1, class T2, … , class Tk=默认类型 , … , class Tn=默认类型 > class 类名{ 类体 };
or template<class T1 =默认类型 , class T2, … , class Tk=默认类型 , … , class Tn> 返回类型 类型名(形参列表){函数体}
类模版为多个默认模版参数声明指定默认值时,必须遵照“从右往左”的规则进行指定。
函数模版不需要遵循此规则,可以任意指定默认值。
函数模板的参数推导规则简单来说就是:能够从函数实参中推导出类型的话,那么默认模版参数就不会被使用,否则使用默认模版参数。
因此,函数模版如果需要提供默认类型,因为其形参列表是从右往左给出默认值的,所以只有其默认类型也是从右往左给出且与值的类型匹配(也就是不会出现不匹配情况)、值的个数匹配,才是有效的默认类型。
#include<iostream>
using namespace std;
template <typename T1, typename T2 = int, typename T3=double>
class A {
public:
T1 Aval1;
T2 Aval2;
T3 Aval3;
};
//template <typename T1 = int, typename T2>
//class DefClass2 {}; // 错误:模板参数的默认值没有遵循“由右往左”的规则
//
//template <typename T, int i = 0>
//class DefClass3 {};
//
//template <int i = 0, typename T>
//class DefClass4 {}; // 错误:即使是非类型模版参数,模板参数的默认值也必须遵循“由右往左”的规则
//尽管可以进行下面这样的声明,但几乎不会用上这个默认类型参数,可以直接无视
template <typename T1 = int, typename T2>
void B1(T1 a, T2 b) {
cout << a << endl;
cout << typeid(T1).name() << endl;
cout << b << endl;
cout << typeid(T2).name() << endl;
return;
}; // 函数模板不用遵循“由右往左”的规则
//允许进行这样的声明,当未提供第二个参数时,自动默认b为int类型且值为0
template <typename T1, typename T2= int>
void B2(T1 a, T2 b=0) {
cout << a << endl;
cout << typeid(T1).name() << endl;
cout << b << endl;
cout << typeid(T2).name() << endl;
return;
}; // 函数模板不用遵循“由右往左”的规则
template <int i = 0, typename T>
void B3(T a) {
cout << i << endl;
cout << typeid(i).name() << endl;
cout << a << endl;
cout << typeid(T).name() << endl;
return;
}; // 非类型模版参数也不用遵循“由右往左”的规则
int main()
{
A<string> a1;
cout << typeid(a1.Aval1).name() << endl;
cout << typeid(a1.Aval2).name() << endl; //为默认的int
cout << typeid(a1.Aval3).name() << endl; //为默认的double
A<char, unsigned int> a2;
cout << typeid(a2.Aval1).name() << endl;
cout << typeid(a2.Aval2).name() << endl; //unsigned int覆盖默认的int
cout << typeid(a2.Aval3).name() << endl; //为默认的double
A<char, unsigned int,float> a3;
cout << typeid(a3.Aval1).name() << endl;
cout << typeid(a3.Aval2).name() << endl; //unsigned int覆盖默认的int
cout << typeid(a3.Aval3).name() << endl; //float覆盖默认的double
B1(3, 4.1); //推导出int,double类型,和默认类型无关
B1(3.1, 4); //推导出double,int类型,覆盖默认类型
B2("s"); //前者推导出const char[2],后者为默认的int且值为0
return 0;
}
//运行结果:
// class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
// int
// double
// char
// unsigned int
// double
// char
// unsigned int
// float
// 3
// int
// 4.1
// double
// 3.1
// double
// 4
// int
// s
// char const* __ptr64
// 0
// int
(三)模版的特化:
模版的特化是模版参数在某种特定类型下的具体实现,是对单一模版提供的一个特殊实例,它将一个或多个模版参数绑定到特定的类型或值上。
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如字符串不能进行比较,此时就需要特化出一个专门用于字符串的模版参数。
特化分为函数模版特化和类模版特化:
-
函数模板特化:
必须要先有一个基础的函数模板,且使用特换模板函数时格式有要求:
- 关键字template后面接一对空的尖括号<>。
- 函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型。
函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
(实际上,此为全特化,c++11中,函数模版也可以进行偏特化的部分特化相似操作,但实际上这是一种重载,会导致函数有多个匹配,此时无法区分;不能进行偏特化的部分限制)
#include <iostream> using namespace std; template< typename T > int compare(const T& a, const T& b) { if (a > b)return 1; else if (a == b)return 0; else return -1; } template<> int compare(const char* const & a, const char* const & b) { return strcmp(a, b); } int main() { cout << compare(3, 4) << endl; cout << compare(4.2, 2.3) << endl; cout << compare((const char*)"vvv", (const char*)"ttt") << endl; return 0; }
-
类模版特化:
分为全特化和偏特化:
-
全特化:将模版参数类型中所有的参数都确定化。
全特化的优先级比偏特化要高。
-
偏特化:对于模版的类型进行限制。
偏特化有两种表现方式:
- 部分特化:将模版参数表中的一部分参数特化。
- 对于模版类型进行进一步的限制。(如必须特化为指针类型、引用类型)
#include<iostream> using namespace std; //类模版 template<class T1, class T2> class Data { public: Data() { cout << "Data<T1,T2>" << endl; } private: T1 _d1; T2 _d2; }; //全特化: template<> class Data<int, char> { public: Data() { cout << "Data<int,char>" << endl; } private: int _d1; char _d2; }; //偏特化1:部分特化 template<class T1> class Data<int, T1> { public: Data() { cout << "Data<int,T1>" << endl; } private: int _d1; T1 _d2; }; //偏特化2:添加限制 //两个参数偏特化为指针类型 template <typename T1, typename T2> class Data <T1*, T2*> { public: Data() { cout << "Data<T1*,T2*>" << endl; } private: T1 _d1; T2 _d2; }; //两个参数偏特化为引用类型 template<class T1, class T2> class Data<T1&, T2&> { T1& _d1; T2& _d2; public: Data(T1& a, T2& b) : _d1(a), _d2(b) { cout << "Data<T1&,T2&>" << endl; } }; int main() { Data<int, int> d1; // Data<T1,T2> Data<string, string> d2; // Data<T1,T2> Data<int, char> d3; // Data<int,char>,其实部分特化也满足,但优先进行全特化 Data<int, double> d4; // Data<int,T1> Data<int*, double*> d5; // Data<T1*,T2*> int i = 5; int& pi = i; double d = 6.5; double& pd = d; Data<int&, double&> d6(pi,pd); //Data<T1&,T2&> return 0; }
-