Pretty-print std::tuple

30,928

Solution 1

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

Solution 2

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.

Solution 3

I got this working fine in C++11 (gcc 4.7). There are I am sure some pitfalls I have not considered but I think the code is easy to read and and not complicated. The only thing that may be strange is the "guard" struct tuple_printer that ensure that we terminate when the last element is reached. The other strange thing may be sizeof...(Types) that return the number of types in Types type pack. It is used to determine the index of the last element (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;
}

Solution 4

I'm surprised the implementation on cppreference has not already been posted here, so I'll do it for posterity. It's hidden in the doc for std::tuple_cat so it's not easy to find. It uses a guard struct like some of the other solutions here, but I think theirs is ultimately simpler and easier-to-follow.

#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

And a test:

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

Output:

(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

Live Demo

Solution 5

Leveraging on std::apply (C++17) we can drop the std::index_sequence and define a single function:

#include <tuple>
#include <iostream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t);
  return os;
}

Or, slightly embellished with the help of a stringstream:

#include <tuple>
#include <iostream>
#include <sstream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::basic_stringstream<Ch, Tr> ss;
  ss << "[ ";
  std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t);
  ss.seekp(-2, ss.cur);
  ss << " ]";
  return os << ss.str();
}
Share:
30,928
Kerrek SB
Author by

Kerrek SB

Updated on May 26, 2021

Comments

  • Kerrek SB
    Kerrek SB about 3 years

    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!

  • Xeo
    Xeo about 13 years
    @Kerrek: I'm currently testing & fixing myself, I get weird output on Ideone though.
  • Kerrek SB
    Kerrek SB about 13 years
    I think you're also confusing streams and strings. You're writing something akin to "std::cout << std::cout". In other words, TuplePrinter doesn't have an operator<<.
  • Xeo
    Xeo about 13 years
    @Kerrek: Yeah, I did some pretty strange stuff.. New version is edited, and works like requested. :P
  • Kerrek SB
    Kerrek SB about 13 years
    Grand, now it works! I'll now try to include this into the pretty printer project from the previous question. Thanks a bunch!
  • Xeo
    Xeo about 13 years
    @Kerrek: To add the delimiters from the previous question, you just need another partial spec on tuples. Included that now. The rest (basic_ostream etc) should be easy from here on.
  • Nate Kohl
    Nate Kohl about 13 years
    Maybe doesn't work so well with empty tuples, but that's easy enough to fix with another specialization or a size check in the top-level operator<<...?
  • Kerrek SB
    Kerrek SB about 13 years
    I wasn't going to deal with empty tuples, I'm happy enough that it works for oneples ;-) Hey, could one of you non-GNU people check if the template aliases in the pretty printer project work? Those would make defining custom delimiters a lot easier.
  • Xeo
    Xeo about 13 years
    @Kerrek: You can define template aliases in C++03 too, as seen here. :)
  • Kerrek SB
    Kerrek SB about 13 years
    Haha, jehova :-) Well, if you look at the prettyprinter.h code, I want a convenient way for users to define those delimiter classes for TChar = {char, wchar_t}, so I basically want to provide an "sdelims" and "wsdelims" shortcut, without making stuff more verbose. C++0x template aliases sound like the way to go, but I'm open to suggestions. Mind you, my sample case already contains a demo of custom delimiters, so the goal here is to be strictly more elegant.
  • Nordlöw
    Nordlöw almost 13 years
    Can't we replace struct TuplePrinter with just templated functions when using C++0x/C++11?
  • Xeo
    Xeo over 12 years
    @Nordlöw: No, we still can't easily, because functions still can't be partially specialized, but see my update on ways to circumvent that, even in C++03.
  • Thomas
    Thomas over 11 years
    If you don't have variadic templates, you can use class Tuple instead of class... Args and std::tuple_size<Tuple>::value instead of sizeof...(Args).
  • Xeo
    Xeo over 11 years
    @Thomas: You can't just use class Tuple for the operator<< overload - it would get picked for any and all things. It would need a constraint, which kinda implies the need for some kind of variadic arguments.
  • Thomas
    Thomas over 11 years
    Dang, my compilation hadn't completed when I posted. Guess I need to port boost/tuple_io.hpp to std::tuple.
  • Daniel Frey
    Daniel Frey about 11 years
    @Xeo: The code has two problems: a) operator<< should return std::basic_ostream<Ch,Traits>& instead of std::ostream& and b) it does not work with empty tuples, i.e., std::tuple<>.
  • Xeo
    Xeo about 11 years
    @DanielFrey: The return type was a fragment from refactoring, thanks for catching that. And yeah, empty tuple is currently broken, but can easily be fixed. In fact, I should probably rewrite this thing to just use the indices trick.
  • Daniel Frey
    Daniel Frey about 11 years
    @Xeo: Using indices won't be easy here, but try it. Main problem will be order of evaluation.
  • Xeo
    Xeo about 11 years
    @DanielFrey: That's a solved problem, list-initialization guarantees left-to-right order: swallow{(os << get<Is>(t))...};.
  • Daniel Frey
    Daniel Frey about 11 years
    @Xeo: Thanks to your hint to use initializer-lists to guarantee the order of execution, I have an elegant version with indices ready. Shall I modify your answer or write a separate one?
  • Cubbi
    Cubbi about 11 years
    @Xeo I borrowed your swallow for cppreference, if you don't mind.
  • Kerrek SB
    Kerrek SB almost 11 years
    Yeah, that looks sensible - perhaps with another specialization for the empty tuple, for completeness.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas over 9 years
    Some nitpicking... You don't want to define operators in the wrong namespace and you cannot define this one in the right namespace (::std). It would be better as a function that is called (possibly qualified) to avoid lookup failing to locate the operator.
  • Yakk - Adam Nevraumont
    Yakk - Adam Nevraumont about 9 years
    @DavidRodríguez-dribeas Really, you should have a namespace print_pretty with an expression template pretty<T> with an overloaded << that recursively handles pretty printing iterables and tuple-likes, falling back on ostream << t, and then a Koenig std enabled ostream << to_string(t), with max line length and indenting and long list tableification, and the like. You end up with a library, not a SO answer.
  • Kerrek SB
    Kerrek SB over 7 years
    You could also use if constexpr for the base case.
  • AndyG
    AndyG over 7 years
    @KerrekSB: For deciding whether to print a comma? Not a bad idea, wish it came in ternary.
  • Kerrek SB
    Kerrek SB over 7 years
    A conditional expression is already a potential constant expression, so what you have is already good :-)
  • Anu
    Anu over 5 years
    @KerrekSB, There isn't a simple way to print tuples in c++?, in python function implicitly returns a tuple and you can simply print them, in c++ in order to return the multiple variables from a function I need to pack them using std::make_tuple(). but at the time of printing it in main(), it throws a bunch of errors!, Any suggestions on simpler way to print the tuples?
  • HCSF
    HCSF over 2 years
    sorry, do you mind elaborating what kind of expression ((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple)); is? Haven't seen a list of comma-separated ostream...
  • AndyG
    AndyG over 2 years
    @HCSF: Sure, thanks for asking. this is three invocations of cout << something separated by the comma operator. The comma operator enforces that the individual expressions are evaluated in order, and the result of the expression (the returned stream) is discarded. We do it this way so that a fold expression can work in such a way that a pack expansion ... results in one function call per argument in the pack (fold expressions only work over operators). Hope that helps.
  • Carlo Wood
    Carlo Wood over 2 years
    Anyone using const& deserves an upvote (plus I used this solution).
  • HEKTO
    HEKTO about 2 years
    This is the best answer, however parenthesis in (os << " " << val) aren't needed - it's not a fold expression.