第十六章 模板与泛型编程

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的类型,而非doubledouble 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指向的值elseret << " 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.xdnf.cn/news/1547486.html

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

相关文章

VmWare17直接开箱即用Win10虚拟机

你是否曾想过在电脑上安装一个Windows 10虚拟机来执行一些高风险的操作&#xff1f;比如测试某个文件是否携带病毒&#xff0c;或者想要在隔离的环境中使用电脑&#xff1f;那么&#xff0c;接下来我将为你提供一份详细的Windows 10虚拟机快速启动教程&#xff0c;让你能够轻松…

electron 设置界面右下角打开

功能需求场景 写一个可以下载各种平台的小工具&#xff0c;需要右下角打开方便做其它事情 实现基础 要在屏幕的右下角设置窗口&#xff0c;可以调整mainWindow的创建参数&#xff0c;特别是通过使用x和y坐标来定位窗口 &#xff1b; 需要获取屏幕的尺寸&#xff0c;并据此计算…

计算机的错误计算(一百零五)

摘要 本节探讨多项式的计算精度问题。 例1. 已知多项式 计算 不妨在Visual Studio 2010下编程计算&#xff0c;其中主要语句如下&#xff1a; #include <math.h>double x1234; double c91021263,c8-1260239000,c7565172,c2-21,c031977890.4; double yc9*pow(x,9)c8*…

WSL进阶体验:gnome-terminal启动指南与中文显示问题一网打尽

起因 我们都知道 wsl 启动后就死一个纯命令行终端&#xff0c;一直以来我都是使用纯命令行工具管理Linux的。今天看到网上有人在 wsl 中启动带图形界面的软件。没错&#xff0c;就是在wsl中启动带有图形界面的Linux软件。比如下面这个编辑器。 ​​ 出于好奇&#xff0c;我就…

YOLOv9改进,YOLOv9主干网络替换为GhostNetV3(2024年华为提出的轻量化架构,全网首发),助力涨点

摘要 GhostNetV3 是由华为诺亚方舟实验室的团队发布的,于2024年4月发布。 摘要:紧凑型神经网络专为边缘设备上的应用设计,具备更快的推理速度,但性能相对适中。然而,紧凑型模型的训练策略目前借鉴自传统模型,这忽略了它们在模型容量上的差异,可能阻碍紧凑型模型的性能…

通信工程学习:什么是TDD时分双工

TDD:时分双工 TDD(时分双工,Time Division Duplexing)是一种在移动通信系统中广泛使用的全双工通信技术。以下是TDD的详细解释: 一、定义与原理 TDD是一种通过时间划分来实现双向通信的技术。在TDD模式中,接收和传送在同一频率信道(即载波)的不同时隙…

Chirp通过Sui让IoT世界变得更简单

据估计&#xff0c;未来十年内&#xff0c;联网设备的数量将增长到近400亿台。无论是追踪共享出行车辆的移动、改善食品追溯性、监控制造设施&#xff0c;还是保障家庭安全&#xff0c;物联网 ( Internet of Things&#xff0c;IoT) 对企业和消费者来说都已经成为一项关键技术。…

认知杂谈84《菜鸟的自我修炼:知易行难与行难知易》

内容摘要&#xff1a; 理解与行动之间的差距是日常生活的常见挑战。"知易行难"体现在理解简单但执行困难&#xff0c;例如知道蔬菜有益但难以坚持食用。而"行难知易"则是开始时困难但后来容易的任务&#xff0c;如学习骑自行车。 这种差异源于心理惰性和习…

【ARM 嵌入式 编译系列 10.5 -- ARM toolchain naming convention】

文章目录 ARM 工具链命名规范详细介绍1. arch(架构)2. vendor(供应商)3. os(操作系统)4. abi(应用二进制接口)ABI(应用二进制接口)常见的 ABI 类型工具链命名约定ExamplesABI 合规性ARM 工具链命名规范详细介绍 ARM 工具链的命名规范指示了 GCC 工具链的构建目的和所…

AI 智能体 | 手捏素材选题库 Coze Bot,帮你实现无限输出

做自媒体的同学经常遇到的一个痛点就是无限输出&#xff0c;那怎么才能有源源不断的选题呢&#xff1f;那就是搭建一个选题素材库。 下面就为大家介绍一下基于 Coze Bot 快速搭建素材选题库&#xff0c;希望能让大家才思泉涌。 一、流程拆解 日常素材库积累的过程可以描述为…

WPF项目中使用Caliburn.Micro框架实现日志和主题切换

目录 一、添加Caliburn.Micro框架 二、配置Serilog日志 三、实现主题切换 Caliburn.Micro是MVVM模式的轻量级WPF框架&#xff0c;简化了WPF中的不少用法。这个框架中所有的页面控制都是通过ViewModel去实现的。 以下内容是自己在进行项目实战的同时进行记录的&#xff0c;对于…

使用npm link 把一个本地项目变成依赖,引入到另一个项目中

突然有天,发现线上的项目有块功能缺失,我以为是我优化的时候不小心改坏了什么代码,导致的,先上图 第一反应,就以为天塌了,完全无从入手,然后我就找了之前的离职的同事,他又给我两个包,让我打成依赖扔进去,这两个包分别是scratch-blocks,scratch-vm, 然后我就使用了npm link np…

【HarmonyOS】组件长截屏方案

【HarmonyOS】普通组件与web组件长截屏方案&#xff1a;原则是利用Scroll内的组件可以使用componentSnapshot完整的截屏 【普通组件长截屏】 import { componentSnapshot, promptAction } from kit.ArkUI import { common } from kit.AbilityKit import { photoAccessHelper }…

001、视频添加字幕

1. 腾讯智影 (可用) https://zenvideo.qq.com/ 1.1 操作步骤 https://zenvideo.qq.com/ https://zenvideo.qq.com/my/material?typeexport 上传资源 自动字幕识别 修改字幕 下载字幕 上传字幕 https://zenvideo.qq.com/my/material?typeexport 2. 秒剪–手机版app &a…

华为云分布式缓存服务Redis®版9月企业版、灵活的购买方式全新上市

华为云分布式缓存服务&#xff08;Distributed Cache Service&#xff0c;简称DCS&#xff09;是华为云提供的一款兼容Redis的高速内存数据处理引擎&#xff0c;为您提供即开即用、安全可靠、弹性扩容、便捷管理的在线分布式缓存能力&#xff0c;满足用户高并发及数据快速访问的…

MacOS多桌面调度快捷键

单桌面调度快捷键 可能是我用着妙控鼠标用着不习惯&#xff0c;所以追求快捷键操作&#xff0c;看起来也比较酷。而且在Windows上&#xff0c;我基本不使用多桌面&#xff0c;但是看着同事用Mac的多桌面用的飞起&#xff0c;炫酷程度不亚于win7的Windows键Tab。在不使用多桌面的…

SSM+Vue家教平台系统

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 spring-mybatis.xml3.5 spring-mvc.xml3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作…

水平分库分表的方法策略

分库分表介绍 在当前业务量迅猛增加的情况下&#xff0c;数据库经常面临性能的极致挑战。尤其是在处理大规模的数据集&#xff0c;例如超过千万条数据记录的情况下&#xff0c;SQL查询的性能将显著下降。随着数据量的增加&#xff0c;查询所需要扫描的数据范围变得更广&#x…

AOT源码解析4.4 -decoder生成预测mask并计算loss

3、生成ref_imgs的预测mask和loss 这一步在训练阶段调用 3.1 数据处理 图1&#xff0c;如图1所示&#xff0c;将enc_embs的最后一个比例的特征图和有ref_imgs相关的特征图得到的LSTT特征图相拼接作为输入 curr_enc_embs self.curr_enc_embscurr_lstt_embs self.curr_lstt_o…

了解针对基座大语言模型(类似 ChatGPT 的架构,Decoder-only)的重头预训练和微调训练

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 随着自然语言处理&#xff08;NLP&#xff09;技术的飞速进步&#xff0c;基于 Transformer 架构的大语言模型在众多任务中取得了显著成就。特别是 Decoder-only 架构&#xff0c;如 GPT 系列模型&…