目录
16.1 定义模板
16.1.1 函数模板
template<typename T>
int compare(const T& v1, const T& v2)
{
if (v1 < v2)
return -1;
if (v1 > v2)
return 1;
return 0;
}
模板定义以关键字template开始,后跟一个模板参数列表,这是一个都好分割的一个或多个模板参数的列表。
(1)实例化函数模板
调用函数模板时,编译器用函数实参来为我们推断模板实参。
cout << compare(1, 0) << endl;
//实例化出 int compare(const int&,const int&)
vector<int> vec1{ 1,2,3 }, vec2{ 4,5,6 };
cout << compare(vec1, vec2) << endl;
//实例化出 int compare(const vector<int>&,const vector<int>&)
(2)模板类型参数
可以将模板类型参数看作类型说明符,就像内置类型或类类型是说明符一样
template<typename T>T foo(T *p)
{
T tmp=*p;
.......
return tmp;
}
(3)非类型模板参数
可以在模板中定义非类型参数。一个非类型参数表示一个值而非一个类型。
例如,编写一个处理字符串字面常量。第一个模板参数表示第一个数组长度,第二个模板参数表示第二个数组长度。
template<unsigned N,unsigned M>
int compare(const char(&p1)[N], const char(&p2)[M])
{
return strcmp(p1, p2);
}
调用时:conpare("hi","mom"),编译器会使用字面值蟾宫i昂大小替代N和M,加上结束符,上述的N为3,M为4.
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或引用。绑定到非类型整形参数的实参必须是一个常量表达式,绑定到指针或引用非类型参数的实参必须是具有静态的生存周期。
16.1.2 类模板
(1)定义类模板
template<typename T> class Blob
{
public:
typedef T value_type;
typedef typename vector<T>::size_type size_type;
Blob();
Blob(initializer_list<T> il);
size_type size() const //Blob中的元素数目
{
return data->size();
}
bool empty() const
{
return data->empty();
}
void push_back(T&& t)
{
data->push_back(move(t));
}
void push_back(const T& t)
{
data->push_back(t);
}
void pop_back();
T& back(); //元素访问
T& operator[](size_type i);
private:
shared_ptr<vector<T>> data;
//若data[i]无效,则抛出msg
void check(size_type i, const string& msg) const;
};
(2)类模板的成员函数
定义在类模板外的成员函数必须以关键字template开始,后接类模板参数列表。
template<typename T>void Blob<T>::check(size_type i, const string& msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
template<typename T>
T& Blob<T>::back()
{
check(0, "back on empty blob");
return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i)
{
check(0, "out of range");
return (*data)[i];
}
template<typename T> void Blob<T>::pop_back()
{
check(0, "pop_back on empty Blob");
data->pop_back();
}
(3)Blob构造函数
template<typename T>Blob<T>::Blob() :data(make_shared<vector<T>>) {};
template<typename T>Blob<T>::Blob(initializer_list<T> il) :data(make_shared<vector<T>>(il)) {};
(4)一个类模板的成员函数只有当程序用到他时才进行实例化
(5)模板类型别名
可以定义一个typdef来引用实例化的类:
typedef Blob<string> StrBlob;
或
template<typename T> using twin=pair<T,T>
twin<string> authors ///authors是一个pair<string,string>
16.1.3 模板参数
(1)模板参数与作用域
一个模板参数的可用范围是在其声明之后,至模板声明或定义结束之前。与其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。
typedef double A;
template<typename A,typename B>void f(A a,B b)
{
A tmp=a; //tmp的类型为模板参数A的类型,而非double
double B; //错误,重声明模板参数B
}
16.1.4 成员模板
当模板被使用时才会被实例化,相同实例化会出现在多个文件中。当两个或多个独立编译的文件使用相同模板,并提供相同模板参数时,每个文件中就都会有该模板的一个实例。
多个文件中实例化相同模板的额外开销非常严重。可以通过显示实例化来避免开销,一个显示实例化有如下形式
extern templte declaration //实例化声明
template declaration //实例化定义
例如
extern template class Blob<string> //声明
template int compare(const int &,const int &) //定义
编译器遇到extern模板声明时,不会在本文件中生成实例化代码。将一个实例化声明为extern就代表在程序其他位置有该实例化的一个非extern声明。
16.2 模板实参推断
16.2.1 类型转换与模板类型参数
与非模板函数一样,我们在一次调用中传递给函数模板的实参被用来初始化函数的形参。如果一个函数形参的类型使用了模板类型参数,则采用特殊的转换规则。
- const转换:可以将一个非const对象的引用传递给一个const的引用形参。
- 数组或函数指针转换:一个数组实参可以转换为一个指向其元素的指针,类似的,一个函数实参可以转换为一个该函数类型的指针。
例:
template<typename T> T fobj(T,T) //实参被拷贝
template<typename T> T fref(cosnt T&,const T&) //引用
string s1("hi")
const string s2("h")
fobj(s1,s2) //const被忽略
fref(s1,s2) //将s1转换为const是允许的
在fobj调用中,实参被拷贝,因此原对象是否是const没有关系。在fref调用中,参数类型是const的引用,对于一个引用参数来说,转换为const是允许的,因此这个调用也是合法的。
int a[10],b[42];
fobj(a,b); //调用fobj(int*,int*)
fref(a,b); //错误:数组类型不匹配
在fobj调用中,数组大小无关紧要,两个数组都会转换为指针。fobj中的模板类型为int*。但是,fref调用是不合法的,如果形参是一个引用,则数组不会转换为指针,a和b的类型是不匹配的,因此调用时错误的。
(1)使用相同模板参数类型的函数形参
一个模板类型参数可以用作多个函数形参类型。传递给这些形参的实参必须具有相同类型y
long lng;
compare(lng,1024)//错误,不能实例化compare(long,int)
可以将函数模板定义为两个类型参数:
template<typename A,typename B>
int f(const A&v1,const B&v2)
{
if(v1<v2) return -1;
if(v1>v2) return 1;
return 0;
}
long lng;
f(lng,1024); //正确,调用f(long,int)
16.2.2 函数模板显示实参
某些情况下,编译器无法推断出模板实参的类型,我们希望允许用户控制模板实例化。当函数返回类型与参数列表中任何类型都不相同时,这两种情况最常出现。
(1)指定显示模板实参
例:我们可以定义表示返回类型的第三模板参数,从而允许用户控制返回类型。
//编译器无法推断T1,他未出现在函数参数列表中
template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);
上述中,没有任何函数实参的类型可用来推断T1的类型,每次调用sum时调用者都必须为T1提供一个显示模板实参
//T1是显示指定的,T2和T3是从函数实参类型推断而来的
auto val3=sum<long long>(i,lng) //long long sum(int,long)
显示指定T1的类型,T2和T3由编译器从i和lng中推断而来
16.2.3 模板实参推断和引用
考虑下述例子
template<typename T>void f(T &p)
(1)从左值引用函数参数推断类型
当一个函数参数是模板类型参数的一个普通左值引用时,只能传递给他一个左值,实参可以是const也可以不是,如果实参是const,则T被推断为const 类型
template<typename T>void f1(T &)//实参必须为一个左值
f1(i); //i是一个int,模板参数类型T是int
f1(ci); //ci是一个const int,模板参数类型T是int
f1(5); //错误,传递给一个&参数的实参必须是左值
如果一个函数参数类型是const T&,可以传递给他任何类型的实参——一个对象(const或非const),一个临时对象或者一个字面值常量。
template<typename T>void f2(const T&) //可以接受一个右值
f2(i); //i是一个int,模板参数T是int
f2(ci); //ci是一个const int,但模板参数T是int
f2(5); //一个const &参数可以绑定到一个右值,T是int
(2)从右值引用推断函数参数推断类型
template<typename T> void f2(T&&)
f3(42); //实参是一个int类型的右值,T是int
16.3 可变模板参数
一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包。存在两种参数包:模板参数包,函数参数包。
用一个省略号指出一个模板参数或函数参数表示一个包。
例:
//Args是一个模板参数包,rest是一个函数参数包
//Args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template<typename T,typename... Args>
void foo(const T &t,Args&...rest);
编译器还会推断包中参数的数目
int i=0;double d=3.14;string s="hi"
foo(i,s,42,d); //包中有三个参数
foo(s,42,"hi"); //包中有两个参数
foo(d,s); //包中有一个函数
foo("hi"); //空包
编译器会为foo实例化出四个版本:
void foo(const int &,const string &,const int &,const double &)
void foo(const string&,const int &,const char[3]&);
void foo(const double &,const string &);
void foo(const char[3]&)
当我们需要知道包中有多少个元素时,可以采用sizeof....运算符。
template<typename...Args> void g(Args...args)
{
cout<<sizeof...(Args)<<endl;
cout<<sizeof...(args)<<endl;
}
16.3.1 编写可变参数函数模板
首先定义一个名为print的函数,在一个给定流上打印给定实参列表的内容
//用来终止递归并打印最后一个元素的函数
//此函数必须在可变参数版本的print定义之前声明
template<typename T>
ostream &print(ostream &os,const T &t)
{
return os<<t;
}
可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身
//包中除了最后一个元素之外的其他元素都会调用这个版本的print
template<typename T,typename...Args>
ostream &print(ostream &os,const T &t,const Args&...rest)
{
os<<t<<",";
return print(os,rest...);
}
第一个版本print负责终止递归并打印初始调用中的最后一个实参。第二个版本的print是可变参数版本,打印绑定到t的实参,并调用自身来打印函数参数包中的剩余部分。
例,对于print(cout,i,s,42);