前言
《C++ Templates Complete Guide 2nd Edition》涵盖了C++11,C++14和C++17的所有内容。如果你的C++编程修为想更进一步的话,那么这本书是一本非常好的C++编程进阶书籍,阅读这本书的必须具备了C++中等程度以后的基础,读这本书没什么压力。虽然大部分写C++模板技术的技术文章,没有显式地说明,但我认为专攻C++方向,这是学习路线图中一个比较正确的顺序(要点不全).请确保你深入学习模板之前,最起码要确保有如下C++基础。
- C++类型系统:熟悉数据类型与转换机制。
- C++STD基础的常用的容器和函数API有所了解。
- C++面向对象(对类接口,类定义,继承与多态有所了解)
- 熟悉C指针与内存管理有所了解。
我一直深受一句话的影响:“C的灵魂是指针,而后继者C++的核心技术就是模板”,因为C++ 标准库本身各种类库和函数API就大规模地使用了模板技术。
这本书绝对是一本良心的书籍,因为从第一版到第二版作者一直坚持不懈地跟随C++新版的步伐,扩充了有关模板技术的新内容。网上那些所谓的阿猫啊狗“原创”的C++模板技术培训班用到教学素材要么来自这本书的第一版或者第二版。因此,我认为要完全透彻掌握C++模板技术,一定要看这本书的英文原版。
- 一来是从精神上支持该书的原作者,不吝啬那么一点零花钱的话可以购买该书授权副本。
- 二来因为有关该书中的一些专业术语(英文),这本书如果解答得不全的,你可以stackflow或者去google会找到更多模板技术的相关内容,我认为发散性地去慢慢品读这本书。
如果你的C++编程技能偏向于为其他高层语言例如Python/Cython/Java等写更底层类库,函数API和专攻算法实现,那么这本书是必读的。就我目前的程度而言,要实现用C++现实各种常用的算法,我认为完成Part 1的模板知识已经够用了,如果需要升华到融合目前主流的设计模式并设计出自己独有的编程模式的,那么可能要将这本书啃完。这本书分三卷《Part1》,《Part2》和《Part3》。我前期的读书随笔,仅会完成Part1。
罗嗦的话说到这里,我们进入正题
攻略要点
基本术语入门,按图说话,正入下文。
下面的函数模板定义了一个关于max的函数族,其中T是模板的模板参数(Template Parameter)可以表示一个具体的数据类型,例如int,double,std::string等等...
template<typename T>
T max(T a, T b){return b<a?a:b;}
其中的typename关键字来定义模板参数,也可以使用class关键字替换
调用示例
int main()
{
auto r1=::max(2.3,44.44);
auto r2=::max(77.23,52);
auto r3=::max("Hello-World!!","IT-dog");
std::cout<<"r1:"<<r1<<std::endl;
std::cout<<"r2:"<<r2<<std::endl;
std::cout<<"r3:"<<r3<<std::endl;
return 0;
}
上面的调用示例,max函数模板没有被编译成可以处理任何类型的单一实体。 相反,对于使用该模板的每种类型,都会从模板生成不同的实体。 也就是编译后会有三个关于max函数的重载版本。因此,将针对这三种类型中的每种类型编译max()。 例如,
返回r1对应的max函数体
double max(double a,double b);
返回r2对应的函数体是
double max(double a,int b);
返回r3对应的max函数签名
std::string max(std::string a,std::string b);
那么上面关于函数max的重载集中,不同的重载版本的参数类型,C++的模板系统是如下推导的呢?
模板参数的推导过程
当我们为某些参数调用函数模板(例如max())时,模板参数的最终类型由我们传递的参数的类型确定的。 如果我们将两个int传递给参数类型T,则C ++编译器就得知T必须为int。
请注意,在类型推导过程中,自动类型转换受到限制:
通过引用声明调用参数时,即使是trivial的转换也不适用于类型推导。 使用相同模板参数T声明的两个参数必须完全匹配。
当按值声明调用参数时,仅支持微妙的退化转换:const或volatile的限定将被忽略,引用转换为引用的类型,原始数组或函数转换为相应的指针类型。 对于使用相同模板参数T声明的两个参数,退化类型必须匹配。
例如
template<typename T>
T max(T a,T b){
return a>b?a:b;
}
int c=72;
const int b=42;
max(b,c); //T会被推导为int
max(c,c); //T会被推导为int
int i=74;
int &r=i;
max(i,b); //T会被推导为 int类型
int arr[3];
foo(&r,arr); //T会被推导为int*
然而下面向模板参数传递的第一个参数是int类型,第二个参数是double类型,但max函数模板的模板参数和返回类型只定义了T,C++编译器要么匹配int max(int,int),要么匹配double max(double,double)
max(4,7.2);
避免这种编译时的的错误,我们在调用代码中会最好显式传入模板参数
max<double>(4,7.2);
或
max(static_cast<double>(4),7.2)
这样C++编译器可以推导为 模板参数T为double,但后一种,我个人是不太喜欢这样书写代码的。
默认参数的类型推导
类型推导不使用与默认调用参数
template<typename T>
void foo(T=""){}
int main()
{
foo();
return 0;
}
为了支持这种情况,我们必须为模板参数声明一个默认的参数类型,
template<typename T=std::string>
void foo(T=""){}
int main()
{
foo();
return 0;
}
下面的示例,C++编译器能够推导的模板参数T为std::string类型,即生成的void foo(std::string)
多个模板参数
上面的max模板参数,我们可以为多个不同模板参数,下面的例子,我们我们在template的模板参数列表,定义了两个模板参数,分别是T1,T2表明它们告知编译器可以匹配两个不同类型的参数,并且max函数模板的返回类型为T1,表明匹配时,返回类型取决于在匹配时第一个模板参数推导的类型
template<typename T1,typename T2>
T1 max(T1 x,T2 y){
return x>=y?x:y;
}
auto r1=::max(4,7.2);
auto r2=::max(24.5,8);
上面例子中r1对应的max函数重载版本为int max(int,double),因为在调用函数中,向max的模板函数传入的第一个参数是4,是一个int类型,而该类型决定了模板函数的返回的类型是int。
同理,r2对应的max函数重载版本为double max(double,int);
有时这样的情况,不是我们所期望的。因此有集中不同的解决方法。
1.3.1返回类型的模板参数
- 可以在模板参数队列里额外指定第三个模板参数,但你必须在调用者函数中要显式指定返回的类型,例如下面的例子,显式告知编译器返回的类型是double
template<typename T1,typename T2,typename RT>
RT max(T1 a,T1 b){ ....}
int main(void){
::max<int,double,double>(44,63.2);
}
我个人不喜欢这种方式,可以再次改进一下上面的例子
template<typename RT,typename T1,typename T2>
RT max(T1 a, T2 b){
return a>b?a:b;
}
int main(void){
::max<double>(44,63.2);
}
上面的例子,仅显式指定返回类型为double,剩下的模板参数由传入参数的类型来推导。
1.3.2推导返回类型
- 从C++14开始,模板函数的返回类型可以通过auto关键字,告知编译器在编译时推导。
#include <type_trains>
template<typename T1,typename T2>
auto max(T1 a,T2 b)-> typename std::decay<decltype(a>b?a:b)>::type{
return a>b?a:b;
}
int main(void){
auto r1=::max(4,7.2);
auto r2=::max(22.1,8.3);
std::cout<<"r1:"<<r1<<std::endl;
std::cout<<"r2:"<<r2<<std::endl;
}
实际上,对返回类型使用auto而不使用相应的尾随返回类型(将在末尾添加->)表
示必须从函数体内的return语句推导出实际的返回类型.这是使用类型特征std::decay<>,它以成员类返回结果类型。由于成员类也是一种类型,因此它必须用typename限定表达式才能访问。
1.3.3 返回值是一般类型
这是C++11之后,标准哭提供的一种方式std::common_type_t<>
#include <type_trains>
template<typename T1,typename T2>
std::common_type_t<T1,T2> max(T1 a,T2 b){
return a>b?a:b;
}
首先,我个人是不喜欢这种方式,为何我简单方便的auto关键字不用,我还要敲打那么多代码~!,而且作者也认为这种方式对后期维护会带来一些麻烦的?因此我这里就不往下说了。请查看原书的48页的内容。
1.4 默认模板参数
(略)我个人同样的返回值推导复杂化,没必要使用(原书49-51页的内容)。
重载模板函数
后面会继续更新....