漂亮的std ::元组
这是我之前关于漂亮打印STL容器的问题的一个后续,为此我们开发了一个非常优雅和全面的通用解决scheme。
在接下来的步骤中,我想包含std::tuple<Args...>
漂亮打印,使用可变参数模板(所以这完全是C ++ 11)。 对于std::pair<S,T>
,我只是说
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p) { return o << "(" << p.first << ", " << p.second << ")"; }
什么是打印元组的类似结构?
我尝试了各种各样的模板参数堆栈解包,传递索引,并使用SFINAE来发现当我在最后一个元素,但没有成功。 我不会用我破碎的代码来加重你的负担。 问题描述有希望足够简单。 基本上,我想要以下行为:
auto a = std::make_tuple(5, "Hello", -0.1); std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
与上一个问题一样包含相同级别的通用性(char / wchar_t,一对分隔符)!
耶, 指数 〜
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上的生动的例子。
对于分隔符的东西,只需添加这些部分专业化:
// 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")" };
并相应地更改operator<<
和print_tuple
:
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; }
和
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_printer确保当到达最后一个元素时我们终止。 另一个奇怪的事情可能是sizeof …(Types)返回Typestypes包中的types数量。 它用于确定最后一个元素的索引(size …(Types) – 1)。
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的实现还没有在这里发布,所以我会为后代做。 它隐藏在std::tuple_cat
的doc中,所以不容易find。 它像这里的其他一些解决scheme一样使用一个保护结构,但是我认为他们最终更简单,更容易遵循。
#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 << ")\n"; } // end helper function
还有一个testing:
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中,我们可以通过利用折叠expression式 (尤其是一元左alignment)的less量代码来实现:
template<class TupType, size_t... I> void print(const TupType& _tup, std::index_sequence<I...>) { std::cout << "("; (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup))); std::cout << ")\n"; } template<class... T> void print (const std::tuple<T...>& _tup) { print(_tup, std::make_index_sequence<sizeof...(T)>()); }
现场演示输出:
(5,你好,-0.1)
特定
auto a = std::make_tuple(5, "Hello", -0.1); print(a);
说明
我们的一元左折是forms
... op pack
在我们的场景中, op
是逗号运算符, pack
是包含我们元组的expression式,如下所示:
(..., (std::cout << std::get<I>(myTuple))
所以,如果我有这样一个元组:
auto myTuple = std::make_tuple(5, "Hello", -0.1);
而一个std::integer_sequence
的值由一个非types模板指定(见上面的代码)
size_t... I
然后expression
(..., (std::cout << std::get<I>(myTuple))
得到扩展
((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));
哪个会打印
5Hello-0.1
这是毛病,所以我们需要做更多的诡计来添加一个逗号分隔符,先打印,除非它是第一个元素。
为了做到这一点,我们修改折叠expression式的pack
部分" ,"
如果当前索引I
不是第一个,则打印" ,"
因此, (I == 0? "" : ", ")
部分* :
(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
现在我们会得到
5,你好,-0.1
看起来更好(注意:我想这个答案类似的输出)
*注意:你可以用不同的方式进行逗号分隔。 我最初通过testingstd::tuple_size<TupType>::value - 1
来有条件地添加逗号,但是太长了,所以我testing了sizeof...(I) - 1
,但最后我复制了Xeo ,并且结束了我所拥有的。
基于Bjarne Stroustrup的C ++编程语言(第817页)的示例:
#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; }
输出:
() ("One meatball") (1, 1.2, "Tail!")
这是另一个实现:
https://github.com/galaxyeye/atlas/blob/master/atlas/io/tuple.h
用testing代码:
https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/tuple.cpp
请享用 :)
另一个,类似于@Tony Olsson的,包括专门为空元组,正如@Kerrek SB所build议的。
#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<I>(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 << ")"; }