Pretty-Print Std::Tuple

Pretty-print std::tuple

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

How to print std::tuple iteratively

Here's one of the possible solutions:

#include <cstddef>
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

template <typename T, std::size_t ...I, typename F>
void tuple_foreach_impl(T &&tuple, std::index_sequence<I...>, F &&func)
{
// In C++17 we would use a fold expression here, but in C++14 we have to resort to this.
using dummy_array = int[];
dummy_array{(void(func(std::get<I>(tuple))), 0)..., 0};
}

template <typename T, typename F> void tuple_foreach(T &&tuple, F &&func)
{
constexpr int size = std::tuple_size<std::remove_reference_t<T>>::value;
tuple_foreach_impl(std::forward<T>(tuple), std::make_index_sequence<size>{},
std::forward<F>(func));
}

int main()
{
auto x = std::make_tuple("Meow", 1, 2.3);

tuple_foreach(x, [](auto &&value)
{
std::cout << value << ' ';
});
// Prints:
// Meow
// 1
// 2.3
}

With tuple_foreach making a proper printer should be simple.

template <typename T> void print_tuple(const T &tuple)
{
std::cout << '{';
tuple_foreach(tuple, [first = true](auto &value) mutable
{
if (!first)
std::cout << "; ";
else
first = 0;
std::cout << value;
});
std::cout << '}';
}

// ...

print_tuple(std::make_tuple("Meow", 1, 2.3)); // Prints `{Meow; 1; 2.3}`

Anatomy of pretty print tuple

Let's go through an example with an arbitrary tuple, say:

using Tuple = tuple<char, int, string>;

Thus, the integer sequence that our function will be called with is:

seq<0, 1, 2>

And our pack expansion in the body is:

(void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};

which, if we manually expand it the way the compiler would, becomes:

(void)swallow{0,
(void(os << (0 == 0? "" : ", ") << std::get<0>(t)), 0),
(void(os << (1 == 0? "" : ", ") << std::get<1>(t)), 0),
(void(os << (2 == 0? "" : ", ") << std::get<2>(t)), 0)
};

And then evaluating the branches:

(void)swallow{0,
(void(os << "" << std::get<0>(t)), 0),
(void(os << ", " << std::get<1>(t)), 0),
(void(os << ", " << std::get<2>(t)), 0)
};

Which is to say, we're constructing an integer array of 4 0s, with the side effect of printing out the contents of the tuple, comma-separated, making sure we don't start with an extra comma. The four expressions must be evaluated in order, so that guarantees that the tuple contents get printed in order.

The initial (void) cast is just there to avoid the unused-variable warning that compilers should emit if you have all warnings turned on. The initial 0 in the array initialization handles the case where the tuple is empty.

How to print a type vector tuple string, int, int to screen c++?

Just iterate the vector, printing each tuple value using cout:

for (const auto& i : tupleVector) {
cout << get<0>(i) << ", " << get<1>(i) << ", " << get<2>(i) << endl;
}

GDB: How do we extract values from an std::tuple

Unfortunately, the pretty-printing code in gdb is a display-only feature -- so while it helps display a tuple in a nice way, it doesn't let you further access it.

The normal problem with something like t.get<0> is that these tiny accessor methods are usually completely optimized away by the compiler -- so there is no copy to call. And, while gdb has an "xmethod" feature that can be used to supply gdb-side Python implementations of these accessors, info xmethods shows (for me at least) that nobody has done this for std::tuple yet.

So then you're pretty much left with just one option: inspecting the implementation. So, start by printing the raw tuple:

(gdb) p/r t
$3 = {<std::_Tuple_impl<0ul, int, int>> = {<std::_Tuple_impl<1ul, int>> = {<std::_Head_base<1ul, int, false>> = {
_M_head_impl = 222}, <No data fields>}, <std::_Head_base<0ul, int, false>> = {_M_head_impl = 111}, <No data fields>}, <No data fields>}

Here you can see the "real" structure of the tuple, and access the fields directly:

(gdb) print ((std::_Head_base<1ul, int, false>) t)._M_head_impl
$7 = 222

This cast to an intermediate type is kind of a pain, eh? It's needed to make gdb choose the correct _M_head_impl field. If this is something you plan to do a lot I'd suggest writing that xmethod. Or you could also easily write a Python convenience function to automate the access; this kind of introspection is a bit simpler to do with the Python API.

How do I print out the contents of a vector?

If you have a C++11 compiler, I would suggest using a range-based for-loop (see below); or else use an iterator. But you have several options, all of which I will explain in what follows.

Range-based for-loop (C++11)

In C++11 (and later) you can use the new range-based for-loop, which looks like this:

std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';

The type char in the for-loop statement should be the type of the elements of the vector path and not an integer indexing type. In other words, since path is of type std::vector<char>, the type that should appear in the range-based for-loop is char. However, you will likely often see the explicit type replaced with the auto placeholder type:

for (auto i: path)
std::cout << i << ' ';

Regardless of whether you use the explicit type or the auto keyword, the object i has a value that is a copy of the actual item in the path object. Thus, all changes to i in the loop are not preserved in path itself:

std::vector<char> path{'a', 'b', 'c'};

for (auto i: path) {
i = '_'; // 'i' is a copy of the element in 'path', so although
// we can change 'i' here perfectly fine, the elements
// of 'path' have not changed
std::cout << i << ' '; // will print: "_ _ _"
}

for (auto i: path) {
std::cout << i << ' '; // will print: "a b c"
}

If you would like to proscribe being able to change this copied value of i in the for-loop as well, you can force the type of i to be const char like this:

for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}

If you would like to modify the items in path so that those changes persist in path outside of the for-loop, then you can use a reference like so:

for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}

and even if you don't want to modify path, if the copying of objects is expensive you should use a const reference instead of copying by value:

for (const auto& i: path)
std::cout << i << ' ';

Iterators

Before C++11 the canonical solution would have been to use an iterator, and that is still perfectly acceptable. They are used as follows:

std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

If you want to modify the vector's contents in the for-loop, then use iterator rather than const_iterator.

Supplement: typedef / type alias (C++11) / auto (C++11)

This is not another solution, but a supplement to the above iterator solution. If you are using the C++11 standard (or later), then you can use the auto keyword to help the readability:

for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

Here the type of i will be non-const (i.e., the compiler will use std::vector<char>::iterator as the type of i). This is because we called the begin method, so the compiler deduced the type for i from that. If we call the cbegin method instead ("c" for const), then i will be a std::vector<char>::const_iterator:

for (auto i = path.cbegin(); i != path.cend(); ++i) {
*i = '_'; // will produce a compiler error
std::cout << *i << ' ';
}

If you're not comfortable with the compiler deducing types, then in C++11 you can use a type alias to avoid having to type the vector out all the time (a good habit to get into):

using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

If you do not have access to a C++11 compiler (or don't like the type alias syntax for whatever reason), then you can use the more traditional typedef:

typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

Side note:

At this point, you may or may not have come across iterators before, and you may or may not have heard that iterators are what you are "supposed" to use, and may be wondering why. The answer is not easy to appreciate, but, in brief, the idea is that iterators are an abstraction that shield you from the details of the operation.

It is convenient to have an object (the iterator) that does the operation you want (like sequential access) rather than you writing the details yourself (the "details" being the code that does the actual accessing of the elements of the vector). You should notice that in the for-loop you are only ever asking the iterator to return you a value (*i, where i is the iterator) -- you are never interacting with path directly itself. The logic goes like this: you create an iterator and give it the object you want to loop over (iterator i = path.begin()), and then all you do is ask the iterator to get the next value for you (*i); you never had to worry exactly how the iterator did that -- that's its business, not yours.

OK, but what's the point? Well, imagine if getting a value wasn't simple. What if it involves a bit of work? You don't need to worry, because the iterator has handled that for you -- it sorts out the details, all you need to do is ask it for a value. Additionally, what if you change the container from std::vector to something else? In theory, your code doesn't change even if the details of how accessing elements in the new container does: remember, the iterator sorts all the details out for you behind the scenes, so you don't need to change your code at all -- you just ask the iterator for the next value in the container, same as before.

So, whilst this may seem like confusing overkill for looping through a vector, there are good reasons behind the concept of iterators and so you might as well get used to using them.

Indexing

You can also use a integer type to index through the elements of the vector in the for-loop explicitly:

for (int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

If you are going to do this, it's better to use the container's member types, if they are available and appropriate. std::vector has a member type called size_type for this job: it is the type returned by the size method.

typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

Why not use this in preference to the iterator solution? For simple cases, you can do that, but using an iterator brings several advantages, which I have briefly outlined above. As such, my advice would be to avoid this method unless you have good reasons for it.

std::copy (C++11)

See Joshua's answer. You can use the STL algorithm std::copy to copy the vector contents onto the output stream. I don't have anything to add, except to say that I don't use this method; but there's no good reason for that besides habit.

std::ranges::copy (C++20)

For completeness, C++20 introduced ranges, which can act on the whole range of a std::vector, so no need for begin and end:

#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support

std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));

Unless you have a recent compiler (on GCC apparently at least version 10.1), likely you will not have ranges support even if you might have some C++20 features available.

Overload std::ostream::operator<<

See also Chris's answer below. This is more a complement to the other answers since you will still need to implement one of the solutions above in the overloading, but the benefit is much cleaner code. This is how you could use the std::ranges::copy solution above:

#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support

using Path = std::vector<char>; // type alias for std::vector<char>

std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}

int main() {
Path path{'/', 'f', 'o', 'o'};

// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;

return 0;
}

Now you can pass your Path objects to your output stream just like fundamental types. Using any of the other solutions above should also be equally straightforward.

Conclusion

Any of the solutions presented here will work. It's up to you (and context or your coding standards) on which one is the "best". Anything more detailed than this is probably best left for another question where the pros/cons can be properly evaluated, but as always user preference will always play a part: none of the solutions presented are objectively wrong, but some will look nicer to each coder.

Addendum

This is an expanded solution of an earlier one I posted. Since that post kept getting attention, I decided to expand on it and refer to the other excellent solutions posted here, at least those that I have personally used in the past at least once. I would, however, encourage the reader to look at the answers below because there are probably good suggestions that I have forgotten, or do not know, about.

access and print data in a tuple and display it using a template function using C++14

You can get by creating your own for_tuple function to iterate over tuples:

template<typename T, typename F, std::size_t... S>
void for_tuple_impl(std::index_sequence<S...>, T&& tup, F& function) {
using expand_t = int[];
(void) expand_t{(void(function(std::get<S>(std::forward<T>(tup)))), 0)..., 0};
}

template<typename T, typename F>
void for_tuple(T&& tup, F function) {
for_tuple_impl(
std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>{},
std::forward<T>(tup),
function
);
}

Then use it like this:

for_tuple(my_tuple, [](auto&& mixed_ele){ std::cout << mixed_ele << ' '; });

C++23 wants to make a language feature called a template for to replace that pile of brakets and parens (not official yet):

template for (auto&& mixed_ele : mixed) {
std::cout << mixed_ele << ' ';
}


Related Topics



Leave a reply



Submit