编译器对C++ 11变参模板(Variadic Template)的函数包扩展实现的差异

编译器对C++ 11变参模板(Variadic Template)的函数包扩展实现的差异

题目挺绕口的。C++ 11的好东西不算太多,但变参模板(Variadic Template)肯定是其中耀眼的一颗明星,在C++设计新思维中,你可以看到很多模版的代码为了支持不确定的参数个数,而要重载1个参数到N个模板参数的N个函数。虽然种代码一般也是用会用宏和脚步辅助生成。但我想也没有人愿意看到几千行这种单调的函数。通过这个东东,模板的威力可以爆发。

目前的最新的编译器基本都已经支持Variadic Template了,GCC 4.6和Visual studio 2013都已经支持变参模板。但今天我在写一个Lua粘结层的时候发现了一个有意思的问题。

先上代码。

 1 #include <iostream>
 2
 3
 4 template <typename ... T>
 5 void dummy_wrapper(T... t)
 6 {
 7 };
 8
 9 template <class T>
10 T unpacker(const T t)
11 {
12     std::cout << ‘[‘ << t << ‘]‘;
13     return t;
14 }
15
16
17 template <typename... Args>
18 void write_line(const Args& ... data)
19 {
20     dummy_wrapper(unpacker(data)...);
21     std::cout << ‘\n‘;
22 }
23
24 int main()
25 {
26     write_line(1, "--", "2.2.2", "--", 3.0);
27     return 0;
28 }

稍作解释,write_line是一个接受变参的模版函数,其内部调用dummy_wrapper,这个函数帮主辅助进行包扩展,让每个参数都调用unpacker(data)函数。那这个代码这个代码的输出预计应该是什么?

我想大部分人会认为是 [1][--][2.2.2][--][3],但其实在Visual C++ 2013和GCC 4.8上的输出都正好相反。输出的是[3][--][2.2.2][--][1]。是不是有点搞?

我觉得这个问题的原因是因为C++的参数入栈顺序是从右到左,1这个参数被计算处理后,被先扔到栈里面。反而变成了最右的参数。所以导致了这个问题。

其实坦白讲这种问题就是典型的规范没有写明的问题。其实我认为从结果和语义正确性上讲,[1][--][2.2.2][--][3]的输出肯定更加正确一些,但这个对于编译器,就要等若干结果计算出来了,再入栈。

而这个问题在IBM的编译器团队的写的《深入理解C++11》上也有一节提过,IBM的编译器XL编译器上类似的测试输出结果是顺序的,他们也测试了GCC,他们认为GCC输出是混乱的(其实是倒序的)。

如何规避这个问题呢?有个法子是把参数反着传递给write_line函数,但这种方法不具备可移植性(谁知道这个bug会哪天修复,另外,你输出东西的时候能倒着想问题?)。

其实可以考虑使用递归的包扩展方式,

 1 #include <iostream>
 2
 3 template <typename ... Tlist>
 4 void dummy_wrapper(Tlist... tlst)
 5 {
 6 };
 7
 8 template <typename T, typename ... Tlist>
 9 void dummy_wrapper(T t, Tlist... tlst)
10 {
11     unpacker(t);
12     dummy_wrapper(tlst...);
13 };
14
15 template <class T>
16 T unpacker(const T t)
17 {
18     std::cout << ‘[‘ << t << ‘]‘;
19     return t;
20 }
21
22 template <typename... Args>
23 void write_line(const Args& ... data)
24 {
25     dummy_wrapper(data...);
26     std::cout << ‘\n‘;
27 }
28
29 int main()
30 {
31     write_line(1, "--", "2.2.2", "--", 3.0);
32     return 0;
33 }

这样改写后,输出变成[1][--][2.2.2][--][3]、

这种方式参数是一个个展开的,所以避免了上述的问题。大部分情况可以这样改写规避一部分问题。但当然这样也不能解决所有问题,

我的代码里面中间有一段是这样的,目的是为了将一个函数注册给lua,

 1 template<typename ret_type,
 2     typename ...args_type>
 3 class g_functor_ret
 4 {
 5 public:
 6     static int invoke(lua_State *state)
 7     {
 8         void *upvalue_1 = lua_touserdata(state, lua_upvalueindex(1));
 9         ret_type(*fun_ptr)(args_type...) = (ret_type( *)(args_type...))(upvalue_1);
10         int para_idx = 0;
11         push_stack<ret_type>(state, fun_ptr(read_stack<args_type>(state, para_idx++)...));
12     }
13 };

fun_ptr(read_stack<args_type>(state, para_idx--)...) 这段代码,由于fun_ptr是一个函数指针,我不可能为所有的函数指针都去写一个递归方法,所以上面的方法无效。

怎么办呢?只能先把参数反向传入,后面等编译器有更新再慢慢改,也只有这个很挫的方法先应付。目前我能测试到的VS2013和GCC 4.8目前还都有问题。

【本文作者是雁渡寒潭,本着自由的精神,你可以在无盈利的情况完整转载此文档,转载时请附上BLOG链接:http://blog.csdn.com/fullsail/,否则每字一元,每图一百不讲价。对Baidu文库和360doc加价一倍】

时间: 2024-08-02 02:33:40

编译器对C++ 11变参模板(Variadic Template)的函数包扩展实现的差异的相关文章

C++11变参模板

在C++11中出现的变参模板,可以让我们不需关心函数调用的参数多少,类似实现C中的printf函数那样. 变参依赖于C++强大的模板 可以这样声明 template<class T1,class... Args>   //   Args就是一种类型参数包,在定义的函数中需要递归去解析 void MutiArg(const T1&t1,Args... args) { //  do something with t1对第一个调用参数进行操作 MutiArg(args...);  //对参数

每日一题20:与C++11的第一次邂逅——可变参模板与C#委托模拟

这篇文章本来是前天发的,但是不知道为什么CSDN上没有显示,可能是我没发,记错了.又由于没有留底稿,还是重写一下吧,也为知己不留底稿的恶习做个标记. 之所以接触C++11是因为自己前天突发奇想想用C++来模拟一下C#里的委托,但是尝试过很多方法和各种搜索后,知道之前的C++是不支持模板重载的,所以不可能通过编写多种版本的模板来实现变参的效果,如果使用<C++设计新思维>里的TypeList方式的话,我不知道能不能做到,即使能做到也不可能达到预期的效果,并且TypeList是一个重型武器,构建起

C++11 : 外部模板(Extern Template)

在C++98/03语言标准中,对于源代码中出现的每一处模板实例化,编译器都需要去做实例化的工作:而在链接时,链接器还需要移除重复的实例化代码.显然,让编译器每次都去进行重复的实例化工作显然是不必要的,并且连接器也因此受累.在现实编码世界里,一个软件的实现可能会在很多代码块中使用同一种类型去实例化同一个模板.此时,如果能够让编译器避免此类重复的实例化工作,那么可以大大提供编译器的工作效率.因此,人们迫切需要一种手段(一种声明方式)来告诉编译器“该处出现的模板实例化工作已在其它编译单元中完成,不再需

C++11 外部模板

[1]引入外部模板为了解决什么问题? “外部模板”是C++11中一个关于模板性能上的改进.实际上,“外部”(extern)这个概念早在C的时候已经就有了. 常见的情况,在一个文件a.c中定义了一个变量int i,而在另外一个文件b.c中想使用它,这个时候就会在没有定义变量i的b.c文件中做一个外部变量的声明.比如: // 声明在b.c文件中 extern int i; 这样做的好处是,在分别编译了a.c和b.c之后,其生成的目标文件a.o和b.o中只有i这个符号(可简单理解为变量)的一份定义.

C++11(15): 模板与泛型编程

面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况.不同之处:OOP能处理类型在程序运行之前都未知的情况:而泛型编程中,在编译时就能获知类型了 模板参数类别不能为空. 模板参数表示在类或函数定义中用到的类型或值. template <typename T> int compare(const T &v1 , const T &v2) { if(v1<v2) return -1; if(v2<v1) return 1; return 0 ; } cout<

Django——模板层(template)(模板语法、自定义模板过滤器及标签、模板继承)

阅读目录(Content) 模板语法之变量 模板之过滤器 default length filesizeformat date slice truncatechars safe 模板之标签 自定义标签和过滤器 模板继承 (extend) 模板语法之include 前言:当我们想在页面上给客户端返回一个当前时间,一些初学者可能会很自然的想到用占位符,字符串拼接来达到我们想要的效果,但是这样做会有一个问题,HTML被直接硬编码在 Python代码之中. 1 2 3 4 def current_dat

Template function 函数模板用法

#include<iostream> using namespace std; const double PI = 3.1415926; template <class T> T min(T a[], int n){ int i; T minv = a[0]; for (i = 1; i < n; i++){ if (a[i] < minv) minv = a[i]; } return minv; } template<class T1> double Ci

Xcode中的变量模板(variable template)的用法

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 你可能经常会写一些小的代码片段,里面自然少不了一些关键的变量.你会把这些代码片段放到网上,比如github里,作为示例. 但是有个问题,就是上述代码片段中的变量会根据不同系统,不同用户发生变化.在你这里有效的,可能在别人那就会无效. 举个不恰当的例子,比如其中有一个变量是你服务器的秘钥ID: NSString *keyID = @"12345678"

模板(1)函数模板

1.引入 如何编写一个通用加法函数?第一个方法是使用函数重载, 针对每个所需相同行为的不同类型重新实现这个函数.C++的这种编程机制给编程者极大的方便,不需要为功能相似.参数不同的函数选用不同的函数名,也增强了程序的可读性.简单示例: 1 int Add(const int &_iLeft, const int &_iRight) 2 { 3 return (_iLeft + _iRight) ; 4 }f 5 loat Add(const float &_fLeft, const