Output Stream Iterators
An istream_iterator lets you treat an input stream as a read-only container of values.
Treating Input as a Sequence
Many programs need to read a sequence of values from the keyboard or from a file until there is nothing left to read. A common first attempt is to write a loop that calls an input operation again and again, managing each read by hand. An istream_iterator takes a different route. It lets an input stream be handled as though it were a container of values that can be stepped through one element at a time.
What do you think happens?
Suppose the input is the three numbers 4 9 2, and a loop reads them through an istream_iterator<int> and prints each value it dereferences. What gets printed?
Reveal answer
All three numbers, 4 then 9 then 2
The iterator presents the stream as a sequence. Each dereference yields the value the iterator currently refers to, and each increment moves on to the next value the stream can supply, so the loop walks through all three values in order.
Creating an istream_iterator
Creating an istream_iterator requires two things: the input stream it will read from, and the type of value each read should produce. In a declaration, the angle brackets name the value type and the parentheses name the stream. Changing either one changes what the iterator does.
#include <iostream>
#include <iterator>
int main() {
std::istream_iterator<int> ii(std::cin);
std::istream_iterator<int> eos;
while (ii != eos) {
std::cout << *ii << std::endl;
++ii;
}
}- 1Line 4 creates ii, attached to the standard input stream and declared to produce int values.
- 2Line 5 creates eos with no stream argument. This default iterator marks the end of the input.
- 3Line 6 compares ii against eos, so the loop runs only while there is still a value to read.
- 4Line 7 dereferences ii to obtain the current value and prints it.
- 5Line 8 advances ii, moving the iterator on to the next value in the stream.
- std::
istream_iterator<int> names the value type: each read should produce an int - (std::cin) names the stream the iterator is attached to
- eos is a default-constructed
istream_iterator<int>, used as the end-of-input marker - *ii dereferences the iterator to get the value it currently refers to
- ++ii advances the iterator to the next value the stream can supply
4
9
2The program never asks cin for a value directly. It works through the single object ii, which stands in for the whole input stream. From the loop's point of view, the input is a sequence it can walk through, even though the data is arriving from outside the program.
Choosing the Value Type
The value type is not decided by the stream. Reading with istream_iterator<string> asks for string values, while istream_iterator<int> asks for integer values from the same stream. Because the choice of type is yours, it is worth deciding it deliberately from the kind of data the input is supposed to contain.
Think of the value type as a promise about each item. Every read tries to produce one value of that type from the stream. If the input contains items the declared type cannot hold, the read fails, and the iterator's behavior at that point is what determines the rest of the output.
Why Iterators Come in Pairs
A single istream_iterator tells you where reading starts, but reading a sequence also needs to know where it stops. Input iterators are therefore used in pairs that together represent a sequence. The first member of the pair is the iterator that begins the reading, and the second is an iterator that marks the end of the input. In the program above, ii plays the first role and eos plays the second.
The pair works together: the loop starts with ii and keeps going while ii is not equal to eos. Without the second member there is no agreed position at which the loop should stop, so the pair is what turns an unbounded stream into a sequence with a defined end.
Tracing a Read Loop Step by Step
Tracing means following the iterator through each pass of the loop and recording what it refers to and what the loop prints. Work through the program with the input 4 9 2 and watch the values appear one per iteration.
Tracing the loop with input 4 9 2
Trace the loop in the program above when the input is 4 9 2, and state exactly what the program prints.
- 1Before the loop
ii is attached to cin and refers to the first value, 4. eos is the end marker.
- 2Iteration 1
ii is not equal to eos, so the body runs. *ii gives 4, which is printed. ++ii moves ii to 9.
- 3Iteration 2
ii refers to 9, which is not the end marker. 9 is printed, and ++ii moves ii to 2.
- 4Iteration 3
ii refers to 2. 2 is printed, and ++ii moves past the last value.
- 5Check after iteration 3
There is nothing left to read, so ii now compares equal to eos. The loop condition fails and the loop ends.
The program prints 4, 9, and 2, one per line, and then stops.
What Happens at End of Input
The loop stops because of what happens inside the iterator when there is no value left to extract. When the underlying extraction fails, the iterator moves into a state where it compares equal to the default end iterator. The loop's comparison against eos then succeeds in signalling that reading has finished.
The same mechanism explains a subtler behavior. If the input contains an item that cannot be read as the declared type, the extraction fails at that item even though more text remains. Reading stops there, and the loop ends early. Nothing in the loop complains about it, which is why this kind of error often looks like missing output rather than an error message.
Mistakes Beginners Make
Common mistake Creating the iterator without naming the stream
Why it failsThe iterator must know which stream it reads from. Without the stream, it is not attached to the input the program means to read.
Do this insteadPass the intended stream in the parentheses, for example std::istream_iterator<int> ii(std::cin).
Common mistake Using only one iterator and no end marker
What it looks likewhile (ii != ...) with no second iterator to compare againstWhy it failsReading a sequence needs a defined end. A single iterator gives no agreed position at which the loop should stop.
Do this insteadCreate the end marker as a default istream_iterator and compare the loop against it.
Common mistake Choosing a value type that does not match the data
What it looks likeistream_iterator<int> reading the input 3 abc
Why it failsThe read of abc cannot produce an int, so extraction fails there and the loop stops early, after printing only 3.
Do this insteadDecide the value type from the kind of data the input contains, and check that every item fits it.
Common mistake Expecting the iterator to write data back into the stream
Practice Tracing a Read Loop
A program reads with istream_iterator<int> and prints each value, using the same loop as above. The input is 5 8 oops 7. Predict exactly what the program prints, and identify the step at which the output stops matching what you would expect from reading every number.
Hints
- Follow the iterator through each iteration and note which token is read on each one.
- Ask whether oops can be produced as an int, and what the iterator does when it cannot.
Key Takeaways
- An
istream_iteratortreats an input stream as a read-only container of values that can be stepped through with dereference and increment. - Creating one requires naming the stream to read from and the value type each read should produce.
- Input iterators are used in pairs: a begin iterator starts the reading, and a default
istream_iteratorserves as the end marker the loop compares against. - When extraction fails or input ends, the iterator becomes equal to the end marker, so the loop stops; an item that does not fit the value type can stop reading early.
- When output is wrong, check the attached stream, the value type, and the end-marker comparison, in that order.