一、函数模板
template <typename T1,typename T2> int function(const T1 & t1,const T2 &t2){}
尖括号内部的参数叫模板参数。
1.实例化函数模板
编译器用函数实参推断模板实参。这些编译器生成的版本被称为模板的实例。
2.模板类型参数
模板类型参数是可以用来表示返回类型或函数参数类型,以及在函数体内用于变量声明或类型转换的参数,如下程序所示。必须说明非类型模板参数并不是表示类型的参数,而是表示具体数值的参数,详见下文。
template <typename T> T foo(T *p) { T tmp=*p; return tmp; }
3.非类型模板参数
非类型模板参数表示一个值不是一个类型!!通过定义特定的类型名来指定参数,而不是typename或class。
非类型模板参数是一个常量值,在需要常量表达式的地方,可以使用非类型参数,比如数组的大小。
非类型模板参数的模板实参必须是常量表达式!!
template <unsigned N,unsigned M> int compare(const char (&p1)[N],const char (&p2)[M]) { return strcmp(p1,p2); }
当调用compare("hi","mom")时,会实例化如下版本:
int compare(const char (&p1)[3],const char (&p2)[4])
一个f非类型参数可以是整型,或一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态生存期(static)。
4.模板编译
当编译器遇到一个模板定义时,并不生成代码,只有实例化模板后,编译器才会生成代码。
模板为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,模板的头文件通常既包括声明也包括定义。
二、类模板
template <typename T> class blob { public: T a; T *P };
1.实例化类模板
类模板都必须显示实例化,使编译器使用实例化后的类型,例 blob<int>.
2.类模板成员函数
(1)普通成员函数
类内定义,与普通函数定义一致。
类外定义,与普通类成员函数定义一致。
(2)模板成员函数
即成员函数是模板函数。
类内定义,与普通模板函数定义一致,如下:
template <typename T>//定义模板类 class blob { public: template <typename U> int fun(const U &u1){};//定义成员模板函数 T a; T *P };
当然,模板函数的形参类型也可以与类模板参数一样。
类外定义,需要同时声明两个模板参数,即
template <typename T> template <typename U> int blob<T>::fun(const U &u1){}
3.类模板与友元
(1)一对一友好关系
类模板与另一个(类或函数)间友好关系的最常见形式是建立对应实例及其友元间的友好关系。
为了引用(类或函数)模板的一个特定实例,必须首先声明模板自身,如下:
//前置声明,在blob中声明友元所需要的 template <typename > class blobptr; template <typename > class blob; template <typename T> bool operator==(const blob<T>&,const blob<T>&); //每个blob实例将访问权限授予相同类型实例化的blobptr和相等运算符 template <typename T> class blob { friend class blobptr<T>; friend bool operator==<T>(const blob<T>&,const blob<T>&); };
(2)通用和特定的模板友好关系
template <typename T>class pal; template <typename T>class C { //C的每个实例将相同实例化pal声明为友元 friend class pal<T>; //pal2的所有实例都是C的每个实例的友元,不需要前置声明 template <typename X> friend class pal2; //pal3是一个非模板类,它是C所有实例的友元 friend class pal3; };
(3)模板参数
由于c++默认的作用域访问说明符为名字而非类型,因此若要使用其表示类型需要使用typename关键字。
4.控制实例化或
当两个或多个独立编译的源文件使用了相回的模板,并提供了相回的模板参数时,每文性中就都会有该模板的一个实例。在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过显式实例化(explicit instantiation)来避免这种开销。一个显式实例化有如下形式:
extern template declaration;//实例化声明 template declaration;//实例化定义
declaration是一个类或函数声明,其中所有模板参数已被替换为模板实参。例如:
extern template class blob<string>;//实例化声明 template int compare(const int &,const int&);//实例化定义
当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。当编译器遇到一个实例化定义时,它为其生成代码。另外,实例化定义会实例化该模板所有成员。
三、模板实参推断
1.类型转换
类型转换能在调用中应用与函数模板的包括如下两项:
- const转换。顶层const无论在形参中还是实参中都会被忽略。可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参。
- 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。一个函数实参可以转换为一个该函数类型的指针。
- 若函数除了模板形参外,还有特定类型的形参,则该实参可以进行正常的类型转换。
2.函数模板显式实参
与类模板一样,可以定义表示返回类型的第三个模板参数,从而允许用户控制返回类型。
template <typename T1,typename T2,typename T3> T1 sum(T2,T3); int i; long lng; //T1是显式指定的,T2和T3是从函数实参类型推断而来的 auto val=sum<long long>(i,lng);//long long sum(int,long)
3.尾置返回类型与类型转换
如果我们希望编写一个函数,接受表示序列的一对迭代器和返回序列中一个元素的引用:
template <typename T> ??? &fcn(T beg,T end) { //处理序列 return *beg;//返回序列中一个元素的引用 }
我们并不知道结果的准确类型,但知道所需类型是所处理的序列的元素类型。为此,需要使用尾置返回类型,即
template <typename T> auto fcn(T beg,T end) -> decltype(*beg) { //处理序列 return *beg;//返回序列中一个元素的引用 }
此例中利用decltype和beg解引用,返回引用的元素类型。那如果需要返回元素类型,而不是元素类型的引用,就需要用到进行类型转换的标准库模板类
remove_reference,它在头文件type_traits中。
template <typename T> auto fcn(T beg,T end) -> typename remove_reference<decltype(*beg)>::type //typename是为了声明type是一个类型而非名字 { //处理序列 return *beg;//返回序列中一个元素的引用 }
4.函数指针和实参推断
template <typename T> int compare(const T&, const T&); //pf1指向实例int compare(const int&,const int&) int (*pf1)(const int&, const int &)=compare;
pf1中参数类型决定了T的模板实参类型。指针PF1指向compare的int版本实例。
5.模板实参推断和引用
(1)左值引用函数参数推断类型
当一个函数参数是模板类型参数的一个普通(左值)引用时,绑定规则告诉我们,只能传递给他一个左值(如变量或一个返回引用类型的表达式)。如果实参是const的,则T将被推断为const类型。例:
template <typename T> void f(T&);//实参必须是一个左值 f(i);//i是一个int类型;模板参数类型T是int f(ci);//ci是一个const int;模板参数T是const int f(5);//错误
template <typename T> void f(const T&);//可以接受一个右值 f(i);//i是一个int类型;模板参数类型T是int f(ci);//ci是一个const int;模板参数T是int f(5);//一个const &参数可以绑定到一个右值;T是int
(2)从右值引用函数参数推断类型
template <typename T> void f1(T&&);//可以接受一个右值 f1(5);//实参是一个int类型的右值;模板参数T是int
(3)引用折叠和右值引用参数
- 如果一个函数参数是一个指向模板类型参数的右值引用(如,T&&),则它可以被绑定到一个左值;
- 且如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将被实例化为一个(普通)左值引用参数(T&)
右值引用通常用于两种情况:模板转发其实参或模板被重载。
6.转发(保持类型信息)
- 利用右值引用,引用折叠保持其对应实参的所有类型信息。
- 利用头文件utility中的forward<T>保持模板函数内传递给其他函数的实参类型。forward<T>返回类型是T&&。
例:
template <typename T> void f1(T&& t1); { f2(std::forward<T>(t1)); }
四、重载与模板
函数匹配规则:
- 对于一个调用,其候选函数包括所有模板实参推断(参见16.2节,第600页)成功的函数模板实例。
- 候选的函数模板总是可行的,因为模板立参推断会排除任何不可行的模板。
- 与在常一样,可行函数(模板与非械板)按类型转换(如果对此调用需要的话)来排序。当然,可以用于函数模板调用的类型转换是非常有限的(参见16.2.1节,第
- 与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是,如果在多个函数提供同样好的匹配,则:
五、可变参数模板
一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。可变数目的参数称为参数包。存在两种参数包:模板参数包,函数参数包
用一个省略号来指出模板参数或函数参数表示一个包。
template <typename T,typename ...arg>//arg是一个模板参数包,表示零个或多个 void foo(const T&t, const arg&... rest);//rest是一个函数参数包,表示零个或多个
(1)sizeof...运算符
当我们需要知道包中有多少元素时,可以使用sizeof...运算符。
template <typename T,typename ...arg>//arg是一个模板参数包,表示零个或多个 void foo(const T&t, const arg&... rest)//rest是一个函数参数包,表示零个或多个 { cout<< sizeof...(arg)<<endl;//类型参数的数目 cout<< sizeof...(rest)<<endl;//函数参数的数目 }
(2)编写可变参数函数模板
//用来终止递归并打印最后一个元素的函数 template <typename T> ostream &print(ostream &os,const T&t) { return os<<t; } //包中除了最后一个元素外的其他元素都会调用这个版本的print template <typename T,typename ...arg> ostream &print(ostream &os,const T&t, const arg&... rest) { cout<<t<<","; return print(os,rest...); }
六、模板特例化
template<>进行模板特例化
template <> ostream &print(ostream &os,const int &t) { return os<<t; }
函数模板必须全部特例化,类模板可以部分特例化。