函数模板
定义:template
以关键字template开始,后跟一个模板参数列表(不能为空),模板参数列表表示在类或函数定义中用到的类型或值,
实例化函数模板:编译器通常通过函数实参来推断欧版实参,并将它绑定到模板参数T上
模板类型参数:我们一般将模板类形参数看作类型说明符,可以用来指定返回类型和函数参数类型,以及函数体内变量的声明和类型转换
非类型模板参数:一个非类型参数表示一个值而非一个类型,通过一个特定的类型来指定,非类型参数被用户提供的一个值或编译器推断出的值代替,这个值必须是常量表达式;非类型参数也可是一个指向对象或函数的指针和引用(左值),绑定到指针或引用的实参必须有静态的生存期
inline和constexpr:放在模板参数列表后
编写类型无关的代码:
模板编译:实例化出一个特定模板时,编译器才会生成代码,因此大多数编译错误在实例化时报告,模板的头文件通常既包含声明,也包含定义
类模板
定义:template
note:一个类模板的每个实例都能形成一个完全独立的类
类模板的成员函数:在类外部定义成员函数时,因为成员函数与模板有相同的模板参数,因此定义时以关键字template开始,后接类模板参数列表
template
note:默认情况下,对于一个实例化了的类模板,其成员函数只有在使用时才被实例化
类模板和友元:如果一个类模板包含一个非友元,那么该友元可以访问到所有类型的类模板;如果友元自身是模板,类模板可以授权给所有类型的友元,也可以授权给特定类型的友元
c++11新标准:可以将模板类型参数声明为友元
template
类模板的static成员:类模板的每个实例都有一个独有的static成员,因此我们将static数据成员也定义为模板
模板参数
模板参数与作用域:一个模板参数名可用的范围是在其声明之后,至模板声明或定义结束之前;一个模板参数名在一个特定的模板参数列表中只能声明一次
使用类的类型成员:编译器直到实例化模板时才知道T::mem是一个类型成员还是一个static数据成员,默认情况下,c++语言假定通过作用域运算符(::)访问的名字不是类型。因此当我们希望使用一个模板参数类型时要显示告诉编译器,用关键字typename来实现,template
默认模板实参:c++11标准中,我们可以为类模板和函数模板提供默认实参
模板默认实参与类模板:无论何时使用模板,我们都必须在模板名后面加上一对尖括号(<>),指出类必须从一个模板实例化而来,如果我们希望使用默认实参,就需要加上一对空的尖括号
成员模板
一个类(无论是普通类还是模板类)可以包含本身是模板的成员函数,这种函数为成员模板,成员模板不能是虚函数
当在类模板外定义一个成员模板时,必须包含类模板和成员模板的模板参数列表,类模板参数列表在前,成员模板的参数列表在后
实例化与成员模板:为了实例化一个类模板的成员模板,我们需要提供类和函数模板的实参,在哪个对象上调用成员模板,编译器就根据对象的类型来推断成员模板的实参类型。
控制实例化
模板被使用时才会实例化这一特性表明,在多个独立编译的文件中,使用了相同模板并提供了相同的实例化,这一额外开销在大系统中十分严重,所以我们用显示实例化来避免
当编译器遇到extern声明时,不会在本文件中生成实例化代码,而文件系统中必须有一个文件中又一个实例化的非extern声明,可以有多个extern声明,但只有一个定义
extern声明必须出现在任何使用此实例化代码之前
当编译器遇到实例化定义时,会为其生成代码
实例化定义会实例化所有成员,所用类型必须能用于模板的所有成员函数
eg:vector
类型转换与模板类形参数
如果一个函数模板的形参使用了模板类型参数,那么采用特殊的初始化规则,只有很有限的几种类型转换会应用于这些实参
一个模板参数类型可以用作多个函数形参的类型,但是传递过去的实参类型必须一致
若函数模板的形参为正常参数类型,则不涉及特殊的初始化规则
函数模板显示实参
template
sum
对于模板参数类型已经指定的函数实参,可以进行正常的类型转换
尾置返回类型与类型转换
尾置返回允许我们在参数列表后指明函数的返回类型
template
我们可以使用标准库的类型转换模板来获得元素类型,这些模板在头文件type_traits中,例如remove_reference
函数指针和实参推断
当我们用函数模板来初始化一个函数指针或为函数指针赋值时,编译器通过指针的类型类推断函数模板实参
note:当参数是一个函数模板实例的地址时,程序上下文必须满足;对每个模板参数,能唯一确定其类型或值
模板实参推断和引用
从左值引用函数参数推断类型:如果模板函数参数是T&(左值引用),那么我们的实参只能为左值(一个变量或一个返回引用类型的表达式),实参可以使const,也可以不是;如果一个模板函数参数是const T&,我们可以传递给它任何类型的实参,当函数参数本身是const时,T的推断类型不会是const
右值引用函数参数的推断过程类似左值引用
引用折叠和右值引用参数
通常情况下,不能将一个右值引用绑定到左值上,但是有两个例外的规则
note:如果一个函数参数是指向模板参数类型的右值引用(T&&),则可以传递给模板任何类型的实参,如果将左值传递给这样的参数,则函数参数被实例化为一个左值引用
右值引用通常应用于:模板转发与模板重载
eg模板参数类型推断
template void func1(const T&)
template void func2(T&)
template void func3(T&&)
template 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还是左值右值属性
eg:
#include
using namespace std;
void f(int v1, int& v2)
{
cout << v1 << " " << ++v2 << endl;
}
void g(int&& v1, int& v2) //接受右值引用的函数
{
cout << v1 << " " << v2 << endl;
}
template void flicp1(F f, T t1, U t2)
{
f(t2, t1);
}
template void flicp2(F f, T&& t1, U&& t2)
{
f(t2, t1);
}
template void flicp3(F f, T&& t1, U&& t2)
{
g(t2,t1) //错误,不能使用左值实例化int&&
g(std::forward(t1), std::forward(t2));
//错误,std::forward(t1)返回的是int&& &类型,折叠成int&
g(std::forward(t2), std::forward(t1));
//std::forward(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");
}
重载与模板