漂亮的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!") 

另一个,类似于@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 << ")"; }