Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pretty-print std::tuple

This is a follow-up to my previous question on pretty-printing STL containers, for which we managed to develop a very elegant and fully general solution.


In this next step, I would like to include pretty-printing for std::tuple<Args...>, using variadic templates (so this is strictly C++11). For std::pair<S,T>, I simply say

std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p) {   return o << "(" << p.first << ", " << p.second << ")"; } 

What is the analogous construction for printing a tuple?

I've tried various bits of template argument stack unpacking, passing indices around and using SFINAE to discover when I'm at the last element, but with no success. I shan't burden you with my broken code; the problem description is hopefully straight-forward enough. Essentially, I'd like the following behaviour:

auto a = std::make_tuple(5, "Hello", -0.1); std::cout << a << std::endl; // prints: (5, "Hello", -0.1) 

Bonus points for including the same level of generality (char/wchar_t, pair delimiters) as the the previous question!

like image 325
Kerrek SB Avatar asked Jun 05 '11 20:06

Kerrek SB


2 Answers

Yay, indices~

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 << ")"; } 

Live example on Ideone.


For the delimiter stuff, just add these partial specializations:

// 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")" }; 

and change the operator<< and print_tuple accordingly:

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; } 

And

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)...}; } 
like image 50
Xeo Avatar answered Sep 28 '22 02:09

Xeo


In C++17 we can accomplish this with a little less code by taking advantage of Fold expressions, particularly a unary left fold:

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)>()); } 

Live Demo outputs:

(5, Hello, -0.1)

given

auto a = std::make_tuple(5, "Hello", -0.1); print(a); 

Explanation

Our unary left fold is of the form

... op pack 

where op in our scenario is the comma operator, and pack is the expression containing our tuple in an unexpanded context like:

(..., (std::cout << std::get<I>(myTuple)) 

So if I have a tuple like so:

auto myTuple = std::make_tuple(5, "Hello", -0.1); 

And a std::integer_sequence whose values are specified by a non-type template (see above code)

size_t... I 

Then the expression

(..., (std::cout << std::get<I>(myTuple)) 

Gets expanded into

((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple)); 

Which will print

5Hello-0.1

Which is gross, so we need to do some more trickery to add a comma separator to be printed first unless it's the first element.

To accomplish that, we modify the pack portion of the fold expression to print " ," if the current index I is not the first, hence the (I == 0? "" : ", ") portion*:

(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup))); 

And now we'll get

5, Hello, -0.1

Which looks nicer (Note: I wanted similar output as this answer)

*Note: You could do the comma separation in a variety of ways than what I ended up with. I initially added commas conditionally after instead of before by testing against std::tuple_size<TupType>::value - 1, but that was too long, so I tested instead against sizeof...(I) - 1, but in the end I copied Xeo and we ended up with what I've got.

like image 31
AndyG Avatar answered Sep 28 '22 02:09

AndyG