函数模板
定义:template<typename/class T> int compare(const T& a,const T& b) {}
以关键字template开始,后跟一个模板参数列表(不能为空),模板参数列表表示在类或函数定义中用到的类型或值,
实例化函数模板:编译器通常通过函数实参来推断欧版实参,并将它绑定到模板参数T上
模板类型参数:我们一般将模板类形参数看作类型说明符,可以用来指定返回类型和函数参数类型,以及函数体内变量的声明和类型转换
非类型模板参数:一个非类型参数表示一个值而非一个类型,通过一个特定的类型来指定,非类型参数被用户提供的一个值或编译器推断出的值代替,这个值必须是常量表达式;非类型参数也可是一个指向对象或函数的指针和引用(左值),绑定到指针或引用的实参必须有静态的生存期
inline和constexpr:放在模板参数列表后
编写类型无关的代码:
- 模板中的函数参数是const的引用(可以用于不能拷贝的类型)
- 条件判断仅使用<符号(降低对处理类型的要求)
模板编译:实例化出一个特定模板时,编译器才会生成代码,因此大多数编译错误在实例化时报告,模板的头文件通常既包含声明,也包含定义
类模板
定义:template<typename/class T>class Blob{}
note:一个类模板的每个实例都能形成一个完全独立的类
类模板的成员函数:在类外部定义成员函数时,因为成员函数与模板有相同的模板参数,因此定义时以关键字template开始,后接类模板参数列表
template<typename/class T> ret-type Blob::member_name(paraments-list) {}
note:默认情况下,对于一个实例化了的类模板,其成员函数只有在使用时才被实例化
类模板和友元:如果一个类模板包含一个非友元,那么该友元可以访问到所有类型的类模板;如果友元自身是模板,类模板可以授权给所有类型的友元,也可以授权给特定类型的友元
- 一对一友好关系
- 通用和特定的模板关系
c++11新标准:可以将模板类型参数声明为友元
template<typename/class Type> class Bar{ friend Type;}
类模板的static成员:类模板的每个实例都有一个独有的static成员,因此我们将static数据成员也定义为模板
模板参数
模板参数与作用域:一个模板参数名可用的范围是在其声明之后,至模板声明或定义结束之前;一个模板参数名在一个特定的模板参数列表中只能声明一次
使用类的类型成员:编译器直到实例化模板时才知道T::mem是一个类型成员还是一个static数据成员,默认情况下,c++语言假定通过作用域运算符(::)访问的名字不是类型。因此当我们希望使用一个模板参数类型时要显示告诉编译器,用关键字typename来实现,template<typename/class T> typename T::valu _type foo();必须用typename而不能用class
默认模板实参:c++11标准中,我们可以为类模板和函数模板提供默认实参
模板默认实参与类模板:无论何时使用模板,我们都必须在模板名后面加上一对尖括号(<>),指出类必须从一个模板实例化而来,如果我们希望使用默认实参,就需要加上一对空的尖括号
成员模板
一个类(无论是普通类还是模板类)可以包含本身是模板的成员函数,这种函数为成员模板,成员模板不能是虚函数
当在类模板外定义一个成员模板时,必须包含类模板和成员模板的模板参数列表,类模板参数列表在前,成员模板的参数列表在后
实例化与成员模板:为了实例化一个类模板的成员模板,我们需要提供类和函数模板的实参,在哪个对象上调用成员模板,编译器就根据对象的类型来推断成员模板的实参类型。
控制实例化
模板被使用时才会实例化这一特性表明,在多个独立编译的文件中,使用了相同模板并提供了相同的实例化,这一额外开销在大系统中十分严重,所以我们用显示实例化来避免
- 实例化声明:extern template declaration
- 实例化定义:template declaration
当编译器遇到extern声明时,不会在本文件中生成实例化代码,而文件系统中必须有一个文件中又一个实例化的非extern声明,可以有多个extern声明,但只有一个定义
extern声明必须出现在任何使用此实例化代码之前
当编译器遇到实例化定义时,会为其生成代码
实例化定义会实例化所有成员,所用类型必须能用于模板的所有成员函数
eg:vector<someType/NoDefault>在显示实例化时会实例化vector的所有成员,包括设置容器大小的构造函数,该构造函数会根据元素的默认构造函数来设置,如果someType/NoDefault没有默认构造函数则不能显示实例化
- 被调用时会实例化
- 指针和引用不会实例化
- 只有在被分配了空间时,模板才会被完全解析(发生在编译阶段)https://stackoverflow.com/questions/21598635/how-is-a-template-instantiated
类型转换与模板类形参数
如果一个函数模板的形参使用了模板类型参数,那么采用特殊的初始化规则,只有很有限的几种类型转换会应用于这些实参
- const转换:将一个非const对象的引用传递给一个const的引用形参
- 数组或函数指针转换:数组实参可以转换为指向首元素的指针,一个函数形参可以转换为一个该函数的指针
编译器通常不对实参进行类型转换,而是生成一个新的模板实例
一个模板参数类型可以用作多个函数形参的类型,但是传递过去的实参类型必须一致
若函数模板的形参为正常参数类型,则不涉及特殊的初始化规则
函数模板显示实参
template<typename/class T1,typename/class T2,typename/class T3> T1 sum(T2,T3)
sum<long,> 用户显示指定T1为long
对于模板参数类型已经指定的函数实参,可以进行正常的类型转换
尾置返回类型与类型转换
尾置返回允许我们在参数列表后指明函数的返回类型
template<typename/class T> auto fcn(T it)->decltype(*it)
我们可以使用标准库的类型转换模板来获得元素类型,这些模板在头文件type_traits中,例如remove_reference<T.> 若T为某个类型的引用,则remove_reference::type为该类型
函数指针和实参推断
当我们用函数模板来初始化一个函数指针或为函数指针赋值时,编译器通过指针的类型类推断函数模板实参
note:当参数是一个函数模板实例的地址时,程序上下文必须满足;对每个模板参数,能唯一确定其类型或值
模板实参推断和引用
从左值引用函数参数推断类型:如果模板函数参数是T&(左值引用),那么我们的实参只能为左值(一个变量或一个返回引用类型的表达式),实参可以使const,也可以不是;如果一个模板函数参数是const T&,我们可以传递给它任何类型的实参,当函数参数本身是const时,T的推断类型不会是const
右值引用函数参数的推断过程类似左值引用
引用折叠和右值引用参数
通常情况下,不能将一个右值引用绑定到左值上,但是有两个例外的规则
- 在函数模板参数上,将一个左值传递给函数的右值引用参数,且次右值引用指向模板参数(T&&),则编译器推断T为实参左值引用类型(如int&)
- 我们间接创建的引用的引用,则这些引用会折叠,折叠成一个普通的左值引用,只有在右值引用的右值引用下才会折叠成右值引用
- X& &、X& &&、X&& &折叠成X&
- X&& &&折叠成X&&
- 引用折叠只应用于间接创建引用的引用,如类型别名和模板参数
note:如果一个函数参数是指向模板参数类型的右值引用(T&&),则可以传递给模板任何类型的实参,如果将左值传递给这样的参数,则函数参数被实例化为一个左值引用
右值引用通常应用于:模板转发与模板重载
eg模板参数类型推断
template<typename T> void func1(const T&)
template<typename T> void func2(T&)
template<typename T> void func3(T&&)
template<typename T> void func4(T)
main()
{
int i=0;
const int ci=i;
func1(i); T为int
func1(ci); T为int
func1(42); T为int
func2(i); T为int
func2(ci); T为int
func2(42); 错误,42不是左值
func3(i); T为int&(T推断为int& &&折叠成int&)
func3(ci); T为const int&
func3(42); T为int
func4(i); T为int
func4(ci); T为int
func4(42); T为int
}
std::move
标准库函数std::move是使用右值引用参数的一个很好的例子,std::move可以让我们将一个右值引用绑定到左值上,move可以接受任何类型的实参
note:从一个左值static_cast到一个右值引用是允许的
转发
某些函数需要将一个或多个不同的实参地转发给其它函数,我们需要保持被转发参数的所有性质,不论是const还是左值右值属性
- 我们可以将函数参数定义为一个指向模板的右值引用,可以保持其对应实参的所有信息(底层const)(不能用于接受右值引用的函数)
- 对于接受右值引用的函数,我们可以使用标准库函数std::forward来保持其所有属性
eg:
#include <iostream>
using namespace std;
void f(int v1, int& v2)
{
cout << v1 << " " << ++v2 << endl;
}
void g(int&& v1, int& v2) //接受右值引用的函数
{
cout << v1 << " " << v2 << endl;
}
template<typename F, typename T, typename U> void flicp1(F f, T t1, U t2)
{
f(t2, t1);
}
template<typename F, typename T, typename U> void flicp2(F f, T&& t1, U&& t2)
{
f(t2, t1);
}
template<typename F, typename T,typename U> void flicp3(F f, T&& t1, U&& t2)
{
g(t2,t1) //错误,不能使用左值实例化int&&
g(std::forward<T>(t1), std::forward<U>(t2));
//错误,std::forward<T>(t1)返回的是int&& &类型,折叠成int&
g(std::forward<U>(t2), std::forward<T>(t1));
//std::forward<U>(t2)返回int&&(因为t2是普通的int)
}
int main(int argc, char** argv)
{
int i = 0;
flicp1(f, i, 42);
cout << "i=" << i << endl; //没有改变实参i的值
flicp2(f, i, 42);
cout << "i=" << i << endl; //改变了实参i的值
flicp3(g, i, 42);
system("pause");
}
重载与模板
- 匹配的函数中只有一个是非模板函数,选择它
- 匹配的函数中只有模板函数,那么选择那个最特例的