Stream Formatting and Buffering
An iostream has a state that can be examined to tell whether the last operation succeeded.
Predicting When a Read Loop Stops
An iostream carries a state that you can examine to determine whether an operation succeeded. The most common use of that state is reading a sequence of values from a stream. A typical reading function loops over the input, stores each value it reads, and stops when a read fails. The question this article answers is simple to ask and easy to get wrong: when the loop finally stops, why did it stop, and what has the container received up to that point?
What do you think happens?
The stream contains the text 1 2 x 3. The first two items are integers, the third is not, and a fourth integer follows the bad item. A loop reads integers into a vector one at a time, using the stream itself as the loop condition. What does the vector contain when the loop finishes?
Reveal answer
1 2
The loop stores 1 and then 2. When it tries to read x as an integer, the stream is no longer ready, the loop condition becomes false, and the loop exits. The 3 that follows x is never read, even though it is a valid integer, because the loop never gets past the bad item.
The Read Loop and Its Single Condition
The function below reads integers from an input stream into a vector and returns the vector. It is a reconstruction of the pattern described in the source: a single while condition does all the work. The loop does not count values, and it does not look for an end marker in the data.
vector<int> read_ints(istream& is) {
vector<int> v;
int i;
while (is >> i) v.push_back(i);
return v;
}- 1The function creates an empty vector named v to hold the results.
- 2It declares an int named i to receive each value read from the stream.
- 3The expression is >> i attempts to read one integer. It returns a reference to is, and testing that stream yields true only if the stream is ready for another operation.
- 4When the read succeeds, the value is stored in v by
push_backand the loop goes around again. - 5When the next item is not an integer, the stream is no longer ready, the condition is false, and the loop exits.
- 6The function returns v, which holds only the values read before the first failure.
- vector<int> v holds the successful reads
- int i is the variable the extraction operator writes into
- while (is >> i) is the only condition in the loop
Each pass through the loop asks one question: did the stream accept another integer? The answer comes from the stream's state, not from a counter or a sentinel value. This is why the loop is so short, and also why it is easy to misjudge. The condition is not checking whether the data is valid in any broader sense. It is checking whether the most recent read succeeded.
What the Stream State Records
The I/O state of a stream holds the information needed to read or write. It includes formatting information, the error state (for example, whether end of input has been reached), and the kind of buffering in use. For error handling, the part that matters is the error state, because it is what the readiness test reports on. When the state says the stream is ready, the next operation is attempted. When it says the stream is not ready, the operation is not attempted and the loop condition evaluates to false.
The same stream can be in different conditions at different points in a program. It starts ready, it can reach the end of input, it can hit an error, and an error that was not serious can be cleared so that the stream is ready again. The diagram below shows these conditions as states and the events that move the stream between them.
Notice that end of input is drawn as a terminal condition. Reaching it is the usual reason a read loop finishes. An error, by contrast, leads to a decision. If the error is serious, reading stops. If it is not serious, the state can be cleared and the stream returns to ready. The source describes the state as something a user can set to record that an error has occurred, and clear if the error was not serious. This gives the program a real choice: a failed read can end the whole operation, or it can be recorded, handled, and followed by further reading.
Tracing the Read of 1 2 x 3
Now trace the loop on the input 1 2 x 3, one iteration at a time. Track the value read, whether the condition is true, and what the vector holds afterward. The trace shows exactly where the result diverges from what a reader might expect, which is the central skill this article is building.
Finding the stopping point in 1 2 x 3
Given the input 1 2 x 3 and the read_ints loop, determine what the vector contains and identify the iteration where the loop stops.
- 1Iteration 1
The extraction reads 1. The stream is ready, so the condition is true. The value 1 is stored in the vector.
- 2Iteration 2
The extraction reads 2. The stream is still ready, so the condition is true. The value 2 is stored, and the vector now holds 1 and 2.
- 3Iteration 3
The extraction tries to read x as an int and fails. The stream is no longer ready, so the condition is false and the loop exits. Nothing is stored.
- 4After the loop
The function returns the vector. The 3 after x was never read, because the loop does not skip bad items and does not resume reading.
The vector contains 1 and 2. The loop stopped at the third iteration, where the read of x failed.
Recording and Clearing a Non-Serious Error
The read_ints function as written stops at the first bad token, because it has no step for recovery. Sometimes a program should keep reading after a bad item, for example to skip a stray word and collect the integers that follow it. The source states that a user can set the stream's state to record that an error has occurred, and can clear that state if the error was not serious. Once the state is cleared, the stream is ready for further operations.
The decision to clear depends on whether the error is serious. The source leaves that judgment to the program. A failed read that makes the rest of the operation meaningless should stop reading. A failed read that affects only one item can be recorded, handled, and followed by further reading.
Where Buffered Text Fits In
Between the input source and the variable being read, characters are held in a buffer. The source identifies buffering as one part of a stream's I/O state, alongside formatting information and the error state. Leftover text that is not consumed by one read remains available to the next read. This is why a bad token that is not removed from the input can cause the next operation to fail in the same way. The source gives this topic less detail than the state and loop behavior, so the key practical point is that a failed read does not automatically clear the text that caused it.
Mistakes in Reading Loop Results
Common mistake Assuming the loop reads every value in the input
What it looks likeWith 1 2 x 3, expecting the vector to hold 1, 2, and 3
Why it failsThe loop stops at the first failed read. The 3 is never reached because the condition is false at x.
Do this insteadTrace the loop one iteration at a time and find the first read that fails.
Common mistake Expecting the loop to skip a bad item and continue
What it looks likeBelieving that x is discarded automatically and reading resumes at 3
Why it failsThe function has no step that clears the state or discards input, so the stream is not ready after the failure.
Do this insteadAdd explicit recovery steps if reading must continue: clear the state and discard the bad input.
Common mistake Treating the returned vector as the full input
What it looks likeReading a vector of two values and assuming the input had only two values
Why it failsThe vector is only a record of the successful reads. The input may have contained many more values after the stopping point.
Do this insteadWhen the result is short, look at the input at the position where the stream stopped being ready.
Common mistake Ignoring that the loop condition tests readiness, not data validity
Locating the Iteration Where Results Diverge
When a read loop returns something you did not expect, the debugging task is to find the iteration where the stream stopped being ready. Ask what the input held at that point, not what the loop was supposed to produce. The first failed read is where the result diverged from your expectation, and everything the loop would have read after that point is missing from the result.
The input is 4 5 6.5 7 8. The read_ints loop from this article runs on it. Before checking, predict the contents of the vector, then identify the iteration where the first divergence from the expected full list of integers occurs.
Hints
- Follow the loop one read at a time and note the value of the condition after each read.
- The value 6.5 is not an int. Consider what the extraction operator does with the .5 part, and whether the stream remains ready afterward.
- The vector is only a record of successful reads, so everything after the first failure is absent from it.
Modify read_ints so that a non-integer token is cleared and discarded, and reading continues until the end of input. State in one sentence what the modified loop does on a bad token, and name which step must happen before the next read.
Hints
- Consider the order of the steps from the recovery diagram: clear, discard, then read again.
- The loop must still stop cleanly at the end of input.
Key Takeaways
- An iostream has a state that can be examined to tell whether the last operation succeeded, and a read loop's condition tests exactly that readiness.
- A loop such as while (is >> i) stops at the first value that is not the expected type, typically end of input, and values after that point are not read.
- A bad token in the middle of the data stops the loop in the same way as the end of input, so the vector holds only the reads that succeeded.
- A program can record an error in the stream's state and clear it if the error is not serious, but the bad input must also be discarded before reading can usefully continue.
- When a result differs from expectation, locate the first failed read and examine the input at that position, because everything after it is missing from the result.