第十六章 模板与泛型编程

16.1 定义模板

  1. 模板是C++泛型编程的基础。为模板提供足够的信息,就能生成特定的类或函数。

16.1.1 函数模板

  1. 在模板定义中,模板参数列表不能为空。
//T的实际类型在编译时根据compare的使用情况来确定
template <typename T>
int compare(const T &v1, const T &v2)
{
	if(v1 < v2) return -1;
	if(v2 < v1) return 1;
	return 0;
}
  1. 当调用一个函数模板时,编译器(通常)用函数实参来推断模板实参,然后实例化一个特定版本的函数。
//实例化出int compare(const int&, const int&)
cout << compare(1,0) << endl; //T为int

//实例化出int compare(const vector<int>&, const vector<int>&)
vector<int> vec1{1,2,3}, vec2{4,5,6};
cout << compare(vec1,vec2) << endl; //T为vector<int>
  1. 类型参数前必须使用关键字class或typename。在模板参数列表中,这两个关键字的含义相同,可以互换使用。
  2. 类型参数可以用来指定返回类型或函数的参数类型。
template<typename T> T foo(T* p)
{
	T tmp = *p; //tmp的类型将是指针p指向的类型
	// ...
	return tmp;
}

//错误:U之前必须加上class或typename
template<typename T,U> T calc(const T&, const U&);
//正确:在目标参数列表中,typename和class没什么不同
template<typename T,class U> calc(const T&, const U&);
  1. 可以在模板中定义非类型参数,表示一个值而非一个类型。
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 (&p)[3],const char (&p2)[4]
  1. 当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。
  2. 一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期。不能用一个普通(非static)局部变量或动态对象作为指针或引用非类型模板参数的实参。
  3. 非类型模板参数的模板实参必须是常量表达式。
  4. 编写泛型代码的两个重要原则:
    模板中的函数参数是const的引用。(保证函数可以用于不能拷贝的类型)
    函数体中的条件判断仅使用<比较运算。(降低函数对要处理的类型的要求)
  5. 模板程序应该尽量减少对实参类型的要求。
template <typename T> int compare(const T &v1, const T &v2){
	if(v1 < v2) return -1;
	if(v2 < v1) return 1;
	return 0;
}
//即使用于指针也正确的版本
template <typename T> int compare(const T &v1, const T &v2){
	if( less<T>()(v1,v2 )) return -1;
	if( less<T>()(v1,v2 )) return 1;
	return 0;
}
  1. 函数模板和类模板成员函数的定义通常放在头文件中。
  2. 当编写模板时,代码不能是针对特定类型的,但模板代码通常对其所使用的类型有一些假设。
  3. 保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。

16.1.2 类模板

  1. 编译器不能为类模板推断模板参数类型。
  2. 一个类模板的每个实例都形成一个独立的类。
  3. 类模板用来实例化类型,而一个实例化的类型总是包含模板参数的。
  4. 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
//实例化Blob<int>和接受initializer_list<int>的构造函数
Blob<int> squares = {0,1,2,3,4,5,6,7,8,9};
//实例化Blob<int>::size() const
for(size_t i = 0; i!= squares.size(); ++i)
	squares[i] = i*i; //实例化Blob<int>::operator[](size_t)
//如果一个成员函数没被使用,则它不会被实例化
  1. 在类模板自己的作用域中,可以直接使用模板名而不提供实参。
//后置:递增/递减对象但返回原值
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{
	//此处无须检查;调用前置递增时会进行检查
	BlobPtr ret = *this; //保存当前值
	++*this; //推进一个元素;前置++检查递增是否合法
	return ret; //返回保存的状态
}
  1. C++11允许为类模板定义一个类型别名。
template<typename T> using twin = pair<T,T>;
twin<string> authors; //authors是一个pair<string,string>
twin<int> win_loss; //win_loss是一个pair<int,int>
twin<double> area; 

//可以固定一个或多个模板参数
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> book; //pair<string, unsigned>
partNo<Vehicle> cars; 
partNo<Student> kids; 
  1. 类模板和友元:一对一友好关系。
//前置声明,在Blob中声明友元所需要的
template <typename> class BlobPtr;
template <typename> class Blob; //运算符==中的参数所需要的
template <typename T>
bool operator==(const Blob<T>&, const Blob<T>&);

template <typename T> class Blob {
	//每个Blob实例将访问权限授予相同类型实例化的BlobPtr和相等运算符
	friend class BlobPtr<T>;
	friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
	//...
};

Blob<char> ca; 	//BlobPtr<char>和operator==<char>都是本对象的友元
Blob<int> ia; 	//BlobPtr<int>和operator==<int>都是本对象的友元
  1. 为了让所有实例成为友元,友元声明中必须使用与模板本身不同的模板参数。
//前置声明,在将目标的一个特定实例声明为友元时要用到
template<typename T> class Pal;
class C { //C是一个普通的非模板类
	friend class Pal<C>; //用类C实例化的Pal是C的一个友元
	//Pal2的所有实例都是C的友元;这种情况无需前置声明
	template <typename T> friend class Pal2;
};
template <typename T> class C2 { //C2本身是一个类模板
	//C2的每个实例将相同实例化的Pal声明为友元
	friend class Pal<T>; //Pal的模板声明必须在作用域之内
	//Pal2的所有实例都是C2的每个实例的友元,不需要前置声明
	template <typename X> friend class Pal2;
	//Pal3是一个非模板类,它是C2 所有实例的友元
	friend class Pal3; //不需要Pal3的前置声明
};
  1. 令模板自己的类型参数成为友元。
template <typename Type> class Bar {
	friend Type; //将访问权限授予用来实例化Bar的类型
	//...
};
  1. 类模板的static成员。
template <typename T> class Foo {
public:
	static std::size_t count() { return ctr; }
	//其他接口成员
private:
	static std::size_t ctr;
	//其他实现成员
};
//实例化static成员Foo<string>::ctr和Foo<string>::count
Foo<string> fs;
//所有三个对象共享相同的Foo<int>::ctr和Foo<int>::count成员
Foo<int> fi,fi2,fi3;

//类模板的每一个实例,都有一个独有的static对象
//将static数据成员定义成模板
template <typename T>
size_t Foo<T>::ctr = 0; //Foo被实例化时,定义并初始化ctr(实例化)
Foo<int> fi; //实例化Foo<int>类和static数据成员ctr
auto ct = Foo<int>::count(); //实例化Foo<int>::count
ct = fi.count(); //使用Foo<int>::count
ct = Foo::count(); //错误:使用哪个版本实例的count?

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
}

//错误:非法重用模板参数名V
template <typename V, typename V> //...
  1. 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。
  2. 默认情况下,C++假定通过作用域运算符访问的名字不是类型。
  3. 当希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。
  4. C++11可以为函数和类模板提供默认实参。
template <class T = int> class Numbers {//T默认为int
public:
	Numbers(T v=0):val(v) { }
	//对数值的各种操作
private:
	T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; //空<>表示我们希望使用默认类型
  1. 与函数参数相同,声明中的模板参数的名字不必与定义中相同。
//3个calc都指向相同的函数模板
template <typename T> T  calc(const T&, const T&); //声明
template <typename U> U  calc(const U&, const U&); //声明
//模板的定义
template <typename Type>
Type calc(const Type& a, const Type& b) { /*...*/ }

16.1.4 成员模板

  1. 一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板。成员模板不能是虚函数。
//函数对象类,对给定指针执行delete
class DebugDelete {
public:
	DebugDelete(std::ostream &s = std::cerr): os(s) { }
	//与任何函数模板相同,T的类型由编译器推断
	template<typename T> void operator()(T* p) const
		{ os << "deleting unique_ptr" << std::endl; delete p; }
private:
	std::ostream &os;
};

double *p = new double;
DebugDelete d; 	//可像delete表达式一样使用的对象
d(p);		 //调用DebugDelete::operator()(double*),释放p
int* ip = new int;
//在一个临时DebugDelete对象上调用operator()(int*)
DebugDelete()(ip);

//销毁p指向的对象
//实例化DebugDelete::operator()<int>(int *)
unique_ptr<int,DebugDelete> p(new int, DebugDelete());
//销毁sp指向的对象
//实例化DebugDelete::operator()<string>(string*)
unique_ptr<string,DebugDelete> sp(new string, DebugDelete());
//DebugDelete的成员模板实例化样例
void DebugDelete::operator()(int *p) const { delete p; }
void DebugDelete::operator()(string *p) const { delete p; }

16.1.5 控制实例化

  1. 通过显式实例化来避免在多个文件中实例化相同模板的额外开销。
//实例化声明与定义
extern template class Blob<string>; //声明
template int compare(const int&, const int&); //定义

//Application.cc
//这些模板类型必须在程序与其他位置进行实例化
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1,sa2; //实例化会出现在其他位置

//Blob<int>及其接受initializer_list的构造函数在本文件中实例化
Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); //拷贝构造函数在本文中实例化
int i = compare(a1[0],a2[0])); //实例化出现在其他位置

//templateBuild.cc
template int compare(const int&, const int&);
template class Blob<string>;
  1. 将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。
  2. 由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何使用此实例化版本的代码之前。
  3. 对每个实例化声明,在程序中某个位置必须有其显式的实例化定义。
  4. 在一个类模板的实例化定义中,所有类型必须能用于模板的所有成员函数。

16.1.6 效率与灵活性

//shared_ptr的析构函数必须包含类似下面这样的语句
del?del(p):delete p; //del是一个成员,运行时需要跳转到del的地址

//unique_ptr的析构函数与shared_ptr类似
del(p); //在编译时del以确定类型,无运行时额外开销

16.2 模板实参推断

16.2.1 类型转换与模板类型参数

  1. 编译器通常不是对实参进行类型转换,而是生成一个新的模板实例。
  2. 在其他类型转换中,能在调用中应用于函数模板的包括如下两项:
    const转换。
    数组或函数指针转换。
template <typename T> T fobj(T,T); 		//实参被拷贝
template <typename T> T fref(const T&, const T&); 	//引用
string s1("a value");
const string s2("another value");
fobj(s1,s2); //调用fobj(string,string); const被忽略
fref(s1,s2); //调用fref(const string&, const string&)
//将s1转换为const是允许的
int a[10], b[42];
fobj(a,b); //调用f(int*,int*)
fref(a,b); //错误:数组类型不匹配
  1. 使用相同模板参数类型的函数形参。
//假设compare函数接受两个const T&参数
long lng;
compare(lng,1024); //错误:不能实例化compare(long,int)

//实参类型可以不同,但必须兼容
template <typename A,typename B>
int flexibleCompare(const A& v1, const B& v2)
{
	if(v1<v2) return -1;
	if(v2<v1) return 1;
	return 0;
}

long lng;
flexibleCompare(lng,1024); //正确
  1. 如果函数参数类型不是模板参数,则对实参进行正常的类型转换。
template <typename T> ostream &print(ostream &os, const T &obj)
{
	return os << obj;
}

print(cout,42); //实例化print(ostream&, int)
ofstream f("output");
print(f,10); //使用print(ostream&,int);将f转换为ostream&

16.2.2 函数模板显式实参

  1. 只有尾部(最右)参数的显式模板实参才可以忽略,而且前提是它们可以从函数参数推断出来。
//编译器无法推断T1,它未出现在函数参数列表中
template <typename T1, typename T2, typename T3>
T1 sum(T2,T3);

//T1是显式指定的,T2和T3是从函数实参类型推断而来的
auto val3 = sum<long long>(i,lng); //long long sum(int,long)

//糟糕的设计:用户必须指定所有三个模板参数
template<typename T1,typename T2,typename T3>
T3 alternative_sum(T2,T1);

//错误
auto val3 = alternative_sum<long long>(i,lng);
//正确:显式指定了所有三个参数
auto val2 = alternative_sum<long long, int , long>(i,lng);
  1. 对于模板类型参数已经显式指定了的函数实参,也进行正常的类型转换。
long lng;
compare(lng,1024); 		//错误:模板参数不匹配
compare<long>(lng,1024); 	//正确:实例化compare(long,long)
compare<int>(lng,1024);		 //正确:实例化compare(int,int)

16.2.3 尾置返回类型与类型转换

template <typename It>
??? &fcn(It beg, It end){
	//处理序列
	return *beg; //返回序列中一个元素的引用
}

vector<int> vi = { 1,2,3,4,5};
Blob<string> ca = {"hi","bye"};
auto &i = fcn(vi.begin(), vi.end()); 	//fcn应该返回int&
auto &s = fnc(ca.begin(), ca.end()); //fcn应该返回string&

//尾置返回允许我们在参数列表之后声明返回类型
//在编译器遇到函数的参数列表之前,beg并不存在
template <typename It>
auto fcn(It beg,It end) -> decltype(*beg){
	//处理序列
	return *beg;//返回序列中一个元素的引用
}
  1. 组合使用remove_reference、尾置返回及decltype,就可以在函数中返回元素值的拷贝。
//为了使用模板参数的成员,必须用typename
template<typename It>
auto fcn2(It beg, It end)->typename remove_reference<decltype(*beg)>::type
{
	//处理序列
	return *beg; //返回序列中一个元素的拷贝
}

在这里插入图片描述

16.2.4 函数指针和实参推断

  1. 当用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参。
template<typename T> int compare(const T&, const T&);
//fp1指向实例int compare(const int&, const int&)
int (*pf1)(const int&,const int&) = compare;

//func的重载版本;每个版本接受一个不同的函数指针类型
void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); //错误:使用compare的哪个实例?

//正确:显式指出实例化哪个compare版本
func(compare<int>); //传递compare(const int&,const int&)
  1. 当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。

16.2.5 模板实参推断和引用

  1. 只在一种特殊情况下引用会折叠成右值引用:右值引用的右值引用。
  2. 引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数。
  3. 如果一个函数参数是指向模板参数类型的右值引用(如T&&),则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用(T&)。
  4. 在实际中,右值引用通常用于两种情况:模板转发其实参或模板被重载。
  5. 左值传递给函数的右值引用参数(模板类型参数),编译器推断模板类型参数为实参的左值应用类型。
template<typename T> void f1(T&); //实参必须是一个左值
//对f1的调用使用实参所引用的类型作为模板的参数类型
f1(i); 	//i是一个int;模板参数类型T是int
f1(ci); 	//ci是一个const int;模板参数T是const int
f1(5); 	//错误:传递给一个&参数的实参必须是一个左值

template<typename T> void f2(const T&); //可以接受一个右值
//f2中的参数是const &;实参中的const是无关的
//在每个调用中,f2的函数参数都被推断为const int&
f2(i); 	//i是一个int;模板参数T是int
f2(ci); 	//ci是一个const int,但模板参数T是int
f2(5); 	//一个const &参数可以绑定到一个右值;T是int

16.2.6 理解std::move

  1. 标准库move函数是使用右值引用的模板的一个很好的例子。
//move告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它
//标准库是这样定义move的
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

string s1("hi"),s2;
s2 = std::move(string("bye!")); 	//正确:从一个右值移动数据
//string&& move(string &&t)

s2 = std::move(s1); 		//正确:但在赋值之后,s1的值不确定
//string&& move(string &t)
  1. C++11:可以用static_cast显式地将一个左值转换为一个右值引用。

16.2.7 转发

  1. 如果一个函数参数是指向模板类型参数的右值引用(如 T&&),它对应的实参的const属性和左值/右值属性将得到保持。
  2. C++11:当用于一个指向模板参数类型的右值引用函数参数(T&&)时,forward会保持实参类型的所有细节。
template <typename F, typename T1, typename T2>
//forward<T>的返回类型是T&&
void flip(F f, T1 &&t1, T2 &&t2)
{
	f(std::forward<T2>(t2),std::forward<T1>(t1));
}
flip(g,i,42); //i将以int&类型传递给g,42将以int&&类型传递给g
  1. 与std::move相同,对于std::forward不使用using声明是一个好主意。
//flip1是一个不完整的实现:顶层const和引用丢失了
template<typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2)
{
	f(t2,t1);
}

void f(int v1, int &v2) //注意v2是一个引用
{
	cout<<v1<<" "<<++v2<<endl;
}

f(42,i); 	//f改变了实参i
flip1(f,j,42); 	//f不会改变j
//实例化:
//void flip1(void(*fcn)(int, int&), int t1, int t2);
template<typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
	f(t2,t1);
}

void g(int &&i, int& j)
{
	cout<<i<<" "<<j<<endl;
}
flip2(f,j,42); //f会改变j,void flip1(void(*fcn)(int, int&), int& t1, int&& t2);
flip2(g,i,42); //错误:不能从一个左值初始化int&&

16.3 重载与模板

  1. 函数模板可以被另一个模板或一个普通非模板函数重载。
cout << debug_rep("hi world!") << endl; //debug_rep(T*)
//有三个可行的版本
//debug_rep(const T&),T被绑定到char[10]
//debug_rep(T*),T被绑定到const char
//debug_rep(const string&),要求从const char*到string的转换
  1. 当有多个重载模板对一个调用提供同样好的匹配时,应选择最特例化的版本。
//打印任何我们不能处理的类型
template<typename T> string debug_rep(const T &t){
	ostringstream ret; 
	ret << t; //使用T的输出运算符打印t的一个表示形式
	return ret.str();
}

//打印指针的值,后跟指针指向的对象
//注意:此函数不能用于char*;
template<typename T> string debug_rep(T *p){
	ostringstream ret;
	ret << "pointer: " << p; 	//打印指针本身的值
	if(p)
		ret <<" "<<debug_rep(*p); //打印p指向的值
	else
		ret << " null pointer"; 	//或指出p为空
	return ret.str(); 		//返回ret绑定的string的一个副本
}

int main(){
	string s("hi");
	cout << debug_rep(s) << endl ; 	//只能匹配第一个版本
	cout << debug_rep(&s) << endl; 	//第二个版本更精确
	const string *sp = &s;
	cout << debug_rep(sp) << endl; //都是精确匹配,但第二个版本更特别
}
  1. 对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。
//非模板函数
string debug_rep(const string &s){
	return '"' + s + '"';
}

string s("hi");
cout << debug_rep(s) << endl; 
  1. 在定义任何函数之前,记得声明所有重载的函数版本。这样就不必担心编译器由于未遇到希望调用的函数而实例化一个并非所需的版本。
template <typename T> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
//为了使debug_rep(char*)的定义正确工作,下面的声明必须在作用域中
string debug_rep(const string&);
string debug_rep(char *p){
	//如果接受一个const string&的版本的声明不在作用域中,
	//返回语句将调用debug_rep(const T&)的T实例化为string的版本
	return debug_rep(string(p));
}

16.4 可变参数模板

  1. 在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。
//Args是一个模板参数包; rest是一个函数参数包
//Args表示零个或多个模板类型参数
//rest表示零个或多个函数参数
template<typename T, typename... Args>
void foo(const T &t, const Args& ... rest);

//编译器会推断包中参数的数目
int i = 0; double d = 3.14; string s = "how now brown cow";
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]&);
  1. 当需要知道包中有多少元素时,可以使用sizeof…运算符。
template<typename ...Args> void g(Args ...args){
	cout<<sizeof...(Args)<<endl; //类型参数的数目
	cout<<sizeof...(args)<<endl; //函数参数的数目
}

16.4.1编写可变参数函数模板

  1. 当定义可变参数版本的printf时,非可变参数版本的声明必须在作用域中。否则,可变参数版本会无限递归。
//用来终止递归并打印最后一个元素的函数
//此函数必须在可变参数版本的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(cout, i, s, 42); //包中有两个参数

16.4.2 包扩展

  1. 扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。通过在模式右边放一个省略号来触发扩展操作。
  2. 扩展中的模式会独立地应用于包中的每个元素。
//在print调用中对每个实参调用debug_rep
template<typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
	//print(os, debug_rep(a1),debug_rep(a2),...,debug_rep(an)
	return print(os,debug_rep(rest)...);
}

errorMsg(cerr,fncName,code,num(),otherData,"other",item);
//等价于
//print(cerr,debug_rep(fcnName),debug_rep(code,num()),
//	debug_rep(otherData),debug_rep("otherData"),
//	debug_rep(item));

print(os,debug_rep(rest...)); //错误:此调用无匹配函数
print(cerr,debug_rep(fcnName,code.num(),otherData,"otherData",item));

16.4.3 转发参数包

  1. C++11:可以组合使用可变参数模板与forward机制来编写函数,实现将其实参不变地传递给其他函数。
class StrVec{
public:
	template<class... Args> void emplace_back(Args&&...);
//...
};

template<class... Args>
inline
void StrVec::emplace_back(Args&&... args)
{
	chk_n_alloc(); //如果需要的话重新分配StrVec内存空间
	alloc.construct(first_free++,std::forward<Args>(args)...);
}

//假定svec是一个StrVec的对象
svec.emplace_back(10,'c'); //将ccccccccc添加为新的尾元素
//std::forward<int>(10),std::forward<char>(c)
svec.emplace_back(s1+s2); //使用移动构造函数
//std::forward<string>(string("the end"))

16.5 模板特例化

  1. 一个特例化版本就是模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。
  2. 特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参,使用关键字template后跟一个空尖括号对,指出正在实例化一个模板。
//compare的特殊版本,处理字符数组的指针
template<>
int compare(const char* const &p1, const char* const &p2)
{
	return strcmp(p1,p2);
}
  1. 特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。
compare("hi","mom");
//由于是特例化版本,并不是独立的非模板函数
//所以还是调用数组引用的版本
  1. 模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。
  2. 一个类模板的部分特例化本身是一个模板,使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
  3. 只能部分特例化类模板,而不能部分特例化函数模板。
//原始的、最通用的版本
template<class T> struct remove_reference{
	typedef T type;
};
//部分特例化版本,将用于左值引用和右值引用
template<class T> struct remove_reference<T&> //左值引用
{ typedef T type; };
template<class T> struct remove_reference<T&&> //右值引用
{ typedef T type; };

int i;
int& ri= i;
remove_reference<decltype(42)>::type a;
remove_reference<decltype(ri)>::type b;
remove_reference<decltype(std::move(i))>::type c;
  1. 标准库算法都是函数模板,标准库容器都是类模板。
  2. 当我们不能(或不希望)使用模板版本时,可以定义一个特例化版本。
//第一个版本;可以比较任意两个类型
template<typename T> int compare(const T&, const T&);
//第二个版本;处理字符串字面常量
template<size_t N, size_t M>
int compare(const char (&)[N],const char (&)[M]);

const char *p1 = "hi", *p2 = "mom";
//无法将一个指针转换为数组的引用
compare(p1,p2); 		//调用第一个模板
compare("hi","mom"); 	//调用有两个非类型参数的版本
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值