Printing a Range to a Stream

Problem

You have a range of elements that you want to print to a stream, most likely cout for debugging.

Solution

Write a function template that takes a range or a container, iterates through each element, and uses the copy algorithm and an ostream_iterator to write each element to a stream. If you want more control over formatting, write your own simple algorithm that iterates through a range and prints each element to the stream. (See Example 7-11.)

Example 7-11. Printing a range to a stream

#include #include #include #include #include using namespace std; int main( ) { // An input iterator is the opposite of an output iterator: it // reads elements from a stream as if it were a container. cout << "Enter a series of strings: "; istream_iterator start(cin); istream_iterator end; vector v(start, end); // Treat the output stream as a container by using an // output_iterator. It constructs an output iterator where writing // to each element is equivalent to writing it to the stream. copy(v.begin( ), v.end( ), ostream_iterator(cout, ", ")); }

The output for Example 7-11 might look like this:

Enter a series of strings: z x y a b c ^Z z, x, y, a, b, c,

 

Discussion

A stream iterator is an iterator that is based on a stream instead of a range of elements in some container, and stream iterators allow you to treat stream input as an input iterator (read from the dereferenced value and increment the iterator) or an output iterator (just like an input iterator, but you write to its dereferenced value instead of read from it). This makes for concise reading of values (especially strings) from a stream, which is what I have done in a number of other examples in this chapter, and writing values to a stream, which is what I have done in Example 7-11. I know this recipe is about printing a range to a stream, but allow me to stray from the path for a moment to explain input stream iterators since I use them in so many examples in this chapter.

There are three key parts to the istream_iterator in Example 7-11. The first part is creating the istream_iterator that refers to the start of the stream input. I do it like this:

istream_iterator start(cin);

This creates an iterator named start that refers to the first element in the input sequence, just as vec.begin (vec is a vector) returns an iterator that refers to the first element in a vector. The template argument string tells the istream_iterator that the elements in this sequence are strings. The constructor argument cin is the input stream to read from. This is an abstraction, though, because there is no first element at this point because nothing has come in from cin. That will happen in a moment.

The second part to the input stream iterator is the end marker, which I created like this:

istream_iterator end;

The standard containers use the special value of one past the end to indicate the point at which any algorithm using the range should stop. Since an input stream iterator has no actual last element in memory, it uses a constructor with no arguments to create a logical endpoint value that represents the point at which any algorithm should stop iterating.

The last part of the istream_iterator technique is how I use it to extract values. A convenient way to pull all values entered on a stream into a container is to use the container's range constructor. For example, if you construct a vector with two iterators, the constructor will copy each element out of the range the iterators refer to into itself. If I pass in the start and end iterators I just created, it looks like this:

vector v(start, end);

This is where the values are actually read from the stream. When v is constructed, it starts at start and iterates forward until it reaches end. Each time v reads from *start, it is equivalent to invoking something like this on cin:

cin >> v[i]; // v is a vector

In other words, the next value is pulled from cin, is converted to a string, and is inserted into the vector.

When you are using cin as the input stream, it is up to your platform to decide what constitutes an end-of-file marker where the stream should end. On Windows, I have to press Enter, Ctrl-Z, Enter to end the stream input. Experiment on your platform to see what you have to do, but chances are it's a combination of these same key combinations.

Output stream iterators behave similarly to input stream iterators. In Example 7-11, I copy from my vector of values to cout by creating an ostream_iterator that refers to cout, like this:

copy(v.begin( ), v.end( ), ostream_iterator(cout, ", "));

The template argument to ostream_iterator tells it that the elements I will be assigning to it are strings. The first constructor argument to ostream_iterator is the stream I will write to (which can be any output stream, including ofstreams and ostringstreams) and the second is the delimiter I want to use. This provides a handy way to dump a range of values to the standard output, which I do often when debugging.

If you want more control over the appearance of the outputsuch as wrapping the sequence with brackets or curly braces, or avoiding the last delimiter on the end of the sequencedoing so requires only a few more lines of code. Example 7-12 shows the body of printContainer and printRange, the first of which I have been using throughout examples in this chapter.

Example 7-12. Writing your own printing function

#include #include #include #include #include using namespace std; template void printContainer(const C& c, char delim = ',', ostream& out = cout) { printRange(c.begin( ), c.end( ), delim, out); } template void printRange(Fwd first, Fwd last, char delim = ',', ostream& out = cout) { out << "{"; while (first != last) { out << *first; if (++first != last) out << delim << ' '; } out << "}" << endl; } int main( ) { cout << "Enter a series of strings: "; istream_iterator start(cin); istream_iterator end; vector v(start, end); printContainer(v); printRange(v.begin( ), v.end( ), ';', cout); }

The function printRange is the more general approach, since it operates on a range (this is explained in more detail in Recipe 7.10), but printContainer is more convenient for printing an entire container. There are many more ways to do this. A couple that come to mind are defining a version of operator<< that operates on an output stream and a container and using the for_each standard algorithm with a custom functor to write out each element in a stream.

Категории