Formatted Input and Output
A string stream behaves like an ordinary input and output stream, but instead of reading from a keyboard or writing to a file, it reads from and writes to a string held in memory.
Turning Values Into Text and Back
Many programs need to move between values and text. A user types a number into a window, and the program needs an integer. A program holds a double and needs to show it in a label. The standard library handles both directions with string streams. A string stream behaves like an ordinary input and output stream, but instead of reading from a keyboard or writing to a file, it reads from and writes to a string held in memory.
This article follows a code-first path. Before reading the explanation of the mechanism, try to predict what each step will produce. The places where your prediction turns out wrong are the most useful clues for understanding how the stream works.
Stringstream and Its Two Specialized Forms
A stringstream can be used for both reading and writing. The same object can accept values through the output operator and hand them back through the input operator. Two more specialized forms exist, each for one direction. An ostringstream is used to build text, and an istringstream is used to parse text that already exists. Both are specializations of the general stringstream, as the diagram below shows.
The two specialized forms have common practical uses. The text collected in an ostringstream can be retrieved with str(), which is typically how values are formatted before the finished string is handed to a graphical user interface. In the other direction, a string received from a graphical user interface can be placed into an istringstream, which lets it be read with formatted input operations.
line holds "Total: 7"; a holds 12; b holds 34- ostringstream: output operator builds the text,
str()returns it - istringstream: the existing text is parsed by the input operator
Tracing a Value Through a Conversion
String streams make it possible to write one general operation that converts any type with a string representation into another type that also has one. The idea is to let the stream do the work. The value is written into the stream as text, then read back out as the target type. If both steps succeed and nothing remains in the stream, the conversion is considered correct.
The source defines this operation as a function template with two type parameters. The first, Target, is the type we want to produce. The second, Source, is the type of the argument. Both default to string. The function creates a local stringstream named interpreter and a result variable of type Target. It then performs three checks in sequence, and it returns the result only if none of them fails. The following sketch follows that description. The leftover check is written out here as one way to express the final check.
- Target and Source default to std::string, so the template arguments can often be left out
- Step 1: the argument is written into interpreter as text
- Step 2: the text is read back as a Target value
- Step 3: a leftover check confirms that nothing remains in the stream
- Any failed step throws instead of returning a partial or guessed value
The diagram follows the same order as the code. The value enters the stream as text, a read produces the target value, and a final check confirms that the stream holds nothing else. A failure at any of these three points leads to the same outcome, a runtime error reporting that the conversion failed. The source is explicit that the function never returns a partial or guessed result on failure.
s1, s2, and s3 each hold the text "1.2"- The source shows four spellings of this call on the value 1.2; these three illustrate the same idea
- An explicit template argument is needed only when the compiler cannot deduce it or it has no default
Finding Where a Conversion Fails
What do you think happens?
What does the call to<int>("42abc") do?
Reveal answer
It throws a runtime error with the message to<>() failed.
The write step succeeds, because the text is placed into the stream. The read step extracts 42 as an int, so that step also succeeds. But the characters abc are still in the stream, so the leftover check finds input and throws. The function never returns a partial result such as 42.
Tracing a successful conversion of the text 42
Trace to<int>("42") through the three checks and determine the returned value.
- 1Write step
The text 42 is written into interpreter. The write succeeds.
- 2Read step
The input operator reads the text as an int, producing 42 in result. The read succeeds.
- 3Leftover check
Nothing remains in the stream after 42, so the leftover read fails as expected and no error is thrown.
- 4Return
No check failed, so the function returns result.
The call returns 42.
Round Trip Through a stringstream
A value written into a stringstream can be read back out of it, which makes a complete round trip. The text does not disappear when it is written. It stays in the stream's buffer until a read takes it out and interprets it as a new type.
- The output operator writes 42, a space, and 3.5 as text in the stream
- The input operator reads the text back, first as an int and then as a double
- The reads happen in the same order as the text was written
Chaining Input and Output Operations
The source gives two practical guidelines for everyday use. The first is to chain the output and input operators to keep code terse, so that several values can be written or read in a single statement. Chaining works because each operation hands back the stream it was given, so the next operation in the chain acts on that same stream.
x=3 y=4- cout << a << b evaluates left to right, each operator passing the stream on
- cin >> a >> b reads two values in one statement
Choosing cout or cerr Deliberately
The second guideline is to use cout for normal output and cerr for errors. Choosing the stream deliberately makes the purpose of each line of output clear to a reader of the code.
| Stream | Intended use | Example message |
|---|---|---|
| cout | Normal output | Total: 7 |
| cerr | Error output | Error: could not parse the input |
Matching the stream to the purpose of the message
- Normal results go to cout
- Error messages go to cerr
Mistakes Beginners Make With Conversions
Common mistake Expecting a failed conversion to return a partial or guessed value
What it looks likeAssuming to<int>("42abc") returns 42Why it failsThe function does not return partial results. A failed check throws a runtime error.
Do this insteadHandle the exception, and check which step failed when debugging.
Common mistake Forgetting the leftover check when reading multiple words into a single value
What it looks likeConverting "hello world" into a std::string with the input operator, which reads only one word
Why it failsThe input operator stops at whitespace, so the rest of the text remains in the stream and the leftover check fails.
Do this insteadRead the whole text if you need all of it, or make sure the input contains exactly one value.
Common mistake Writing error messages to cout, which mixes them with normal results
Common mistake Writing a long chain of operators without checking which value each operator reads or writes
Practice: Predict the Stream Behavior
Using the conversion template from this article, predict what happens for to<double>("3.5 kg") and for to<double>("3.5"). For each call, name the check that succeeds or fails and state whether a value or a runtime error results.
Hints
- Think about what the read step can extract from 3.5 kg, and what remains in the stream afterward
- Ask whether the leftover check finds any input
Key Takeaways
- A stringstream reads and writes text in memory, while istringstream parses existing text and ostringstream builds new text.
- The conversion template writes a value into a stream, reads the target type, and checks for leftover input; a failure at any of these steps throws a runtime error instead of returning a partial result.
- A value written into a stringstream stays in its buffer until a read takes it out, which allows a round trip from value to text and back to value.
- Use cout for normal output and cerr for errors, and chain operators to read or write several values in one statement.