iostream
IteratorsEven though the iostream
types are not containers, there are iterators that can be used with objects of the IO types (§ 8.1, p. 310). An istream_iterator
(Table 10.3 (overleaf)) reads an input stream, and an ostream_iterator
(Table 10.4 (p. 405)) writes an output stream. These iterators treat their corresponding stream as a sequence of elements of a specified type. Using a stream iterator, we can use the generic algorithms to read data from or write data to stream objects.
istream_iterators
When we create a stream iterator, we must specify the type of objects that the iterator will read or write. An istream_iterator
uses >>
to read a stream. Therefore, the type that an istream_iterator
reads must have an input operator defined. When we create an istream_iterator
, we can bind it to a stream. Alternatively, we can default initialize the iterator, which creates an iterator that we can use as the off-the-end value.
istream_iterator<int> int_it(cin); // reads ints from cin
istream_iterator<int> int_eof; // end iterator value
ifstream in("afile");
istream_iterator<string> str_it(in); // reads strings from "afile"
As an example, we can use an istream_iterator
to read the standard input into a vector
:
istream_iterator<int> in_iter(cin); // read ints from cin
istream_iterator<int> eof; // istream ''end'' iterator
while (in_iter != eof) // while there's valid input to read
// postfix increment reads the stream and returns the old value of the iterator
// we dereference that iterator to get the previous value read from the stream
vec.push_back(*in_iter++);
This loop reads int
s from cin
, storing what was read in vec
. On each iteration, the loop checks whether in_iter
is the same as eof
. That iterator was defined as the empty istream_iterator
, which is used as the end iterator. An iterator bound to a stream is equal to the end iterator once its associated stream hits end-of-file or encounters an IO error.
The hardest part of this program is the argument to push_back
, which uses the dereference and postfix increment operators. This expression works just like others we’ve written that combined dereference with postfix increment (§ 4.5, p. 148). The postfix increment advances the stream by reading the next value but returns the old value of the iterator. That old value contains the previous value read from the stream. We dereference that iterator to obtain that value.
What is more useful is that we can rewrite this program as
istream_iterator<int> in_iter(cin), eof; // read ints from cin
vector<int> vec(in_iter, eof); // construct vec from an iterator range
Here we construct vec
from a pair of iterators that denote a range of elements. Those iterators are istream_iterator
s, which means that the range is obtained by reading the associated stream. This constructor reads cin
until it hits end-of-file or encounters an input that is not an int
. The elements that are read are used to construct vec
.
Because algorithms operate in terms of iterator operations, and the stream iterators support at least some iterator operations, we can use stream iterators with at least some of the algorithms. We’ll see in § 10.5.1 (p. 410) how to tell which algorithms can be used with the stream iterators. As one example, we can call accumulate
with a pair of istream_iterators
:
istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;
This call will generate the sum of values read from the standard input. If the input to this program is
23 109 45 89 6 34 12 90 34 23 56 23 8 89 23
then the output will be 664
.
istream_iterator
s Are Permitted to Use Lazy EvaluationWhen we bind an istream_iterator
to a stream, we are not guaranteed that it will read the stream immediately. The implementation is permitted to delay reading the stream until we use the iterator. We are guaranteed that before we dereference the iterator for the first time, the stream will have been read. For most programs, whether the read is immediate or delayed makes no difference. However, if we create an istream_iterator
that we destroy without using or if we are synchronizing reads to the same stream from two different objects, then we might care a great deal when the read happens.
ostream_iterator
sAn ostream_iterator
can be defined for any type that has an output operator (the <<
operator). When we create an ostream_iterator
, we may (optionally) provide a second argument that specifies a character string to print following each element. That string must be a C-style character string (i.e., a string literal or a pointer to a null-terminated array). We must bind an ostream_iterator
to a specific stream. There is no empty or off-the-end ostream_iterator
.
Table 10.4: ostream_iterator
Operations
We can use an ostream_iterator
to write a sequence of values:
ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
*out_iter++ = e; // the assignment writes this element to cout
cout << endl;
This program writes each element from vec
onto cout
following each element with a space. Each time we assign a value to out_iter
, the write is committed.
It is worth noting that we can omit the dereference and the increment when we assign to out_iter
. That is, we can write this loop equivalently as
for (auto e : vec)
out_iter = e; // the assignment writes this element to cout
cout << endl;
The *
and ++
operators do nothing on an ostream_iterator
, so omitting them has no effect on our program. However, we prefer to write the loop as first presented. That loop uses the iterator consistently with how we use other iterator types. We can easily change this loop to execute on another iterator type. Moreover, the behavior of this loop will be clearer to readers of our code.
Rather than writing the loop ourselves, we can more easily print the elements in vec
by calling copy
:
copy(vec.begin(), vec.end(), out_iter);
cout << endl;
We can create an istream_iterator
for any type that has an input operator (>>
). Similarly, we can define an ostream_iterator
so long as the type has an output operator (<<
). Because Sales_item
has both input and output operators, we can use IO iterators to rewrite the bookstore program from § 1.6 (p. 24):
istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, "
");
// store the first transaction in sum and read the next record
Sales_item sum = *item_iter++;
while (item_iter != eof) {
// if the current transaction (which is stored in item_iter) has the same ISBN
if (item_iter->isbn() == sum.isbn())
sum += *item_iter++; // add it to sum and read the next transaction
else {
out_iter = sum; // write the current sum
sum = *item_iter++; // read the next transaction
}
}
out_iter = sum; // remember to print the last set of records
This program uses item_iter
to read Sales_item
transactions from cin
. It uses out_iter
to write the resulting sums to cout
, following each output with a newline. Having defined our iterators, we use item_iter
to initialize sum
with the value of the first transaction:
// store the first transaction in sum and read the next record
Sales_item sum = *item_iter++;
Here, we dereference the result of the postfix increment on item_iter
. This expression reads the next transaction, and initializes sum
from the value previously stored in item_iter
.
The while
loop executes until we hit end-of-file on cin
. Inside the while
, we check whether sum
and the record we just read refer to the same book. If so, we add the most recently read Sales_item
into sum
. If the ISBNs differ, we assign sum
to out_iter
, which prints the current value of sum
followed by a newline. Having printed the sum for the previous book, we assign sum
a copy of the most recently read transaction and increment the iterator, which reads the next transaction. The loop continues until an error or end-of-file is encountered. Before exiting, we remember to print the values associated with the last book in the input.
Exercise 10.29: Write a program using stream iterators to read a text file into a vector
of string
s.
Exercise 10.30: Use stream iterators, sort
, and copy
to read a sequence of integers from the standard input, sort them, and then write them back to the standard output.
Exercise 10.31: Update the program from the previous exercise so that it prints only the unique elements. Your program should use unqiue_copy
(§ 10.4.1, p. 403).
Exercise 10.32: Rewrite the bookstore problem from § 1.6 (p. 24) using a vector
to hold the transactions and various algorithms to do the processing. Use sort
with your compareIsbn
function from § 10.3.1 (p. 387) to arrange the transactions in order, and then use find
and accumulate
to do the sum.
Exercise 10.33: Write a program that takes the names of an input file and two output files. The input file should hold integers. Using an istream_iterator
read the input file. Using ostream_iterator
s, write the odd numbers into the first output file. Each value should be followed by a space. Write the even numbers into the second file. Each of these values should be placed on a separate line.