第十六章 模板与泛型编程

news2024/11/17 6:27:13

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"); 	//调用有两个非类型参数的版本

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2167338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

乱篇弹(54)让子弹飞

创作者在知乎能挣到钱吗&#xff1f; 芝士平台的答案&#xff1a;“当然能&#xff0c;在知乎&#xff0c;无论是各领域的优秀回答者&#xff0c;还是拥有几百或几千关注者的潜力创作者&#xff0c;甚至是只在知乎创作过几篇回答的新人创作者&#xff0c;都有可能在知乎赚钱 。…

[Linux]从零开始的Linux的远程方法介绍与配置教程

一、为什么需要远程Linux 相信大家在学习Linux时&#xff0c;要么是使用Linux的虚拟机或者在物理机上直接安装Linux。这样确实非常方便&#xff0c;我们也能直接看到Linux的桌面或者终端。既然我们都能直接看到终端或者Linux的桌面了&#xff0c;那我们为什么还要远程Linux呢&a…

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯&#xff08;IM&#xff09;系统和实时通信应用中&#xff0c;WebSocket作为一种高效的双向通信协议&#xff0c;得到了广泛应用。然而&#xff0c;在实际使用中&#xff0c;如何确保消息的可靠传输…

ai智能抠图有哪些?我只告诉你这些

在广告、设计、摄影以及视频剪辑等创意领域&#xff0c;抠图技术就像是一把神奇的钥匙&#xff0c;能够将图片中的精彩瞬间或独特元素巧妙地分离出来&#xff0c;并融入到全新的背景之中&#xff0c;创造出无限的可能性。 当面对复杂图形的挑战时&#xff0c;使用高效的在线智…

RabbitMQ基础使用

1.MQ基础介绍 同步调用 OpenFeign的调用。这种调用中&#xff0c;调用者发起请求后需要等待服务提供者执行业务返回结果后&#xff0c;才 能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态&#xff0c;因此我们称这种调用方式为同步调用 异步调用 异步调用通…

Lucene 倒排索引原理详解:深入探讨相关算法设计

引言 随着互联网的快速发展&#xff0c;数据量呈现爆炸性的增长&#xff0c;如何从海量数据中快速准确地获取所需信息成为了一项挑战。全文搜索引擎的出现极大地解决了这个问题&#xff0c;而 Lucene 正是一款优秀的开源全文搜索引擎库。本文将深入探讨 Lucene 的核心技术之一…

NtripShare测量机器人自动化监测系统测站更换仪器后重新设站

NtripShare测量机器人自动化监测系统投入商业运营已经很久了&#xff0c;在MosBox与自动优化网平差技术的加持下&#xff0c;精度并不让人担心&#xff0c;最近基于客户需求处理了两个比较大的问题。 1、增加对反射片和免棱镜的支持。 2、进一步优化测站更换仪器或重新整平后重…

顶点缓存对象(VBO)与顶点数组对象(VAO)

我们的顶点数组在CPU端的内存里是以数组的形式存在,想要GPU去绘制三角形,那么需要将这些数据传输给GPU。那这些数据在显存端是怎么存储的呢?VBO上场了,它代表GPU上的一段存储空间对象,表现为一个unsigned int类型的变量,GPU端内存对象的一个ID编号、地址、大小。一个VBO对…

Cpp内存管理(7)

文章目录 前言一、C/C内存区域划分二、C/C动态内存管理C语言动态内存管理C动态内存管理对于内置类型对于自定义类型 三、new和delete的底层实现四、new和delete的实现原理五、定位new六、malloc/free和new/delete的区别总结 前言 软件开发过程中&#xff0c;内存管理的重要性不…

vue3中echarts柱状图横轴文字太多放不下怎么解决

问题&#xff1a;在做数据展示的时候&#xff0c;使用的是echarts&#xff0c;遇到了个问题&#xff0c;就是数据过多&#xff0c;但是设置的x轴的文字名称又太长&#xff0c;往往左边第一个或右边最后一个的名称展示不全&#xff0c;只有半个。 从网上找到了几种办法&#xff…

进击J8:Inception v1算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、实验目的&#xff1a; 了解并学习图2中的卷积层运算量的计算过程了解并学习卷积层的并行结构与1x1卷积核部分内容&#xff08;重点&#xff09;尝试根据模…

pdf转换成word有哪些方法?10种将PDF转成word的方法

pdf转换成word有哪些方法&#xff1f;在数字化世界中&#xff0c;PDF和word文档是最常用的两种文件格式。PDF凭借其固定布局和跨平台的兼容性&#xff0c;成为了文件分享的首选&#xff0c;而word则因其灵活的编辑功能被广泛应用于各种文本处理需求。在许多情况下&#xff0c;我…

高效IaC测试利器:AlibabaCloud ROS-Tool-Iact3快速上手

在云计算时代&#xff0c;基础设施即代码&#xff08;Infrastructure as Code, IaC&#xff09;已成为提升运维效率、实现自动化部署的重要手段。为了进一步简化IaC模板的测试流程&#xff0c;alibabacloud-ros-tool-iact3工具应运而生&#xff0c;它专为Terraform和阿里云资源…

再次重温 Spring 中 Bean 的生命周期

Bean的生命周期 Spring中的bean的生命周期主要包含四个阶段&#xff1a;实例化Bean --&#xff1e; Bean属性填充 --&#xff1e; 初始化Bean --&#xff1e;销毁Bean 首先是实例化Bean&#xff0c;当客户向容器请求一个尚未初始化的bean时&#xff0c;或初始化bean的时候需要…

Java 导出excel

1.导出excel 带合计 如&#xff1a; public void export(DriverAndGuestMealQueryVO vo) {DriverAndGuestMealListDTO riceLiquidationPage page(vo);// 创建一个Excel工作簿Workbook workbook new XSSFWorkbook();// 创建一个工作表sheetSheet sheet workbook.createShee…

网络通信——DHCP

目录 一.DHCP应用场景 二.通信过程 三.DHCP报文 四.DHCP通信原理 &#xff08;1&#xff09;租借过程 &#xff08;2&#xff09;DHCP 租期更新 &#xff08;3&#xff09;DHCP重绑定 五.一般路由器的DHCP支持两种地址池 &#xff08;1&#xff09;接口地址池 &…

Html jquery下拉select美化插件——selectFilter.js

1. Html jquery下拉select美化插件——selectFilter.js jQuery是一个广泛使用的JavaScript库&#xff0c;它简化了DOM操作、事件处理、动画以及Ajax交互&#xff0c;使得开发者能更高效地构建交互式网页。在本案例中&#xff0c;jquery.selectlist.js插件正是基于jQuery构建的&…

复旦大学附属中山医院院士团队论文遭遇质疑

近日&#xff0c;一篇发表于肝脏领域顶级期刊《Hepatology》(IF:17.1;Q1&#xff09;杂志的肝细胞癌研究论文因图像数据的相似性问题受到质疑。该论文题为‘Protein tyrosine phosphatase receptor S acts as a metastatic suppressor in hepatocellular carcinoma by control …

找不到concrt140.dll怎么修复,这4种方法可轻松搞定

1. concrt140.dll 定义 1.1 系统文件 concrt140.dll 是一个系统文件&#xff0c;属于 Windows 操作系统中重要的动态链接库&#xff08;DLL&#xff09;之一。它通常位于系统的 System32 或 SysWOW64 文件夹中&#xff0c;是 Microsoft Visual C 2015 Redistributable 包的一…

如何在产品上扩大储存?教你一招简单好用的!

你是不是经常遇到需要扩大库存的问题&#xff1f;毕竟总是有很多文件需要存储&#xff1a;视频、音频、文件。。。 但是芯片的空间寸土寸金呀&#xff01; 内部不够只能外扩&#xff0c;然后就是要编写各种驱动&#xff0c;还有Flash替换。。。怎么听着就头疼&#xff01; 教…