Printing a container

It is such a common task to dump a container to the standard output console that I have written a template function to do this job. (Actually, this solution does not work for maps - in another post I have changed a bit the code to overcome this issue - but this post is still useful as an example for ostream_iterator usage).

There are a few interesting features in a handful of lines: it is a template function getting in input a couple of InputIterators, and uses the std::copy algorithm and the std::ostream_iterator to get its result.

But let's start from the beginning. We have a container, with some data in it:
std::list<int> list1;

list1.push_back(12);
list1.push_back(43);
list1.push_back(44);
list1.push_back(21);
We want to dump it to the console. A trivial way to do that is using a function tailored on the current container we are using:
void dump(std::list<int>& l)
{
  std::list<int>::iterator it;
  for(it = l.begin(); it != l.end(); ++it)
    std::cout << *it << ' ';
  std::cout << std::endl;
}

This works, but it is far from being an optimal solution.
Any time we use a different container, and even if just the typename changes, we have to write another function. Besides, we would like to remove the for loop and using instead a standard algorithm. Moreover, it would be nice if we could print our container on other streams than the output console, and let the user choose for the delimiter between the elements. And, why dumping always all the container, wouldn't be better letting the caller specify the interval to print?

Lots of requirements, as you can see, but it is easy to achieve a solution:
template <typename T, typename InputIterator>
void dump(std::ostream& os, InputIterator begin, InputIterator end,
const char* const delimiter = " ")
{
  std::copy(begin, end, std::ostream_iterator<T>(os, delimiter));
}
One thing is still missing. In the original function the print was completed with an end of line, that here doesn't make much sense. And, the normal usage - at least in my case - of this function is actually for dumping the container to the output console. So I thought it was a good idea to provide this overload:
template <typename T, typename InputIterator>
inline void dump(InputIterator begin, InputIterator end)
{
  dump<T>(std::cout, begin, end);
  std::cout << std::endl;
}
Now we can dump our list to the standard output in this way:
dump<int>(list1.begin(), list1.end());
Nice result, still, do we really have to specify the value type we are about to dump? Actually not, since this information is already stored in the iterator itself.

Our task now is calling the dump function in this way:
dump(vi.begin(), vi.end());
To do that, we remove the typename T reference in the utility dump overload:
template <typename InputIterator>
inline void dump(InputIterator begin, InputIterator end)
{
  dump(std::cout, begin, end);
  std::cout << std::endl;
}
And rewrite the actual dump function like this:
template <typename InputIterator>
inline void dump(std::ostream& os, InputIterator begin, InputIterator end,
const char* const delimiter = " ")
{
  typedef std::iterator_traits::value_type T;
  std::copy(begin, end, std::ostream_iterator(os, delimiter));
}
To extract the information on the data type underneath the iterator, we use the iterator_traits template, that makes it available as a typedef (named value_type).

Right, but we often want to dump the entire container, so it would be friendly to offer another overload, that expects as input parameter just the container itself. By the way, providing it, we keep our new dump functionality compatible with the original dump showed at the beginning of this post:
template <typename Container>
inline void dump(const Container& c) { dump(c.begin(), c.end()); }

No comments:

Post a Comment