Ashkan Arabi
Ashkan Arabi

Reputation: 119

noskipws on char extraction from StringStream causes it to not work with subsequent string extractions

Let me make this simple. Why does the program below write "Thi" instead of "Thisisatest."? I expect it to write the first two characters in the first loop, and then write the rest of the test string without whitespace.

#include <sstream>
#include <iostream>
using namespace std;

int main() {
  string test = "This is just a test.";
  stringstream ss(test);
  
  char c;
  int i = 0;
  while (ss >> noskipws >> c) { 
    int ascii = (int)c;
    if (i > 2) { // break after writing 2 characters
      break;
    }
    i++;
    cout << c; 
  }

  // continue using the same sstring, 
  // but with a string instead of a char
  // notice that there is no noskipws here
  string s;
  while(ss >> s) {
    cout << s; 
  }

  // program exits with only "Thi" printed
  return 0;
}

Interestingly enough, removing >> noskipws fixes the problem. Why though?

Upvotes: 2

Views: 72

Answers (2)

tbxfreeware
tbxfreeware

Reputation: 2206

ss >> s means read in string s, and stop when you encounter whitespace.

I added a pair of extra output statements to your program, one at the end of each loop. After the first loop ends, the input stream is positioned to input a space as its next character.

When the program tries to input string s, it encounters whitespace immediately, and because you configured the stream to not skip leading whitespace, it stops, failing to input string s. That failure causes the stream to be put into a failed state.

#include <sstream>
#include <string>
#include <iostream>
using namespace std;

int main() {
    string test = "This is just a test.";
    stringstream ss(test);

    char c;
    int i = 0;
    while (ss >> noskipws >> c) {
        int ascii = (int)c;
        if (i > 2) { // break after writing 2 characters
            break;
        }
        i++;
        cout << c;
    }
    std::cout << boolalpha 
        << "\nAfter first loop: " 
        << "\n  ss.fail() : " << ss.fail() 
        << "\n  c         : \'" << c << '\''
        << "\n";

    // continue using the same sstring, 
    // but with a string instead of a char
    // notice that there is no noskipws here
    string s{ "value before ss >> s" };
    while (ss >> s) {
        cout << s;
    }
    std::cout << "\nAfter second loop: "
        << "\n  ss.fail() : " << ss.fail()
        << "\n  s         : \"" << s << '\"'
        << '\n';

    // program exits with only "Thi" printed
    return 0;
}

Here is the output:

Thi
After first loop:
  ss.fail() : false
  c         : 's'

After second loop:
  ss.fail() : true
  s         : ""

Upvotes: 1

3CxEZiVlQ
3CxEZiVlQ

Reputation: 39084

The reduced code (the first loop reads 4 characters and exits before printing the 4th one):

#include <sstream>
#include <iostream>
using namespace std;

int main() {
  string test = " is just a test.";
  stringstream ss(test);
  ss >> noskipws;
  
  string s;
  while(ss >> s) {
    cout << s; 
  }
  cout << ss.fail();
}
// Output: 1.

The extracting to a string has a contract:

Characters are extracted and appended until any of the following occurs:

  • end-of-file occurs on the input sequence;
  • isspace(c, is.getloc()) is true for the next available input character c.

If no characters are extracted then std::ios::failbit is set on a stream.

ss >> s job is str.erase() then str.append(1, c) for each read c above. isspace(c, is.getloc()) is true on the first character - nothing is extracted, s is empty, the stream ss is set to the fail state.

Upvotes: 0

Related Questions