Pretty-print std::tuple
Asked Answered
D

13

102

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!

Deianira answered 5/6, 2011 at 20:43 Comment(3)
Has someone put any of the code here into a library? Or even an .hpp-with-everything-in which one could grab and use?Molal
@einpoklum: Maybe cxx-prettyprint? That's what I needed that code for.Deianira
Great question, and +1 for "I shan't burden you with my broken code", although I am surprised it seems to have actually succeede in fending off the mindless "what have you tried" hordes.Gayegayel
O
80

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)...};
}
Oarlock answered 5/6, 2011 at 20:52 Comment(22)
@Kerrek: I'm currently testing & fixing myself, I get weird output on Ideone though.Oarlock
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<<.Deianira
@Kerrek: Yeah, I did some pretty strange stuff.. New version is edited, and works like requested. :POarlock
Grand, now it works! I'll now try to include this into the pretty printer project from the previous question. Thanks a bunch!Deianira
@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.Oarlock
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<<...?Woofer
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.Deianira
@Kerrek: You can define template aliases in C++03 too, as seen here. :)Oarlock
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.Deianira
Can't we replace struct TuplePrinter with just templated functions when using C++0x/C++11?Outbrave
@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.Oarlock
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).Explant
@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.Oarlock
Dang, my compilation hadn't completed when I posted. Guess I need to port boost/tuple_io.hpp to std::tuple.Explant
@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<>.Gladine
@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.Oarlock
@Xeo: Using indices won't be easy here, but try it. Main problem will be order of evaluation.Gladine
@DanielFrey: That's a solved problem, list-initialization guarantees left-to-right order: swallow{(os << get<Is>(t))...};.Oarlock
@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?Gladine
@Oarlock I borrowed your swallow for cppreference, if you don't mind.Entwistle
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.Finbur
@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.Incontestable
S
31

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.

Salvia answered 15/12, 2016 at 19:5 Comment(5)
You could also use if constexpr for the base case.Deianira
@KerrekSB: For deciding whether to print a comma? Not a bad idea, wish it came in ternary.Salvia
A conditional expression is already a potential constant expression, so what you have is already good :-)Deianira
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...Rascally
@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.Salvia
I
21

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;
}
Inconsequent answered 4/7, 2013 at 14:37 Comment(3)
Yeah, that looks sensible - perhaps with another specialization for the empty tuple, for completeness.Deianira
@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?Northing
@Northing you have answered yourself, pick another programming language, or wait for a newer c++. Overall, printing a tuple is not that important.Mana
S
18

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

Salvia answered 29/6, 2015 at 12:59 Comment(0)
S
9

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();
}
Sigma answered 16/10, 2019 at 15:52 Comment(2)
Anyone using const& deserves an upvote (plus I used this solution).Indicative
This one is cool, but gives me build errors for a more complicated tuple. I think it's not linking the std::function in this but it was a very long compile error on mac os clang. template<typename TResult, typename... TInputParams> std::tuple<std::string, TResult, std::tuple<TInputParams...>, std::optional<std::function<bool(const TResult& expected, const TResult& actual)>>, std::optional<std::function<void()>>, std::optional<std::function<void()>>, bool> It's still my preferred solution for now.Scratch
B
4

Based on AndyG code, for C++17

#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<I>(_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;
}

with output:

deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
Biflagellate answered 26/1, 2019 at 22:6 Comment(0)
P
3

Based upon example on The C++ Programming Language By Bjarne Stroustrup, page 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;
    }

Output:

()
("One meatball")
(1, 1.2, "Tail!")
Pungent answered 12/11, 2015 at 23:22 Comment(0)
M
3

I like DarioP's answer, but stringstream uses heap. This can be avoided:

template <class... Args>
std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) {
  os << "(";
  bool first = true;
  std::apply([&os, &first](auto&&... args) {
    auto print = [&] (auto&& val) {
      if (!first)
        os << ",";
      (os << " " << val);
      first = false;
    };
    (print(args), ...);
  }, t);
  os << " )";
  return os;
}
Makeshift answered 13/5, 2020 at 21:10 Comment(1)
This is the best answer, however parenthesis in (os << " " << val) aren't needed - it's not a fold expression.Borage
A
1

Another one, similar to @Tony Olsson's, including a specialization for the empty tuple, as suggested by @Kerrek SB.

#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 << ")";
}
Absent answered 30/12, 2014 at 11:13 Comment(0)
A
1

Here's some code I recently made for printing a tuple.

#include <iostream>
#include <tuple>

using namespace std;

template<typename... Ts>
ostream& operator<<(ostream& output, const tuple<Ts...> t) {
    output << '(';
    apply([&](auto&&... args) {
        ((cout << args << ", "), ...);
    }, t);
    output << "\b\b";
    output << ')';
    return output;
}

Using your example case:

auto a = std::make_tuple(5, "Hello", -0.1); 
cout << a << '\n'; // (5, Hello, -0.1)
Aldrich answered 30/11, 2020 at 0:37 Comment(1)
It actually prints (5, Hello, -0.1, )Balsamiferous
C
1

I see answers using std::index_sequence with C++17, however, that's not the road I'd go personally. I'd rather go for recursion and constexpr if:

#include <tuple>
#include <iostream>

template<std::size_t I, class... Ts>
void doPrintTuple(const std::tuple<Ts...>& tuples) {
    if constexpr (I == sizeof...(Ts)) {
        std::cout << ')';
    }
    else {
        std::cout << std::get<I>(tuples);
        if constexpr (I + 1 != sizeof...(Ts)) {
            std::cout << ", ";
        }
        doPrintTuple<I + 1>(tuples);
    }
}

template<class... Ts>
void printTuple(const std::tuple<Ts...>& tuples) {
    std::cout << '(';
    doPrintTuple<0>(tuples);
}

int main() {
    auto tup = std::make_tuple(1, "hello", 4.5);
    printTuple(tup);
}

Output:

(1, hello, 4.5)

Note that this does not work for nested tuples.

Comte answered 25/5, 2021 at 11:50 Comment(0)
C
1

Starting with C++23 you can use std::format or std::print (or std::println) to do this.

#include <print>

int main() {
    std::print("{}", std::make_tuple(1, 5.5, "text"));
}
(1, 5.5, "text")

If you need the result as std::string use std::format. Note that formatting tuples requires C++23 support, specifically the implementation of P2286R8 (check C++23 library features).

#include <format>

int main() {
    std::string text = std::format("{}", std::make_tuple(1, 5.5, "text"));
}

If you are stuck to an older standard, you can use the fmt library to print tuples.

#include <fmt/ranges.h>

int main() {
    // direct print
    fmt::print("{}", std::make_tuple(1, 5.5, "text"));

    // create std::string object
    std::string str = fmt::format("{}", std::make_tuple(1, 5.5, "text"));
}
Chaw answered 15/3, 2023 at 19:16 Comment(1)
Finally, something reasonable, without bending over backwards!Riggle
F
0

One thing I dislike about the previous answers that use fold expressions is that they use index sequences or flags to keep track of the first element, which removes much of the benefit of nice clean fold expressions.

Here is an example that does not need indexing, but achieves a similar result. (Not as sophisticated as some of the others, but more could be added.)

The technique is to use what the fold already gives you: a special case for one element. I.e., one element fold just expands to elem[0], then 2 elements is elem[0] + elem[1], where + is some operation. What we want is for one element to write just that element to the stream, and for more elements, do the same, but join each one with an additional write of ", ". So mapping this on to the c++ fold, we want each element to be the action of writing some object to the stream. We want our + operation to be to intersperse two writes with a ", " write. So first transform our tuple sequence into a sequence of write actions, CommaJoiner I have called it, then for that action add an operator+ to join two actions in the way we want, adding a ", " in between:

#include <tuple>
#include <iostream>

template <typename T>
struct CommaJoiner
{
    T thunk;
    explicit CommaJoiner(const T& t) : thunk(t) {}

    template <typename S>
    auto operator+(CommaJoiner<S> const& b) const
    {
        auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) {
            a(os);
            os << ", ";
            b(os);
        };
        return CommaJoiner<decltype(joinedThunk)>{joinedThunk};
    }

    void operator()(std::ostream& os) const
    {
        thunk(os);
    }

};

template <typename ...Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup)
{
    std::apply([&](auto ...ts) {
        return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os);

    return os;
}

int main() {
    auto tup = std::make_tuple(1, 2.0, "Hello");
    std::cout << tup << std::endl;
}

A cursory glance at godbolt suggests that this compiles quite well too, all the thunks calls being flattened.

This will need a second overload to deal with an empty tuple however.

Fideicommissum answered 11/6, 2020 at 23:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.