使用C++11变长参数模板 处理任意长度、类型之参数实例
2014-03-11 23:19
246 查看
变长模板、变长参数是依靠C++11新引入的参数包的机制实现的。
一个简单的例子是std::tuple的声明:
这里的三个点“...”表示这个模板参数是变长的。
有了这个强大的工具,我们可以编写更加丰富的函数,例如任意类型参数的printf等。由于这个技术还比较新,还没有见到成熟的用法用例,我把我尝试的一些结果总结如下,希望对大家有帮助。
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(大致如下)。
解包时采用“包扩展表达式”,就是包名加上三个点,如“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。可以简单地认为:不能让展开之后的表达式成为一个独立的语句。
下面我以打印出一组参数为例,简单介绍一下变成参数函数怎么用。
方法一:
虽然写起来麻烦一点,但是它在运行期的效率比较高(没有递归,顺序搞定,DummyWrapper的参数传递会被编译器优化掉),而且编译期的代价也不是很高(对于相同类型的子元素,unpacker<T>只需要特化出一份即可,但DummyWrapper需要根据参数类型特化很多版本)。
但是这个方法存在一个问题:参数包在展开的时候,是从右(结束)向左(开始)进行的,所以unpacker(data)...所打印出来的东西可能是反序的(gcc的实现会,clang不会)!
所以这种方法对于屏幕输出这样要求严格顺序的操作就不适合了。它的适用范围更多的是在对顺序不敏感的地方。例如将一组序列化后的数据存储到一个std::map里去。
感谢@zhx6044 指出:在C++17标准中,可以使用fold expression,更直接地表达,并且确保正序展开:
详情参见他的blog: /article/3731356.html 。
附cppreference上关于fold expression的参考链接:http://en.cppreference.com/w/cpp/language/fold
方法二:
这种方法思路直观,书写便捷,而且可以保证执行顺序。但是运行时有递归,效率有所下降。编译时也需要生成不少版本的_write。
原载于http://blog.csdn.net/yanxiangtianji
转载请注明出处
一个简单的例子是std::tuple的声明:
template <typename... Elements> class tuple;
这里的三个点“...”表示这个模板参数是变长的。
有了这个强大的工具,我们可以编写更加丰富的函数,例如任意类型参数的printf等。由于这个技术还比较新,还没有见到成熟的用法用例,我把我尝试的一些结果总结如下,希望对大家有帮助。
1,参数包
考虑到这个知识点很多朋友都不熟悉,首先明确几个概念:1,模板参数包(template parameter pack):
它指模板参数位置上的变长参数(可以是类型参数,也可以是非类型参数),例如上面例子中的 Elements。
2,函数参数包(function parameter pack):
它指函数参数位置上的变长参数,例如下面例子中的args,(ARGS是模板参数包):
template <typename ... ARGS> void fun(ARGS ... args)
在很多情况下它们是密切相关的(例如上面的例子),而且很多概念和用法也都一致,在不引起误解的情况下,后面我在讨论时会将他们合并起来讨论,或只讨论其中一个(另一个于此相同)。
注意:
模板参数包本身在模板推导过程中被认为是一个特殊的类型(函数参数包被认为是一个特殊类型的参数)。
一个包可以打包任意多数量的参数(包含0个)。
有一个新的运算符:sizeof...(T) 可以用来获知参数包中打包了几个参数,注意不是参数所占的字节数之和。
一般情况下参数包必须在最后面,例如:
template <typename T, typename ... Args> void fun(T t,Args ... args);//合法 template <typename ... Args, typename T> void fun(Args ... args,T 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(大致如下)。
template < typename... Types1, template <typename...> class T , typename... Types2, template <typename...> class V> void bar(const T<Types1...>&, const V<Types2...>&) { std::cout << sizeof...(Types1) << std::endl; std::cout << sizeof...(Types2) << std::endl; } 调用: tuple<int,double> a; tuple<char,float,long> b; bar(a,b);总之就是让编译器能够轻松地唯一地确定包到底有多大就可以了。
2,解包 (包展开)
在实际使用时,拿到一个复合而成的包对没有并没有什么用,我们通常需要获得它里面内一个元素的内容。解包是把参数包展开为它所表示的具体内容的动作。解包时采用“包扩展表达式”,就是包名加上三个点,如“Args...”。
例如:
假设我们有一个模板类Base:
template <typename ... Args> class D1 : public Base<Args...>{}; 或 template <typename ... Args> class D2 : public Base<Args>...{};解包用两种常见的形式:
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函数的捕获列表。
例如下面例子的两个展开就是非法的:
template <typename T> void fun_hehe(T t){ //do something } template <typename... T> void fun(T... t){ t...; fun_hehe(t)...; }
第一个(t...)非法很好理解,直接并列一堆东西没有意义嘛。第二个(fun_hehe(t)...)貌似是有意义的,但是一般情况下不能这样用,需要类似的功能时可以采用下一节介绍的方法2。可以简单地认为:不能让展开之后的表达式成为一个独立的语句。
3,函数实例
一个常用的技巧是:利用模板推导机制,每次从参数包里面取第一个元素,缩短参数包,直到包为空。template <typename T> void fun(const T& t){ cout << t << '\n'; } template <typename T, typename ... Args> void fun(const T& t, Args ... args){ cout << t << ','; fun(args...);//递归解决,利用模板推导机制,每次取出第一个,缩短参数包的大小。 }
下面我以打印出一组参数为例,简单介绍一下变成参数函数怎么用。
方法一:
template <typename ... T> void DummyWrapper(T... t){} template <class T> T unpacker(const T& t){ cout<<','<<t; return t; } template <typename T, typename... Args> void write_line(const T& t, const Args& ... data){ cout << t; DummyWrapper(unpacker(data)...); //直接用unpacker(data)...是非法的,(可以认为直接逗号并列一堆结果没有意义) //所以需要用一个函数包裹一下,就好像这些结果后面还有用 cout << '\n'; }
虽然写起来麻烦一点,但是它在运行期的效率比较高(没有递归,顺序搞定,DummyWrapper的参数传递会被编译器优化掉),而且编译期的代价也不是很高(对于相同类型的子元素,unpacker<T>只需要特化出一份即可,但DummyWrapper需要根据参数类型特化很多版本)。
但是这个方法存在一个问题:参数包在展开的时候,是从右(结束)向左(开始)进行的,所以unpacker(data)...所打印出来的东西可能是反序的(gcc的实现会,clang不会)!
所以这种方法对于屏幕输出这样要求严格顺序的操作就不适合了。它的适用范围更多的是在对顺序不敏感的地方。例如将一组序列化后的数据存储到一个std::map里去。
感谢@zhx6044 指出:在C++17标准中,可以使用fold expression,更直接地表达,并且确保正序展开:
template <typename T, typename... Args> void write_line(const T& t, const Args& ... data){ <span style="white-space:pre"> </span>cout<<','<<t; <span style="white-space:pre"> </span>(unpacker(data), ...);//展开成(((unpacker(data_1), unpacker(data_2)), unpacker(data_3), ... ),unpacker(data_n) <span style="white-space:pre"> </span>cout<<'\n'; <span style="white-space:pre"> </span>//如果不需要输出间隔符,后两行还可以使用下面的简单形式 <span style="white-space:pre"> </span>//(cout<< ... <<args)<<'\n'; }
详情参见他的blog: /article/3731356.html 。
附cppreference上关于fold expression的参考链接:http://en.cppreference.com/w/cpp/language/fold
方法二:
template <typename T> void _write(const T& t){ cout << t << '\n'; } template <typename T, typename ... Args> void _write(const T& t, Args ... args){ cout << t << ','; _write(args...);//递归解决,利用模板推导机制,每次取出第一个,缩短参数包的大小。 } template <typename T, typename... Args> inline void write_line(const T& t, const Args& ... data){ _write(t, data...); }
这种方法思路直观,书写便捷,而且可以保证执行顺序。但是运行时有递归,效率有所下降。编译时也需要生成不少版本的_write。
原载于http://blog.csdn.net/yanxiangtianji
转载请注明出处
相关文章推荐
- 使用C++11变长参数模板 处理任意长度、类型之参数
- C++11可变数量模板参数可变类型模板参数并使用lamada函数调用使用范例
- C++11 引用叠加规则和模板参数类型推导规则
- 使用ModelBinder处理Actin实体类型参数
- 使用eclipse JDT compile class,解决 无法确定 X 的类型参数;对于上限为 X,java.lang.Object 的类型变量 X,不存在唯一最大实例
- Effective C# 学习笔记(四十)使用Dynamic处理匿名类型参数
- C++11 引用叠加规则和模板参数类型推导规则
- 使用C++中string实现任意长度的正小数、整数之间加减法方法实例
- 使用eclipse JDT compile class,解决 无法确定 X 的类型参数;对于上限为 X,java.lang.Object 的类型变量 X,不存在唯一最大实例
- 使用润乾参数模板后如何对参数进行二次处理
- C++ - 非类型模板参数(nontype template parameters) 使用 及 代码
- 在模板类中使用字符串作为无类型模板参数
- 使用eclipse JDT compile class,解决 无法确定 X 的类型参数;对于上限为 X,java.lang.Object 的类型变量 X,不存在唯一最大实例
- 非类型模板参数与 非类型类模板参数的使用
- 尽可能使用泛型方法,除非需要将类型参数用于实例的字段
- WP7 Page基类的使用,以及传入任意类型的参数的封装
- Effective C# 学习笔记(三十九) 使用Dynamic处理范型参数的运行时类型
- 在模板类中使用字符串作为无类型模板参数
- C++ - 使用非成员函数(non-member)处理函数的所有参数都需要类型转换
- 泛型约束where条件的使用(通过类型参数动态反射创建实例)