• C++ print tuple


    std::tuple不支持输入输出,就想实现一下。最初想用模板函数实现,无奈总是不行,就改用模板类了,下面是实现代码。

     1 #include <iostream>
     2 #include <tuple>
     3 using namespace std;
     4 
     5 template <class Tuple, size_t N>
     6 struct print_imp
     7 {
     8     static void print(ostream& out, const Tuple& t)
     9     {
    10         print_imp<Tuple, N-1>::print(out, t);
    11         out << ", " << get<N-1>(t);
    12     }
    13 };
    14 template <class Tuple>
    15 struct print_imp<Tuple, 1>
    16 {
    17     static void print(ostream& out, const Tuple& t)
    18     {
    19         out << get<0>(t);
    20     }
    21 };
    22 template <class... Args>
    23 ostream& operator<<(ostream& out, const tuple<Args...>& t)
    24 {
    25     out << '(';
    26     print_imp<decltype(t), sizeof...(Args)>::print(out, t);
    27     return out << ')';
    28 }
    29 int main()
    30 {
    31     auto t = make_tuple(12,4.5,3,6.7,"efa"s);
    32     cout << t << endl;
    33 }

    用模板函数失败的原因是,std::get<INDEX>()里的INDEX必须是编译期常量,而我没有办法通过函数的参数传递一个编译期常量,只好在模板类的模板参数里传递。后来受樱子妹妹启发,不能传常量,就传类型好了。下面的代码使用了一个空类把常量封装成类型:

     1 template <size_t> struct uint{};
     2 
     3 template <class Tuple, size_t N>
     4 void print(ostream& out, const T &t, uint<N>)
     5 {
     6     print(out,t, uint<N-1>());
     7     out << ", " << get<N-1>(t) ;
     8 }
     9 
    10 template <class Tuple>
    11 void print(ostream& out, const T &t, uint<1>)
    12 {
    13     out << get<0>(t);
    14 }
    15 
    16 template <class... Args>
    17 ostream& operator<<(ostream& out, const tuple<Args...> &t)
    18 {
    19     out << '(';
    20     print(out,t, uint<sizeof...(Args)>());
    21     return out << ')';
    22 }

    不得不说,这份实现清爽了好多,再次感谢樱子妹妹。

    顺便多说一句,关于把数值封装成类,C++标准里也有这样的功能,就是模板类integral_constant,其一个可能实现如下:

    template<class T, T v>
    struct integral_constant {
        static constexpr T value = v;
        typedef T value_type;
        typedef integral_constant type;
        constexpr operator value_type() const { return value; }
    };

    其模板参数有两个,第一个是类型,第二个是值,也就是它可以封装各种整型类型的常量。接口为:一个静态成员数据,两个type,一个类型转换符。

    如果使用integral_constant代替手写类的话,就可以这样用:

    template <size_t N>
    using uint = integral_constant<size_t, N>;

    最初我也想用ratio来封装常量,但是失败了,因为ratio的模板参数为signed类型,与get<>()不符。

    如果就这样结束了,未免显得图森破。可能有同学在想,能不能采用非递归的方式实现呢?普通的方式是不行的。我反反复复强调了,get<>()使用的是编译期常量,无论是基于变量的for循环还是基于迭代器的for循环都是不能用的。但是还真有大牛实现了,方式是传递一堆常量,再用特殊方式遍历。以下实现出自http://stackoverflow.com/a/6245777/273767

     1 template<class Ch, class Tr, class Tuple, std::size_t... Is>
     2 void print_tuple_impl(std::basic_ostream<Ch,Tr>& os,
     3                       const Tuple & t,
     4                       std::index_sequence<Is...>)
     5 {
     6     using swallow = int[]; // guaranties left to right order
     7     (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
     8 }
     9  
    10 template<class Ch, class Tr, class... Args>
    11 decltype(auto) operator<<(std::basic_ostream<Ch, Tr>& os,
    12                           const std::tuple<Args...>& t)
    13 {
    14     os << "(";
    15     print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
    16     return os << ")";
    17 }

    我看到这份代码第一反应是:这他喵是啥?!(╯°Д°)╯︵ ┻━┻ 。一步步分解吧。

    先看operator<<。std::ostream就是std::basic_ostream<char>,这份代码更加泛化而已。函数前面的decltype(auto)表示自动推导返回类型,这是C++14的新特性。这样operator<<里就剩下一个对print_tuple_impl 的调用了,里面使用了一个工厂元函数 index_sequence_for<>,这又是C++14的新特性,属于integer_sequence知识点,下面细细道来:

    template< class T, T... Ints >
    class integer_sequence {};
    template<std::size_t... Ints>
    using index_sequence = std::integer_sequence<std::size_t, Ints...>;
    template<class T, T N>
    using make_integer_sequence = std::integer_sequence<T, /* a sequence 0, 1, 2, ..., N-1 */ >;
    template<std::size_t N>
    using make_index_sequence = std::make_integer_sequence<std::size_t, N>;
    template<class... T>
    using index_sequence_for = std::make_index_sequence<sizeof...(T)>;

    integer_sequence是对integral_constant的扩展,一次封装一个序列的整数常量。index_sequence就是类型限定为size_t的integer_sequence。std提供了三个工厂元函数,make_integer_sequence是创建序列为0, 1, 2, ..., N-1的工厂元函数,make_index_sequence同理。index_sequence_for是根据参数包的数量来调用make_ndex_sequence。那么,代码第15行的std::index_sequence_for<Args...>就是,以传入tuple的size创建一个index_sequence,从0到sizeof...(Args)-1,以供get<>()使用。对了,元函数返回的是一个类型,所以后面要加{}来创建实例。{}是初始化列表,功能同()。

    接下来再来看看print_tuple_impl。第一行创建了一个类型swallow,就是int[]的别名。第二行就让人发狂了,但也不是完全看不懂。首先,这个(void)还是能看懂的。如果你定义了一个变量而未使用,编译器会给出一个warning,这时候把变量void一下就不会警告了,像这样:int a; (void)a;

    所以(void)swallow{0, 什么鬼}; 就等价于 int arr[] = {0,什么鬼}; (void)arr;

    所以“什么鬼”实际是整数,也就是(void(表达式), 0)... 是整数。可是(XX,**)这种格式从来没见过啊。做个实验,把这段代码cout << typeid("ewf",1.2) << endl;编译一下,gcc给出的诊断是: warning: left operand of comma operator has no effect [-Wunused-value]。就是说,逗号操作符左边的运算数没有效果,是个unused value(所以“表达式”需要void一下)。这时候我才想想来逗号确实也是一个运算符,意义是按顺序执行,先左后右。所以(void(表达式), 0)其实就是0。卧槽槽槽……这意味着什么?所有的C++语句都可以转化为一个0!全都是0!我感觉我要控制不住体内的洪荒之力了(╯°Д°)╯︵ ┻━┻

    下面是最后一个知识点了,参数包展开。(void(表达式), 0)是一个0,(void(表达式), 0)...是一堆0,“...”就是将参数包展开。先看一下参数包展开在C++11标准里的定义:

    14.5.3.4

    A pack expansion is a sequence of tokens that names one or more parameter packs, followed by an ellipsis.
    The sequence of tokens is called the pattern of the expansion; its syntax depends on the context in which
    the expansion occurs. Pack expansions can occur in the following contexts:
    — In an initializer-list (8.5); the pattern is an initializer-clause.
    — In a base-specifier-list (Clause 10); the pattern is a base-specifier.
    — In a mem-initializer-list (12.6.2); the pattern is a mem-initializer.
    — In a template-argument-list (14.3); the pattern is a template-argument.
    — In a dynamic-exception-specification (15.4); the pattern is a type-id.
    — In an attribute-list (7.6.1); the pattern is an attribute.
    — In an alignment-specifier (7.6.2); the pattern is the alignment-specifier without the ellipsis.
    — In a capture-list (5.1.2); the pattern is a capture.

    大概翻译一下:参数包展开式是一个包含参数包的标记序列后面跟着省略号。标记序列就是展开式的范式,其语法根据展开式出现的环境而定。参数包展开式可以出现在以下环境中:……

    这里用到的就是第一个环境,初始化列表。代码展开了一个复杂的数值0,这个0里面包含参数包 Is。

    晕了吗?现在来总结一下:print_tuple_impl 其实就建了一个数组,这个数组里每一个元素都是一条语句,所以本质上相当于执行了一个循环。稍微改写一下可能更容易理解:

     1 #define FOREACH_IN_PACK(expr) swallow{(expr ,0)...}
     2 // 省略了(void),Siriuslzx亲测不会warning
     3 template<class Tuple, std::size_t... Is>
     4 auto print_tuple_impl(std::ostream& os, const Tuple & t,
     5                       std::index_sequence<Is...>)
     6 {
     7     using swallow = int[];
     8     FOREACH_IN_PACK(os << (Is == 0? "" : ", ") << std::get<Is>(t) );
     9 }
    10 
    11 template<class... Args>
    12 decltype(auto) operator<<(std::ostream& os, const std::tuple<Args...>& t)
    13 {
    14     os << "(";
    15     print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
    16     return os << ")";
    17 }
     



  • 相关阅读:
    分时段查询数据
    优秀产品经理的7大核心技能
    控制台打印螺旋数组
    强大的jquery,再次让我为之鼓掌——三维展示插件
    立象条码打印机PPLB类单双标签打印
    一次与德国程序员的交流
    用ASP为Discuz扩展点小功能
    GDI+ 小破孩动画
    回应YeanJay同学jQuery按钮改变DIV背景色
    C# Dock Style 设置
  • 原文地址:https://www.cnblogs.com/lzxskjo/p/4940679.html
Copyright © 2020-2023  润新知