C++ / CONSOLE AND FILE INPUT-OUTPUT
String streams for parsing in memory
Parse typed values and delimited fields out of a std::string with istringstream, and assemble strings with ostringstream.
What you will learn
- Pull typed values out of a std::string using std::istringstream and >>
- Split delimited text with getline(in, field, ',') and keep empty fields
- Reject trailing garbage by checking for leftover characters after a parse
- Recycle a stringstream correctly with clear() plus str(newText)
Understanding String streams for parsing in memory
std::istringstream and std::ostringstream are ordinary streams whose character source or sink is a std::string held in memory instead of a console or a file. Every rule you already know about >> still applies: leading whitespace is skipped, extraction stops at the first character that cannot belong to the requested type, and failure shows up in the stream's state bits rather than as an exception. So you can obtain text from anywhere, a file line or a hard-coded literal, and then parse it with the same typed reads, on a private copy you are free to re-read.
The useful mental model is a read cursor over a fixed run of characters, plus state bits that live separately from that cursor. failbit means the characters sitting at the cursor did not match the type you asked for, and the cursor stays exactly where it was. eofbit means only that the cursor reached the end, which is not by itself an error. Once failbit is set, every later extraction returns immediately without touching your variables, which is why a single bad field appears to poison the rest of a parse until you call clear().
The type you pick fixes the direction: istringstream reads, ostringstream collects characters you fetch back with str(), and stringstream does both over one buffer. Inside a loop over file lines, the cheapest correct pattern is to construct a fresh istringstream in the loop body, because a new object starts with clean state and its cursor at position zero. Recycling one object takes two independent operations, clear() for the state bits and str(text) for the contents, and str() rewinds the cursor without ever clearing failbit.
<iostream>
<sstream>
<string>
int main() {
std::string line = "temperature 21.5 celsius";
std::istringstream in(line);
std::string label, unit;
double value = 0.0;
in >> label >> value >> unit;
if (in) {
std::cout << label << " = " << value << " " << unit << "\n";
} else {
std::cout << "could not parse: " << line << "\n";
}
std::istringstream bad("temperature warm celsius");
std::string label2;
double value2 = -1.0;
bad >> label2 >> value2;
std::cout << std::boolalpha;
std::cout << "second parse succeeded: " << static_cast<bool>(bad) << "\n";
std::cout << "value2 after failure: " << value2 << "\n";
bad.clear(); // drop failbit; the cursor has not moved
std::string word;
bad >> word;
std::cout << "resumed at: " << word << "\n";
}A string stream is a cursor plus state bits over a string in memory, so parsing means extracting, then checking state, and resetting state separately from contents.
Worked examples
Splitting one line on commas
Tokenizing a comma-separated row with getline and a delimiter character, including an empty field.
<iostream>
<sstream>
<string>
<vector>
int main() {
std::string row = "ada,lovelace,,1815";
std::istringstream in(row);
std::vector<std::string> fields;
std::string field;
while (std::getline(in, field, ',')) {
fields.push_back(field);
}
std::cout << "count " << fields.size() << "\n";
int index = 0;
for (const std::string& f : fields) {
std::cout << index << ": [" << f << "]\n";
++index;
}
}Example explained
Line 1getline(in, field, ',') copies characters up to the next comma and then discards that comma, leaving the cursor on the first character of the next field.
Line 2Because getline never skips whitespace, the gap between the two commas is reported as an empty string, while >> would have jumped straight to 1815.
Line 3The loop ends when a call extracts zero characters, which happens on the call made after the cursor has already reached the end of the buffer.
ostringstream as a string builder
Accumulating mixed text and numbers in memory and retrieving the result with str().
<iostream>
<sstream>
<string>
std::string label(const std::string& name, int count) {
std::ostringstream out;
out << name << " x" << count;
if (count != 1) {
out << " (plural)";
}
return out.str();
}
int main() {
std::cout << label("bolt", 1) << "\n";
std::cout << label("bolt", 7) << "\n";
std::ostringstream out;
out << 255;
std::string first = out.str();
out.str(""); // new contents, put cursor back to zero
out << 16;
std::cout << first << " then " << out.str() << "\n";
}Example explained
Line 1out << name << " x" << count writes text and an int into one buffer, so no manual number-to-string conversion is needed.
Line 2out.str() returns a copy of everything written so far; the stream keeps its own contents and can be written to again.
Line 3out.str("") swaps in an empty buffer and moves the put cursor back to zero, which is how one ostringstream is recycled, and it leaves error flags untouched.
Strict parse that rejects leftovers
Turning a whole string into an int only when nothing but whitespace remains after the number.
<iostream>
<sstream>
<string>
bool parseInt(const std::string& text, int& out) {
std::istringstream in(text);
int value;
if (!(in >> value)) {
return false; // no number at the cursor
}
char extra;
if (in >> extra) {
return false; // something other than blanks is left
}
out = value;
return true;
}
int main() {
const std::string samples[] = {"42", " 42 ", "42abc", "42 7", "abc"};
for (const std::string& s : samples) {
int n = 0;
std::cout << "[" << s << "] -> ";
if (parseInt(s, n)) {
std::cout << n << "\n";
} else {
std::cout << "rejected\n";
}
}
}Example explained
Line 1if (!(in >> value)) rejects text with no leading number, because failbit is set and the cursor has consumed nothing.
Line 2The read into extra skips whitespace first, so " 42 " is accepted while "42abc" fails on the a.
Line 3"42 7" shows why checking the stream state alone is not enough: the first extraction genuinely succeeded, and only the leftover 7 reveals the problem.
Line 4The istringstream copies text into its own buffer, so the caller's string is never consumed or modified.
Important notes
str() hands back a copy of the buffer, so stream.str().c_str() points into a temporary that dies at the end of the statement; store the std::string first.
Since C++11 a failed numeric extraction writes 0 into your variable instead of leaving it alone, so an old value cannot serve as a fallback.
Common mistakes
Calling ss.str(line) on a reused stringstream without ss.clear(): failbit from the previous line survives, so every remaining row parses as zeros and empty strings with no visible error.
Treating a successful >> as validation of the whole string: "42abc" yields 42 with the stream still good, so the trailing junk is silently accepted.
Splitting with getline and a delimiter and expecting a trailing empty field: "a,b," produces two fields, because the call after the last comma extracts nothing and fails.
Try it yourself
Change, predict, then run
Parse "12:34:56" with an istringstream by reading three ints and extracting each ':' into a char, then print the total seconds (45296). Change the input to "12:34:xx" and make the code print "bad time" instead of a wrong number.
Open the C++ workspaceCheck your understanding
A loop reads lines from a file and, for each line, calls ss.str(line) then ss >> id >> name on the same std::stringstream declared outside the loop. Line 3 has a malformed id. What happens to lines 4 onward?
- They parse correctly, because str() replaces the contents and resets the stream
- They all fail, because failbit stays set until clear() is called
- They re-read line 3, because str() only rewinds the cursor without replacing the text
- The stream throws an exception when line 4 is extracted
Show answer
str(line) does two of the three things people expect: it swaps in the new characters and puts the read cursor back at position zero. It does not touch the state bits, and while failbit is set every >> returns immediately without writing to its target, so lines 4 onward yield zeros and empty strings. Option 0 is tempting precisely because the rewind is real, but only clear() resets state; and streams do not throw by default, since the exception mask starts empty.