Pretty-print std::tuple
这是我上一个关于漂亮印刷STL容器问题的后续问题,为此,我们设法开发了一个非常优雅和全面的解决方案。
在下一个步骤中,我希望使用可变模板(包括这是严格的C++ 11),为EDCOX1(0)提供漂亮的打印。对于
1 2 3 4 | std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p) { return o <<"(" << p.first <<"," << p.second <<")"; } |
打印元组的类似结构是什么?
我尝试过各种各样的模板参数栈解包,传递索引,并使用sfinae发现我在最后一个元素的时间,但是没有成功。我不会用我的坏代码给您带来负担;希望问题描述足够直接。从本质上讲,我喜欢以下行为:
1 2 | auto a = std::make_tuple(5,"Hello", -0.1); std::cout << a << std::endl; // prints: (5,"Hello", -0.1) |
与前一个问题具有相同的通用性级别(char/wchar_t,对定界符)的额外分数!
YAY,指数~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | namespace aux{ template<std::size_t...> struct seq{}; template<std::size_t N, std::size_t... Is> struct gen_seq : gen_seq<N-1, N-1, Is...>{}; template<std::size_t... Is> struct gen_seq<0, Is...> : seq<Is...>{}; template<class Ch, class Tr, class Tuple, std::size_t... Is> void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){ using swallow = int[]; (void)swallow{0, (void(os << (Is == 0?"" :",") << std::get<Is>(t)), 0)...}; } } // aux:: template<class Ch, class Tr, class... Args> auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) -> std::basic_ostream<Ch, Tr>& { os <<"("; aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>()); return os <<")"; } |
IDeone上的实时示例。
对于分隔符,只需添加以下部分专用化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Delimiters for tuple template<class... Args> struct delimiters<std::tuple<Args...>, char> { static const delimiters_values<char> values; }; template<class... Args> const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = {"(",",",")" }; template<class... Args> struct delimiters<std::tuple<Args...>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<class... Args> const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L",", L")" }; |
并相应更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | template<class Ch, class Tr, class... Args> auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) -> std::basic_ostream<Ch, Tr>& { typedef std::tuple<Args...> tuple_t; if(delimiters<tuple_t, Ch>::values.prefix != 0) os << delimiters<tuple_t,char>::values.prefix; print_tuple(os, t, aux::gen_seq<sizeof...(Args)>()); if(delimiters<tuple_t, Ch>::values.postfix != 0) os << delimiters<tuple_t,char>::values.postfix; return os; } |
和
1 2 3 4 5 6 7 | template<class Ch, class Tr, class Tuple, std::size_t... Is> void print_tuple(std::basic_ostream<Ch, Tr>& os, Tuple const& t, seq<Is...>){ using swallow = int[]; char const* delim = delimiters<Tuple, Ch>::values.delimiter; if(!delim) delim =""; (void)swallow{0, (void(os << (Is == 0?"" : delim) << std::get<Is>(t)), 0)...}; } |
我在C++ 11(GCC 4.7)中完成了这个工作。我确信我还没有考虑到一些陷阱,但我认为代码容易阅读而且不复杂。唯一可能奇怪的是"guard"结构tuple打印机,它确保在到达最后一个元素时终止。另一个奇怪的事情可能是返回类型包中类型数的sizeof…(types)。它用于确定最后一个元素的索引(大小…(类型)-1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | template<typename Type, unsigned N, unsigned Last> struct tuple_printer { static void print(std::ostream& out, const Type& value) { out << std::get<N>(value) <<","; tuple_printer<Type, N + 1, Last>::print(out, value); } }; template<typename Type, unsigned N> struct tuple_printer<Type, N, N> { static void print(std::ostream& out, const Type& value) { out << std::get<N>(value); } }; template<typename... Types> std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) { out <<"("; tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value); out <<")"; return out; } |
我很惊讶CPPreference上的实现还没有发布在这里,所以我会为子孙后代这样做。它隐藏在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <iostream> #include <tuple> #include <string> // helper function to print a tuple of any size template<class Tuple, std::size_t N> struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter<Tuple, N-1>::print(t); std::cout <<"," << std::get<N-1>(t); } }; template<class Tuple> struct TuplePrinter<Tuple, 1> { static void print(const Tuple& t) { std::cout << std::get<0>(t); } }; template<class... Args> void print(const std::tuple<Args...>& t) { std::cout <<"("; TuplePrinter<decltype(t), sizeof...(Args)>::print(t); std::cout <<") "; } // end helper function |
测试:
1 2 3 4 5 6 7 8 | int main() { std::tuple<int, std::string, float> t1(10,"Test", 3.14); int n = 7; auto t2 = std::tuple_cat(t1, std::make_pair("Foo","bar"), t1, std::tie(n)); n = 10; print(t2); } |
输出:
(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)
现场演示
在C++ 17中,我们可以利用折叠表达式来实现这一点,尤其是一元左折叠:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | template<class TupType, size_t... I> void print(const TupType& _tup, std::index_sequence<I...>) { std::cout <<"("; (..., (std::cout << (I == 0?"" :",") << std::get(_tup))); std::cout <<") "; } template<class... T> void print (const std::tuple<T...>& _tup) { print(_tup, std::make_index_sequence<sizeof...(T)>()); } |
现场演示输出:
(5, Hello, -0.1)
鉴于
1 2 | auto a = std::make_tuple(5,"Hello", -0.1); print(a); |
解释
我们的一元左折是这样的
1 | ... op pack |
其中,在我们的场景中,
1 | (..., (std::cout << std::get(myTuple)) |
所以如果我有一个这样的元组:
1 | auto myTuple = std::make_tuple(5,"Hello", -0.1); |
以及由非类型模板指定值的
1 | size_t... I |
然后是表达式
1 | (..., (std::cout << std::get(myTuple)) |
扩展到
1 | ((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple)); |
哪个会打印
5Hello-0.1
这是很粗糙的,所以我们需要做更多的技巧来添加一个逗号分隔符,除非它是第一个元素。
为此,如果当前索引
1 | (..., (std::cout << (I == 0?"" :",") << std::get(_tup))); |
现在我们可以
5, Hello, -0.1
这看起来更好(注:我想要和这个答案相似的输出)
*注意:你可以用不同的方式来做逗号分隔,而不是我最后的结果。我最初是有条件地在后面添加逗号,而不是在前面通过测试
基于BJARNE StruouTutp的C++编程语言,第817页:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <tuple> #include <iostream> #include <string> #include <type_traits> template<size_t N> struct print_tuple{ template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type print(std::ostream& os, const std::tuple<T...>& t) { char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0; os <<"," << quote << std::get<N>(t) << quote; print_tuple<N+1>::print(os,t); } template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type print(std::ostream&, const std::tuple<T...>&) { } }; std::ostream& operator<< (std::ostream& os, const std::tuple<>&) { return os <<"()"; } template<typename T0, typename ...T> std::ostream& operator<<(std::ostream& os, const std::tuple<T0, T...>& t){ char quote = (std::is_convertible<T0, std::string>::value) ? '"' : 0; os << '(' << quote << std::get<0>(t) << quote; print_tuple<1>::print(os,t); return os << ')'; } int main(){ std::tuple<> a; auto b = std::make_tuple("One meatball"); std::tuple<int,double,std::string> c(1,1.2,"Tail!"); std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; } |
输出:
1 2 3 | () ("One meatball") (1, 1.2,"Tail!") |
基于ANDYG代码的C++ 17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream> #include <tuple> template<class TupType, size_t... I> std::ostream& tuple_print(std::ostream& os, const TupType& _tup, std::index_sequence<I...>) { os <<"("; (..., (os << (I == 0 ?"" :",") << std::get(_tup))); os <<")"; return os; } template<class... T> std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup) { return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>()); } int main() { std::cout <<"deep tuple:" << std::make_tuple("Hello", 0.1, std::make_tuple(1,2,3,"four",5.5), 'Z') << std::endl; return 0; } |
输出:
1 | deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z) |
另一个类似于@tony olsson的,包括空元组的专门化,如@kerrek sb所建议的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <tuple> #include <iostream> template<class Ch, class Tr, size_t I, typename... TS> struct tuple_printer { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { tuple_printer<Ch, Tr, I-1, TS...>::print(out, t); if (I < sizeof...(TS)) out <<","; out << std::get(t); } }; template<class Ch, class Tr, typename... TS> struct tuple_printer<Ch, Tr, 0, TS...> { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { out << std::get<0>(t); } }; template<class Ch, class Tr, typename... TS> struct tuple_printer<Ch, Tr, -1, TS...> { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) {} }; template<class Ch, class Tr, typename... TS> std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { out <<"("; tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t); return out <<")"; } |
下面是另一个实现:
网址:https://github.com/galaxyeye/atlas/blob/master/atlas/io/tuple.h
测试代码:
https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/tuple.cpp
享受: