Search code examples
c++ioeof

What does rdstate() return value mean?


istream& Read(istream &is)
{
    std::string buf;
    while (is >> buf)       
    {   
        cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
        cout << is.rdstate() << endl;
        cout << buf << endl;
    }
    cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
    cout << is.rdstate() << endl;
    is.clear();
    cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
    cout << is.rdstate() << endl;
    return is;
}

If I input normal characters like "test",the output is 1 2 4 0.
Then I type CTRL+Z (windows),the output is 1 2 4 3 1 2 4 0.

Question : 1. what does rdstate() return value means? (Why does it output 3,not 2? not 1?)

  1. Why did not is.eofbitand is.failbit change after I typed CTRL+Z ? (As C++ Primer 5th Editon says,Reaching end-of-file sets both eofbit and failbit )

Solution

  • The member std::ios::rdstate() simply returns a combination of the state flags std::ios_base::badbit, std::ios_base::eofbit, and std::ios_base::failbit. Under which conditions which bits gets set isn't entirely consistent but the intent is the following:

    1. std::ios_base::badbit gets set when the stream is in genuinely dysfunctional state and you [probably] won't get anything out of it. For example, this flag is set if there is no stream buffer or when any of the operations on the stream has thrown an exception.
    2. std::ios_base::failbit gets set when an input operation failed, e.g., because a formatted input operation got an unexpected characters. It may be possible to recover from this error by clearing it, ignoring a few characters, and trying again.
    3. std::ios_base::eofbit gets set when [the current] EOF is reached, i.e., when there could be no more characters extracted for now.

    Now, in your case you entered a string and reading it was successful, i.e., there are no flags set. Note that reading stopped with the newline character, i.e., you really entered "test\n" and the stream extracted these five characters. When you then ended the stream, the stream reached EOF while trying to read a string, i.e., it set std::ios_base::eofbit and the input failed also setting std::ios_base::failbit.

    If you want to see only std::ios_base::eofbit set, you can do so by using a stream which ends with a word right at the end of the stream without any following space character. An easy way to get such a stream is to use an std::istringstream and read from that:

    std::istringstream in("test");
    Read(in);
    

    Another easy set up is to see std::ios_base::badbit set: you'd just create a stream without a stream buffer, e.g.:

    std::istream in(0);
    Read(in);
    

    Note that the stream will initially have std::ios_base::badbit set and also get std::ios_base::failbit set upon an attempt to read a character. After clear()ing the std::ios_base::badbit will still be set, though.

    To get std::ios_base::failbit set without also having std::ios_base::eofbit set you'll need to prevent it from seeing a non-whitespace character: the input operator for std::string by default start off skipping whitespace and then reads until it either reaches whitespace or EOF and it is successful if it could read at least one non-whitespace character. An approach to do that is to turn automatic skipping of whitespace off, e.g.:

    std::istringstream in("test test");
    Read(in >> std::noskipws);
    

    BTW, note that there is no guarantee for the values of std::ios_base::eofbit, std::ios_base::failbit, or std::ios_base::badbit other than they can be used as bitmasks in some form.