Skip to content

27 Beginner The Hundred

The Last Line, Twice

three readings go in, four come out

#include <iostream>
#include <sstream>

int main() {
    std::istringstream readings("10\n20\n30\n");   // a tiny data file: three readings
    int value = 0, total = 0;

    while (!readings.eof()) {
        readings >> value;
        total += value;
        std::cout << "read " << value << '\n';
    }

    std::cout << "total = " << total << '\n';
}

Run it. Three numbers, one loop — what does it print?

Answer
read 10
read 20
read 30
read 30
total = 90

The last reading is counted twice, and the total comes out half again too big.

Why

eof() is not a look-ahead — it reports that some previous read already ran off the end, and nothing more. Extracting 30 stops at the newline behind it and leaves that newline in the stream, so eofbit is still clear and the loop takes a fourth turn. That fourth >> skips the trailing whitespace, runs out of input, and gives up before it starts parsing: failbit goes up, but value is never touched and still holds 30 — C++11 made a failed parse store 0, yet here the parse never begins. Delete the trailing newline and the demo prints three lines, which is how this bug hides until someone edits the data file. Worse, failbit is not eofbit: feed the same loop "10 20 xx" and it never terminates, since a stream stuck in fail state never reaches end of file either. -Wall -Wextra say nothing — the loop is perfectly legal, just wrong.

The fix

Loop on the read itself: a stream converts to false exactly when its last read failed, so test and read can never disagree — likewise while (std::getline(in, line)) for lines.

while (readings >> value) {   // prints 10, 20, 30 — total = 60
    total += value;
    std::cout << "read " << value << '\n';
}

Takeaway: test whether the read succeeded, never whether the stream already failed.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry