Pretty-print std::tuple
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 << ")";
}
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)
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();
}
Kerrek SB
Updated on May 26, 2021Comments
-
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). Forstd::pair<S,T>
, I simply saystd::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 about 13 years@Kerrek: I'm currently testing & fixing myself, I get weird output on Ideone though.
-
Kerrek SB about 13 yearsI 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 anoperator<<
. -
Xeo about 13 years@Kerrek: Yeah, I did some pretty strange stuff.. New version is edited, and works like requested. :P
-
Kerrek SB about 13 yearsGrand, now it works! I'll now try to include this into the pretty printer project from the previous question. Thanks a bunch!
-
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 about 13 yearsMaybe 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 about 13 yearsI 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 about 13 years@Kerrek: You can define template aliases in C++03 too, as seen here. :)
-
Kerrek SB about 13 yearsHaha, 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 almost 13 yearsCan't we replace
struct TuplePrinter
with just templated functions when using C++0x/C++11? -
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 over 11 yearsIf 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 over 11 years@Thomas: You can't just use
class Tuple
for theoperator<<
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 over 11 yearsDang, my compilation hadn't completed when I posted. Guess I need to port boost/tuple_io.hpp to std::tuple.
-
Daniel Frey about 11 years@Xeo: The code has two problems: a)
operator<<
should returnstd::basic_ostream<Ch,Traits>&
instead ofstd::ostream&
and b) it does not work with empty tuples, i.e.,std::tuple<>
. -
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 about 11 years@Xeo: Using indices won't be easy here, but try it. Main problem will be order of evaluation.
-
Xeo about 11 years@DanielFrey: That's a solved problem, list-initialization guarantees left-to-right order:
swallow{(os << get<Is>(t))...};
. -
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 about 11 years@Xeo I borrowed your swallow for cppreference, if you don't mind.
-
Kerrek SB almost 11 yearsYeah, that looks sensible - perhaps with another specialization for the empty tuple, for completeness.
-
David Rodríguez - dribeas over 9 yearsSome 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 about 9 years@DavidRodríguez-dribeas Really, you should have a namespace
print_pretty
with an expression templatepretty<T>
with an overloaded<<
that recursively handles pretty printing iterables and tuple-likes, falling back onostream << t
, and then a Koenigstd
enabledostream << 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 over 7 yearsYou could also use
if constexpr
for the base case. -
AndyG over 7 years@KerrekSB: For deciding whether to print a comma? Not a bad idea, wish it came in ternary.
-
Kerrek SB over 7 yearsA conditional expression is already a potential constant expression, so what you have is already good :-)
-
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 inmain()
, it throws a bunch of errors!, Any suggestions on simpler way to print the tuples? -
HCSF over 2 yearssorry, 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-separatedostream
... -
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 over 2 yearsAnyone using
const&
deserves an upvote (plus I used this solution). -
HEKTO about 2 yearsThis is the best answer, however parenthesis in
(os << " " << val)
aren't needed - it's not a fold expression.