现代C++之”可变参数模板”

从17年开始就不太做C++开发了,因此知识还停留在C++03上,所以这次趁着过年的短暂空隙补一下比较有意思的现代C++特性,有些简单的新语法不是我想表达的重点就一笔带过了。

本文通过1个简单例子来了解”可变参数模板”,代码:https://github.com/owenliang/modern-cpp/blob/main/variadic_tpl.cpp

这是模板的一个新特性,我们从一个需求出发更容易解释:

实现一个sum函数,可以接受任意长度+任意类型的传参,然后返回一个double类型的总和。

如果用C++03,那么类型一样还好说点,我可以重载N个不同参数个数的版本,但是现在类型也是不限制的,咋弄呐?那就看看现代C++的”可变参数模板吧”。

typename… Args表示多个模板参数,每个模板参数的类型可以不一样。

然后sum函数接受T v,以及后续要求和的N个参数,Args…可以展开函数形参声明,args…可以展开实际参数,因此sum是一个递归函数。

sum(1, 2, 3)等于1 + sum(2,3)等于1+2+sum(3)等于1+2+3,回想模板与模板实例化的过程,因此编译期会为模板实例化3个版本的sum函数:

sum(int, int, int)

sum(int, int)

sum(int)

编译器甚至聪明到可以帮我们把这个运行时尾递归在编译期间展开,这个就不扯远了。

但是这还没算完,注意上面的sum函数至少接收1个T v参数(Args…可以容纳0个参数),但是递归最终会出现sum(3)等于3+sum()的情况,因此我们必须给这个递归一个出口,也就是重载一个空参数的版本:

而且还得注意double sum()必须写在上面,因为下面的template函数实例化出来的sum(int)版本会调用sum(),因此需要sum()先出现,否则会报找不到sum()函数的错误,都是小细节啊。

另一个超酷的sum实现

动态模板参数就是靠上述递归的思想完成了多个模板实例化的生成,其核心就是递归过程中如果Args…列表消耗完了就不要再递归了,能不能不用重载方式实现递归出口呢?下面就来看一个厉害的操作:

哈哈,sizeof…可以求”可变模板参数”的长度,注意这是编译期生成模板实例化代码过程中执行的,不是运行时的。

这个很容易理解的,sum_super_cool(1,2,3)会生成如下几个模板实例化版本:

sum_super_cool(int,int,int)

sum_super_cool(int,int)

sum_super_cool(int)  — > 生成这个版本时,sizeof…(args) 就是0,所以该版本生成的代码实际就是:

也就是说sum_super_cool(int)在运行时会执行if(0==0)的判定然后就return v了,例如sum_super_cool(int, int)的if(1==0)就会导致走else分支继续递归调用v+sum_super_cool()。

但实际编译这个代码会失败,因为虽然sum_super_cool(int)会直接return v,但是return v+sum_super_cool()代码还是被生成出来的,这就要求我们必须实现double sum_super_cool()版本的重载才能编译通过:

好,我们再引入一个高级语法,让我们可以不用手动重载空参数版本代码的同时也能终止递归:

在if后面加constexpr,可以让编译器在编译阶段直接根据if (sizeof…) {} else {} 语句进行代码裁剪,如果是if (0==0),那么实例化的代码就是:

否则就会被裁剪成这样(某一个版本的模板实例化代码):

这样就不会出现调用sum_super_cool()空传参版本的重载需求啦~

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~