Concepts / Formatted Input and Output

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.

  • Programming

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.

stringstreamreads and writes textistringstreamparses existing textostringstreambuilds textspecializesspecializes
Which stream type is the general form, and which two forms specialize it for one direction each?

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.

cpp
Output
line holds "Total: 7"; a holds 12; b holds 34
Notes on the code
  • 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.

cpp
Notes on the code
  • 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
Source valuethe argument, such as 1.2Write stepvalue written as textRead steptext read as TargetLeftover checkstream must be emptyReturn resultconversion succeededThrow runtime errorto<>() failedsucceedsfailssucceedsfailsstream emptyinput left over
From the input value to the stored result, which check flags a failed conversion?

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.

cpp
Output
s1, s2, and s3 each hold the text "1.2"
Notes on the code
  • 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?

  • It returns 42 and ignores the rest of the text

  • It returns 0 because the text is not a clean number

  • It throws a runtime error with the message to<>() failed

Reveal answer
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.

Worked example

Tracing a successful conversion of the text 42

Trace to<int>("42") through the three checks and determine the returned value.

  1. 1
    Write step

    The text 42 is written into interpreter. The write succeeds.

  2. 2
    Read step

    The input operator reads the text as an int, producing 42 in result. The read succeeds.

  3. 3
    Leftover check

    Nothing remains in the stream after 42, so the leftover read fails as expected and no error is thrown.

  4. 4
    Return

    No check failed, so the function returns result.

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.

cpp
Notes on the code
  • 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
42int value writtenstringstream buffertext "42 3.5"nint, read backddouble, read backoutput operatorinput operatorinput operator
Where does the text go after it is written, and how does it come back out as a value?

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.

cpp
Output
x=3 y=4
Notes on the code
  • 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.

StreamIntended useExample message
coutNormal outputTotal: 7
cerrError outputError: could not parse the input

Matching the stream to the purpose of the message

cpp
Notes on the code
  • 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 like
    Assuming to<int>("42abc") returns 42
    Why it fails

    The function does not return partial results. A failed check throws a runtime error.

    Do this instead

    Handle 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 like

    Converting "hello world" into a std::string with the input operator, which reads only one word

    Why it fails

    The input operator stops at whitespace, so the rest of the text remains in the stream and the leftover check fails.

    Do this instead

    Read 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

MEDIUM

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.

Related concepts

More concepts starting with F