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 }