Search code examples
c++istream

std::istream::unget() setting fail and bad bits if first read failed but not if second or further reads failed


I have the following code:

#include <algorithm>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

std::vector<int> read_ints(std::istream& is)
{
    std::vector<int> res;

    std::cout << "Please input a list of integers ('quit' to finish):" << std::endl;
    for (int i; is >> i; )
    {
        res.push_back(i);
    }
    if (is.eof())  // fine: end of file
    {
        std::cout << "EOF reached" << std::endl;
        return res;
    }
    std::cout << (is.bad() ? "1) BAD\n" : "");
    std::cout << (is.fail() ? "1) FAIL\n" : "");
    if (is.fail())  // we failed to read an int: was it the 'quit' string?
    {
        is.clear();  // reset the state to good
        std::cout << (is.bad() ? "2) BAD\n" : "");
        std::cout << (is.fail() ? "2) FAIL\n" : "");
        is.unget();  // put the non-digit back into the stream
        std::cout << (is.bad() ? "3) BAD\n" : "");
        std::cout << (is.fail() ? "3) FAIL\n" : "");
        std::string s;
        if (is >> s && s == "quit")
        {
            std::cout << "Exiting correctly" << std::endl;
            return res;
        }
        std::cout << "Exiting with an error. User typed: " << s << std::endl;
        is.setstate(std::ios_base::failbit);  // add fail() to stream's state
    }
    return res;
}

int main()
{
    // Read ints
    auto v = read_ints(std::cin);
    bool first = true;
    std::for_each(v.begin(), v.end(),
        [&first](int n) { std::cout << (first ? "" : " ") << n; first = false; });
    std::cout << std::endl;
}

If I input one or more numbers followed by a word (e.g. 1hola, 1 2 3 quit), the is.unget() doesn't set neither the fail bit nor the bad bit, and I get the expected output messages (e.g. Exiting with an error. User typed: hola, Exiting correctly).

But if I just input a word (e.g. hola or quit), the is.unget() sets fail bit and bad bit and I cannot recover the last input, getting the message Exiting with an error. User typed:.

Why can't we put whatever we read from the stream back into it for the latter case?

https://godbolt.org/z/dMoodE


Solution

  • Your mistake seems to be thinking that when reading an integer and a non-digit is found that character is consumed. It isn't. Just remove the call to std::unget.