Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ - Does resetting stringstream not reset the get position or clear flags?

I was extracting values from a stringstream in a loop, resetting the stringstream at the top of the loop every time the loop executed. However, the stringstream's >> operator fails at the second iteration every time. This distilled version of the code reproduces the problem I'm having:

istringstream word;
string in;
int number;

while(cin >> in) {

   word.str(in);

   //Uncommenting either one of the following lines seems to solve the issue:
   //word.clear();
   //word.seekg(0);

   word >> number;

   if(word.fail()) {
       cerr << "Failed to read int" << endl;
       return 1;
   }

   cout << in << ' ' << number << endl;
}

As it stands, it always fails on second loop iteration. However, uncommenting any one of the two commented lines of code solves the issue. What I don't get is, since I've reset the stringstream with word.str(in), why does it still fail? And why does resetting the get position solve the problem?

Am I missing something about the workings of a stringstream? Does it set the eofbit flag on the last valid read rather than on the read that fails due to EOF? And if so, why does seekg(0) seem to clear that flag, while resetting the stringstream doesn't?

like image 832
Fahim Faisal Avatar asked Dec 12 '19 13:12

Fahim Faisal


People also ask

How do you clear a Stringstream?

The clear() member function is inherited from ios and is used to clear the error state of the stream, e.g. if a file stream has the error state set to eofbit (end-of-file), then calling clear() will set the error state back to goodbit (no error). For clearing the contents of a stringstream , using: m. str("");

What is string stream?

A stringstream associates a string object with a stream allowing you to read from the string as if it were a stream (like cin). To use stringstream, we need to include sstream header file. The stringstream class is extremely useful in parsing input.


2 Answers

If you look at the state of the stream, this should be a bit clearer.

int main()
{
    std::vector<std::string> words = { "10", "55", "65" };
    std::istringstream word;
    for (const auto &in : words)
    {
        word.str(in);
        std::cout << "stream state:"
            << (word.rdstate() & std::ios::badbit ? " bad" : "")
            << (word.rdstate() & std::ios::failbit ? " fail" : "")
            << (word.rdstate() & std::ios::eofbit ? " eof" : "")
            << std::endl;
        int number;
        word >> number;

        if (word.fail()) {
            std::cerr << "Failed to read int" << std::endl;
            return 1;
        }

        std::cout << in << ' ' << number << std::endl;
        std::cout << "stream state:"
            << (word.rdstate() & std::ios::badbit ? " bad" : "")
            << (word.rdstate() & std::ios::failbit ? " fail" : "")
            << (word.rdstate() & std::ios::eofbit ? " eof" : "")
            << std::endl;
    }
}

Which will result in:

stream state:
10 10
stream state: eof
stream state: eof
Failed to read int

So initially none of the flags are set, but reading the number reaches the end of the stream, setting eofbit. std::istringstream::str is defined as if to call rdbuf()->str(new_str). That says nothing about clearing flags.

Calling clear() will of course clear the eofbit, but so does calling seekg in C++11 or newer. " Before doing anything else, seekg clears eofbit. (since C++11)".

Note that if you had say "10 20", that it will just discard the " 20", and not detect an error.

stream state:
10 20 10
stream state:
stream state:
55 55
stream state: eof
stream state: eof
Failed to read int

So you might actually want to check that eof flag to see if you read the entire stream.

As noted by others, of course constructing a new stream object each loop also means no concerns over previous flags/states.

like image 54
Fire Lancer Avatar answered Oct 23 '22 18:10

Fire Lancer


As @Someprogrammerdude suggests: simply move your istringstream inside your while loop (which you can change to a for loop to keep in in the loop as well):

for (string in; cin >> in;)
{
    istringstream word(in);
    int number;
    if (!(word >> number))
    {
        cerr << "Failed to read int" << endl;
        return 1;
    }
    cout << in << ' ' << number << endl;
}

that way it's re-created each loop.

While you're at it, move number in there too (unless you use it outside the loop, of course).

like image 43
Paul Evans Avatar answered Oct 23 '22 18:10

Paul Evans