zoukankan      html  css  js  c++  java
  • 编译器对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 << '
    ';
    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 << '
    ';
    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://www.cnblogs.com/fullsail/,否则每字一元,每图一百不讲价。对Baidu文库和360doc加价一倍】

  • 相关阅读:
    Java实现 LeetCode 382 链表随机节点
    Java实现 LeetCode 382 链表随机节点
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 380 常数时间插入、删除和获取随机元素
    Java实现 LeetCode 380 常数时间插入、删除和获取随机元素
    Linux下的iwpriv(iwlist、iwconfig)的简单应用
    OCX控件的注册卸载,以及判断是否注册
    .OCX、.dll文件注册命令Regsvr32的使用
  • 原文地址:https://www.cnblogs.com/fullsail/p/3872167.html
Copyright © 2011-2022 走看看