模板是泛型编程的基础。
16.1 定义模板
16.1.1 函数模板
function template 函数模板
关键字 template + 模板参数列表 template parameter list
在模板定义中,模板参数列表不能为空
实例化模板参数 instantiate
编译器推断模板参数
模板的实例:编译器生成的版本
模板类型参数 type parameter
类型参数前必须使用关键字class 或 typename --- 这两个关键字含义相同,可以互换使用
非类型模板参数 nontype parameter
一个非类型参数表示一个值而非一个类型,通过一个特定的类型名来指定非类型参数。
非类型模板参数的模板实参必须是常量表达式
inline和constexpr的函数模板
函数模板可以声明为inline或constexpr的,
inline或constexpr说明符放在模板参数列表之后,返回类型之前:
template<typename T> inline T min(const T &x,const T &y);
编写类型无关的代码
两个重要原则:
(1)模板中的函数参数是const的引用。
保证了函数可以用于不能拷贝的类型,是函数运行更快。
(2)函数体中的条件判断仅使用<比较运算。
这些类型必须支持<,但不必同时支持>。
note:模板程序应该尽量减少对实参类型的要求。
模板编译
实例化一个特定版本时,编译器才会生成代码。
模板的头文件通常既包括声明也包括定义。
模板包含两种名字:那些不依赖于模板参数的名字,那些依赖于模板参数的名字。
大多数编译错误在实例化期间报告:
warning:保证传递给模板的实参支持模板所要求的操作,以及这些操作中能正确工作,是调用者的责任。
16.1.2 类模板
类模板 class template 是用来生成类的蓝图。
编译器不能为类模板推断模板参数
定义类模板
template<typename T> class ClassName
{
public:
private:
}
实例化类模板
显示模板实参(explicit template argument)列表,他们被绑定到模板参数,编译器使用这些模板实参来实例化出特定的类。
ClassName<int> ci;
ClassName<string> cs;
在模板作用域中引用模板类型
类模板的成员函数
类模板的成员函数的实例化
note:默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
在类代码内简化模板类名的使用
note:在一个类模板的作用域内,可以直接使用模板名而不必指定模板实参。
类模板和友元
模板类型别名
C++11 允许我们为类模板定义一个类型别名。
template<typename T> using twin=pair<T,T>;
twin<string> pair<string,string> 等价
类模板的static成员
所有ClassName<X>类型的对象共享相同的static成员。
①通过类类型对象来访问一个类模板的static成员。
②通过作用域运算符直接访问static成员。(必须实例化)
ClassName<int>::static_member;
16.1.3 模板参数
模板参数与作用域
模板参数遵循普通的作用域规则。
模板参数会隐藏外层作用域中声明的相同名字。
在模板内不能重用模板参数名。
模板声明
模板声明必须包含模板参数,
与函数参数相同,声明中的模板参数的名字不必与定义中相同,但一个给定的模板的每个声明和定义必须有相同数量和种类(即,类型和非类型)的参数。
使用类的类型成员
默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。
note:当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。
默认模板实参
default template argument
C++11 中,允许为函数和类模板提供默认实参。
与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。
模板默认实参与类模板
无论何时使用类模板,都必须在模板名之后接上<>
如果一个类模板为其所有模板参数都提供了默认实参,且我们希望使用这些默认实参,必须在模板名之后跟一个空的<>
16.1.4 成员模板
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板 member template。
成员模板不能是虚函数。
普通类的成员模板
类模板的成员模板
类和成员模板各自有自己的、独立的模板参数。
实例化与成员模板
与普通函数模板相同,编译器通常根据传递给成员模板的函数实参来推断它的模板实参。
16.1.5 控制实例化
当模板被使用时才会进行实例化。
显式实例化(explicit instantiation):避免在多个文件中实例化相同模板的额外开销。
形式:
extern template declaration; //实例化声明
template declaration; //实例化定义
extern 表示承诺在程序其他位置有该实例化的一个非 extern 声明(定义)。
warning:对每一个实例化声明,在程序中某个位置必须有其显式的实例化定义。
实例化定义 会 实例化所有成员。
16.1.6 效率与灵活性
16.2 模板实参推断
对于函数模板,编译器利用调用中的函数实参来确定其模板参数。
模板实参推断(template argument deduction):从函数实参来确定模板实参的过程。
16.2.1 类型转换与模板类型参数
note:将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有:const转换、数组或函数到指针的转换。
使用相同模板参数类型的函数形参
正常类型转换应用于普通函数实参
16.2.2 函数模板显式实参
explicit template argument
在某些情况下,编译器无法判断出模板实参的类型。--- 需要显式实参
template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);
显式模板实参在<>中给出,位于函数名之后,实参列表之前。
auto val=sum<long long>(i,lng); //long long sum(int,long)
显式模板实参按由左至右的顺序与对应的模板参数匹配。 //顺序很重要
正常类型转换应用于显式指定的实参
16.2.3 尾置返回类型与类型转换
并不知道返回结果的准确类型,可从所需类型中推断。例如:
template<typename It>
auto fcn(It beg,It end)->decltype(*beg)
{
//...
return *beg;
}
进行类型转换的标准库模板
标准库的类型转换模板 type transformation --- 定义在头文件 #include <type_traits> 中。这个头文件中的类通常用于模板元程序设计。但是,类型转换模板在普通编程中也很有用。
表 16.1 :标准类型转换模板
16.2.4 函数指针和实参推断
//TODO
16.2.5 模板实参推断和引用
//TODO
16.2.6 理解 std::move
//TODO
16.2.7 转发
转发:某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。 --- 需要保持被转发实参的所有性质(const属性、左值/右值属性)。
note:如果一个函数参数是指向模板类型参数的右值引用(如T&&),它对应的实参的const属性和左值/右值属性将得到保持。
在调用中使用 std::forward 保持类型信息
新标准库设施forward定义在头文件#include <utility> 中。必须通过显式模板实参来调用。返回该显式实参类型的右值引用,即,forward<T>的返回类型是T&&
16.3 重载与模板
函数模板可以被另一个模板或一个普通非模板函数重载。名字相同的函数必须具有不同数量或类型的参数。
函数匹配规则:
16.4 可变参数模板
可变参数模板(variadic template):一个接受可变数目参数的模板函数或模板类。
参数包(parameter packet):可变数目的参数。两种:
模板参数包:表示零个或多个模板参数。
函数参数包:表示零个或多个函数参数。
参数包的表示:我们用一个省略号(...)来指出一个模板参数或函数参数表示一个包。
class...或typename...指出接下来的参数表示零个或多个类型的列表。
一个类型名后名跟一个省略号表示零个或多个给定类型的非类型参数的列表。
eg:
template<typename T,typename... Args>
void foo(const T &t,const Args&... rest);
sizeof... 运算符
包中元素的数目。
cout<<sizeof...(Args)<<endl; //类型参数的数目
cout<<sizeof...(rest)<<endl; //函数参数的数目
16.4.1 编写可变参数函数模板
16.4.2 包扩展
16.4.3 转发参数包
16.5 模板特例化
当我们不能(或不希望)使用模板版本时,可以定义类或函数模板的一个特例化版本。
模板特例化(template specialization):模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。
16.5.1 函数模板特例化
特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。
形式:
关键字 template<> (空尖括号),指出我们将为原模板的所有模板参数提供实参。
eg://特例化compare的特殊版本
template<>
int compare(const char* const &p1,const char* const &p2)
{
return strcmp(p1,p2);
}
函数重载与模板特例化
note:特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。
当一个非模板函数提供与函数模板同样好的匹配时,编译器会选择非模板版本。
16.5.2 类模板特例化
template<>
class ClassName<sales_datas>
{
//...
}
类模板部分特例化
与函数模板特例化不同,类模板的特例化不必为所有模板参数提供实参。
类模板部分特例化(partial specialization):本身是一个模板,使用时必须为那些在特例化版本中未指定的模板参数提供实参。
note:我们只能部分特例化类模板,而不能特例化函数模板。
我们 可以 只 特例化特定成员函数而不是特例化整个模板。其他成员将由类模板提供。
16.1 定义模板
16.1.1 函数模板
function template 函数模板
关键字 template + 模板参数列表 template parameter list
在模板定义中,模板参数列表不能为空
实例化模板参数 instantiate
编译器推断模板参数
模板的实例:编译器生成的版本
模板类型参数 type parameter
类型参数前必须使用关键字class 或 typename --- 这两个关键字含义相同,可以互换使用
非类型模板参数 nontype parameter
一个非类型参数表示一个值而非一个类型,通过一个特定的类型名来指定非类型参数。
非类型模板参数的模板实参必须是常量表达式
inline和constexpr的函数模板
函数模板可以声明为inline或constexpr的,
inline或constexpr说明符放在模板参数列表之后,返回类型之前:
template<typename T> inline T min(const T &x,const T &y);
编写类型无关的代码
两个重要原则:
(1)模板中的函数参数是const的引用。
保证了函数可以用于不能拷贝的类型,是函数运行更快。
(2)函数体中的条件判断仅使用<比较运算。
这些类型必须支持<,但不必同时支持>。
note:模板程序应该尽量减少对实参类型的要求。
模板编译
实例化一个特定版本时,编译器才会生成代码。
模板的头文件通常既包括声明也包括定义。
模板包含两种名字:那些不依赖于模板参数的名字,那些依赖于模板参数的名字。
大多数编译错误在实例化期间报告:
warning:保证传递给模板的实参支持模板所要求的操作,以及这些操作中能正确工作,是调用者的责任。
16.1.2 类模板
类模板 class template 是用来生成类的蓝图。
编译器不能为类模板推断模板参数
定义类模板
template<typename T> class ClassName
{
public:
private:
}
实例化类模板
显示模板实参(explicit template argument)列表,他们被绑定到模板参数,编译器使用这些模板实参来实例化出特定的类。
ClassName<int> ci;
ClassName<string> cs;
在模板作用域中引用模板类型
类模板的成员函数
类模板的成员函数的实例化
note:默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
在类代码内简化模板类名的使用
note:在一个类模板的作用域内,可以直接使用模板名而不必指定模板实参。
类模板和友元
模板类型别名
C++11 允许我们为类模板定义一个类型别名。
template<typename T> using twin=pair<T,T>;
twin<string> pair<string,string> 等价
类模板的static成员
所有ClassName<X>类型的对象共享相同的static成员。
①通过类类型对象来访问一个类模板的static成员。
②通过作用域运算符直接访问static成员。(必须实例化)
ClassName<int>::static_member;
16.1.3 模板参数
模板参数与作用域
模板参数遵循普通的作用域规则。
模板参数会隐藏外层作用域中声明的相同名字。
在模板内不能重用模板参数名。
模板声明
模板声明必须包含模板参数,
与函数参数相同,声明中的模板参数的名字不必与定义中相同,但一个给定的模板的每个声明和定义必须有相同数量和种类(即,类型和非类型)的参数。
使用类的类型成员
默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。
note:当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。
默认模板实参
default template argument
C++11 中,允许为函数和类模板提供默认实参。
与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。
模板默认实参与类模板
无论何时使用类模板,都必须在模板名之后接上<>
如果一个类模板为其所有模板参数都提供了默认实参,且我们希望使用这些默认实参,必须在模板名之后跟一个空的<>
16.1.4 成员模板
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板 member template。
成员模板不能是虚函数。
普通类的成员模板
类模板的成员模板
类和成员模板各自有自己的、独立的模板参数。
实例化与成员模板
与普通函数模板相同,编译器通常根据传递给成员模板的函数实参来推断它的模板实参。
16.1.5 控制实例化
当模板被使用时才会进行实例化。
显式实例化(explicit instantiation):避免在多个文件中实例化相同模板的额外开销。
形式:
extern template declaration; //实例化声明
template declaration; //实例化定义
extern 表示承诺在程序其他位置有该实例化的一个非 extern 声明(定义)。
warning:对每一个实例化声明,在程序中某个位置必须有其显式的实例化定义。
实例化定义 会 实例化所有成员。
16.1.6 效率与灵活性
16.2 模板实参推断
对于函数模板,编译器利用调用中的函数实参来确定其模板参数。
模板实参推断(template argument deduction):从函数实参来确定模板实参的过程。
16.2.1 类型转换与模板类型参数
note:将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有:const转换、数组或函数到指针的转换。
使用相同模板参数类型的函数形参
正常类型转换应用于普通函数实参
16.2.2 函数模板显式实参
explicit template argument
在某些情况下,编译器无法判断出模板实参的类型。--- 需要显式实参
template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);
显式模板实参在<>中给出,位于函数名之后,实参列表之前。
auto val=sum<long long>(i,lng); //long long sum(int,long)
显式模板实参按由左至右的顺序与对应的模板参数匹配。 //顺序很重要
正常类型转换应用于显式指定的实参
16.2.3 尾置返回类型与类型转换
并不知道返回结果的准确类型,可从所需类型中推断。例如:
template<typename It>
auto fcn(It beg,It end)->decltype(*beg)
{
//...
return *beg;
}
进行类型转换的标准库模板
标准库的类型转换模板 type transformation --- 定义在头文件 #include <type_traits> 中。这个头文件中的类通常用于模板元程序设计。但是,类型转换模板在普通编程中也很有用。
表 16.1 :标准类型转换模板
16.2.4 函数指针和实参推断
//TODO
16.2.5 模板实参推断和引用
//TODO
16.2.6 理解 std::move
//TODO
16.2.7 转发
转发:某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。 --- 需要保持被转发实参的所有性质(const属性、左值/右值属性)。
note:如果一个函数参数是指向模板类型参数的右值引用(如T&&),它对应的实参的const属性和左值/右值属性将得到保持。
在调用中使用 std::forward 保持类型信息
新标准库设施forward定义在头文件#include <utility> 中。必须通过显式模板实参来调用。返回该显式实参类型的右值引用,即,forward<T>的返回类型是T&&
16.3 重载与模板
函数模板可以被另一个模板或一个普通非模板函数重载。名字相同的函数必须具有不同数量或类型的参数。
函数匹配规则:
16.4 可变参数模板
可变参数模板(variadic template):一个接受可变数目参数的模板函数或模板类。
参数包(parameter packet):可变数目的参数。两种:
模板参数包:表示零个或多个模板参数。
函数参数包:表示零个或多个函数参数。
参数包的表示:我们用一个省略号(...)来指出一个模板参数或函数参数表示一个包。
class...或typename...指出接下来的参数表示零个或多个类型的列表。
一个类型名后名跟一个省略号表示零个或多个给定类型的非类型参数的列表。
eg:
template<typename T,typename... Args>
void foo(const T &t,const Args&... rest);
sizeof... 运算符
包中元素的数目。
cout<<sizeof...(Args)<<endl; //类型参数的数目
cout<<sizeof...(rest)<<endl; //函数参数的数目
16.4.1 编写可变参数函数模板
16.4.2 包扩展
16.4.3 转发参数包
16.5 模板特例化
当我们不能(或不希望)使用模板版本时,可以定义类或函数模板的一个特例化版本。
模板特例化(template specialization):模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。
16.5.1 函数模板特例化
特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。
形式:
关键字 template<> (空尖括号),指出我们将为原模板的所有模板参数提供实参。
eg://特例化compare的特殊版本
template<>
int compare(const char* const &p1,const char* const &p2)
{
return strcmp(p1,p2);
}
函数重载与模板特例化
note:特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。
当一个非模板函数提供与函数模板同样好的匹配时,编译器会选择非模板版本。
16.5.2 类模板特例化
template<>
class ClassName<sales_datas>
{
//...
}
类模板部分特例化
与函数模板特例化不同,类模板的特例化不必为所有模板参数提供实参。
类模板部分特例化(partial specialization):本身是一个模板,使用时必须为那些在特例化版本中未指定的模板参数提供实参。
note:我们只能部分特例化类模板,而不能特例化函数模板。
我们 可以 只 特例化特定成员函数而不是特例化整个模板。其他成员将由类模板提供。