6

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?

  • 1
    Since you only use `word` inside the loop, why not define it in the scope of the loop (i.e. inside the loop)? A good rule of thumb is to define variables as close to their point of use as possible (which means you should define `number` just before you read into it as well). – Some programmer dude Dec 12 '19 at 13:57
  • Possible duplicate: [C++ - repeatedly using istringstream](https://stackoverflow.com/questions/2767298/c-repeatedly-using-istringstream) (calling `word.str()` doesn't reset the iostatus bits) – Adrian Mole Dec 12 '19 at 14:04
  • I believe it just calls `str(s)` on the buffer, not sure offhand all the effects that has https://en.cppreference.com/w/cpp/io/basic_stringbuf/str . Probably the error flags are completely separate from the buffer, but I am not sure. – Fire Lancer Dec 12 '19 at 14:04
  • 1
    @Adrian-ReinstateMonica I think there is a difference between "you need to call `clear()`" and "why must I call clear?". OP has the `clear()`/working solution. – Fire Lancer Dec 12 '19 at 14:05
  • @FireLancer Which is why I said *possible* duplicate! – Adrian Mole Dec 12 '19 at 14:06

2 Answers2

2

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).

Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • Personally I would rather use `istringstream word(in);` instead of the separate call to `str`. Also probably use `if (!(word >> number))` instead of separate extraction and `fail` check. ;) – Some programmer dude Dec 12 '19 at 14:16
  • @Someprogrammerdude I shall never use your name in vain. Fixed! :-) – Paul Evans Dec 12 '19 at 14:54
2

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.

Fire Lancer
  • 29,364
  • 31
  • 116
  • 182
  • So, unlike an fstream, a stringstream essentially looks ahead for the eof and sets `eofbit` in the last valid read? – Fahim Faisal Dec 12 '19 at 15:02
  • @FahimFaisal Not sure what your distinction is here, fstream sets eof the same. as it has to touch the end of stream/file to see that the number is finished, otherwise if it read literally only "10", how does it know it is not actually "100"? – Fire Lancer Dec 12 '19 at 15:06
  • ```ifstream infile { "myfile.txt" }; string word; while (infile >> word) { cout << word << endl; if(infile.eof()) cout << "eof" << endl; cout << word << endl; }``` – Fahim Faisal Dec 12 '19 at 15:48
  • If "myfile.txt" consists of a few space separated words, the above code won't print "eof" between printing the last word twice, suggesting that file streams set `eofbit` only on the eof read, rather than the last valid read. – Fahim Faisal Dec 12 '19 at 15:51
  • Same for a string stream though, if I have `std::stringstream ss("10 40\nmore stuff");` then a single `ss >> my_int` *does not set* eof, I have to do multiple reads to get eof, see the "10 20" example output. `10 20 10\nstream state:\n`. – Fire Lancer Dec 12 '19 at 15:54
  • Yes, I was talking about the last read. Say "myfile.txt" is "a\nb\nc\nd". if `eofbit` is set during the last read, the code should print "a\na\nb\nb\nc\nc\nd\neof\nd\n" since d was the last string. but it prints "a\na\nb\nb\nc\nc\nd\nd\n", suggesting `eofbit` was set on the read attempt after the last string was read. – Fahim Faisal Dec 12 '19 at 16:00
  • 1
    You sure you don;t have an extra newline on the file? e.g. https://rextester.com/TVOC70941 (I don't know of an easy to use online C++ with multiple files, but gives me the same result with ifstream if the files are *exactly* as the string). – Fire Lancer Dec 12 '19 at 16:33
  • 1
    Oh, turns out I did have a trailing newline. Without it, it acts the same way as stringstreams, as it should. Thank you so much! – Fahim Faisal Dec 12 '19 at 16:40