6

使用C++11变长参数模板 处理任意长度、类型之参数实例

 3 years ago
source link: https://blog.csdn.net/yanxiangtianji/article/details/21045525
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用C++11变长参数模板 处理任意长度、类型之参数实例

变长模板、变长参数是依靠C++11新引入的参数包的机制实现的。

一个简单的例子是std::tuple的声明:

这里的三个点“...”表示这个模板参数是变长的。

有了这个强大的工具,我们可以编写更加丰富的函数,例如任意类型参数的printf等。由于这个技术还比较新,还没有见到成熟的用法用例,我把我尝试的一些结果总结如下,希望对大家有帮助。

1,参数包

考虑到这个知识点很多朋友都不熟悉,首先明确几个概念:

1,模板参数包(template parameter pack):
它指模板参数位置上的变长参数(可以是类型参数,也可以是非类型参数),例如上面例子中的 Elements。
2,函数参数包(function parameter pack):

它指函数参数位置上的变长参数,例如下面例子中的args,(ARGS是模板参数包):

在很多情况下它们是密切相关的(例如上面的例子),而且很多概念和用法也都一致,在不引起误解的情况下,后面我在讨论时会将他们合并起来讨论,或只讨论其中一个(另一个于此相同)。

模板参数包本身在模板推导过程中被认为是一个特殊的类型(函数参数包被认为是一个特殊类型的参数)。

一个包可以打包任意多数量的参数(包含0个)。

有一个新的运算符:sizeof...(T) 可以用来获知参数包中打包了几个参数,注意不是参数所占的字节数之和。

一般情况下参数包必须在最后面,例如:

有人说了,我向来是二班的,什么奇葩情况都能遇见。那么二般情况请参见: http://stackoverflow.com/questions/4706677/partial-template-specialization-with-multiple-template-parameter-packs 和 http://stackoverflow.com/questions/9831501/how-can-i-have-multiple-parameter-packs-in-a-variadic-template(大致如下)。

总之就是让编译器能够轻松地唯一地确定包到底有多大就可以了。

2,解包 (包展开)

在实际使用时,拿到一个复合而成的包对没有并没有什么用,我们通常需要获得它里面内一个元素的内容。解包是把参数包展开为它所表示的具体内容的动作。

解包时采用“包扩展表达式”,就是包名加上三个点,如“Args...”。

假设我们有一个模板类Base:

解包用两种常见的形式:

1,直接解包(上面第一个)

D1<X,Y,Z> 相当于 D1:public Base<X,Y,Z>

2,先参与其他表达式再解包(上面第二个)

D2<X,Y,Z> 相当于 D2: public Base<X>, Base<Y>, Base<Z>

直观上理解就是在...所在的位置将包含了参数包的表达式展开为若干个具体形式。

参数包的展开不能无条件地在任何地方使用,这会给编译器看到的源代码的结构带来很大的复杂性。严格来说标准规定可以进行参数包展开的有7中情况:1,表达式;2,初始化列表;3,基类描述列表;4,类成员初始化;5,模板参数列表;6,通用属性列表;7,lambda函数的捕获列表。

例如下面例子的两个展开就是非法的:

第一个(t...)非法很好理解,直接并列一堆东西没有意义嘛。第二个(fun_hehe(t)...)貌似是有意义的,但是一般情况下不能这样用,需要类似的功能时可以采用下一节介绍的方法2。可以简单地认为:不能让展开之后的表达式成为一个独立的语句。

3,函数实例

一个常用的技巧是:利用模板推导机制,每次从参数包里面取第一个元素,缩短参数包,直到包为空。

下面我以打印出一组参数为例,简单介绍一下变成参数函数怎么用。

虽然写起来麻烦一点,但是它在运行期的效率比较高(没有递归,顺序搞定,DummyWrapper的参数传递会被编译器优化掉),而且编译期的代价也不是很高(对于相同类型的子元素,unpacker<T>只需要特化出一份即可,但DummyWrapper需要根据参数类型特化很多版本)。

但是这个方法存在一个问题:参数包在展开的时候,是从右(结束)向左(开始)进行的,所以unpacker(data)...所打印出来的东西可能是反序的(gcc的实现会,clang不会)!

所以这种方法对于屏幕输出这样要求严格顺序的操作就不适合了。它的适用范围更多的是在对顺序不敏感的地方。例如将一组序列化后的数据存储到一个std::map里去。

感谢@zhx6044 指出:在C++17标准中,可以使用fold expression,更直接地表达,并且确保正序展开:

详情参见他的blog: http://blog.csdn.net/zhx6044/article/details/50931003 。

附cppreference上关于fold expression的参考链接:http://en.cppreference.com/w/cpp/language/fold

这种方法思路直观,书写便捷,而且可以保证执行顺序。但是运行时有递归,效率有所下降。编译时也需要生成不少版本的_write。

原载于http://blog.csdn.net/yanxiangtianji

转载请注明出处


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK