String Streams

A detailed guide to C++ String Streams using std::stringstream. Covers basic use cases, stream position seeking, and open modes.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

String streams allow us to create and use a stream that is based on an underlying string. Once we’re familiar with stream-based I/O mechanisms, covered in this chapter, string streams offer us a convenient way to use those techniques to manipulate strings.

String streams are available by including <sstream>:

#include <sstream>

There are three main types of string streams we are likely to encounter:

  • std::istringstream, which is an input stream
  • std::ostringstream, which is an output stream
  • std::stringstream, which is a bidirectional stream

We’ll use std::ostringstream in this lesson, but the concepts apply to all variants. We cover input streams and bidirectional streams in the next lesson.

Creating String Streams

As with other output streams, we can use the << operator to add content to it:

#include <sstream>

int main() {
  std::ostringstream Stream;
  
  // Adding content to the stream
  Stream << "Hello World";
}

The << operator returns a reference to the stream, so it can be chained:

#include <sstream>

int main() {
  std::ostringstream Stream;
  Stream << "Hello"
         << " World"; 
}

Getting the String with str()

The underlying string managed by the string stream is accessible via the str() method:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream;
  Stream << "Hello World";

  std::cout << Stream.str(); 
}
Hello World

An immediate use case for string streams is to concatenate strings. Using string streams for this tends to be more performant than any other approach, particularly when the strings are large.

#include <iostream>
#include <sstream>

int main() {
  std::string Greeting{"Hello"};

  std::ostringstream Stream;
  Stream << Greeting << " World"; 

  std::string Result{Stream.str()};
  std::cout << Result;
}
Hello World

Getting Stream Position with tellp()

A key property of streams that makes them usable in the ways we’ve shown is their internal position.

A position is just an integer. For an output stream, it is where the next output will be inserted. Most functions that insert content into our output stream (including the << operator) also update the output position, which affects where future content will be written.

When working with an output stream, we can find out the current output position using the tellp() method:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream;
  std::cout << "Output position: "
            << Stream.tellp(); 

  Stream << "Hello";
  std::cout << "\nOutput position: "
            << Stream.tellp(); 

  Stream << " World";
  std::cout << "\nOutput position: "
            << Stream.tellp(); 
}
Output position: 0
Output position: 5
Output position: 11

In all our examples in both this lesson and the previous one, the output position has been at the end of the stream, ensuring future content gets appended.

But, the output position doesn’t need to be at the end of the stream. For example, string streams have a constructor that lets us set the initial content of our stream.

std::ostringstream Stream{"Hello"};

However, after construction, the output position is always at 0 even if we provided initial content. This means future writes will occur at the start of the stream, effectively overwriting our initial content.

The following example shows the effect of this:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream{"Hello"};

  std::cout << "Stream contents: "
    << Stream.str();
  std::cout << "\nOutput position: "
    << Stream.tellp();

  Stream << "B";
  std::cout << "\nStream contents: "
    << Stream.str();
  std::cout << "\nOutput position: "
    << Stream.tellp();
}
Stream contents: Hello
Output position: 0
Stream contents: Bello
Output position: 1

Similarly, the str() method of string streams allows us to provide a new string. This resets the content of the stream, but also sets the output position back to 0:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream;
  Stream << "Hello";
  std::cout << "Stream contents: "
            << Stream.str();

  std::cout << "\nOutput position: "
            << Stream.tellp();

  Stream.str("Hello");

  std::cout << "\n\nStream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();

  Stream << "B";

  std::cout << "\n\nStream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();
}
Stream contents: Hello
Output position: 5

Stream contents: Hello
Output position: 0

Stream contents: Bello
Output position: 1

Later in this lesson, we cover an alternative constructor that causes our streams to move their output position to the end after construction and reset.

But, we can also control the positions manually, using seekp(), which we cover next.

Setting Stream Position with seekp()

Output and bidirectional streams have a seekp() method, which allows us to set the output stream position. There are two ways we can use this - we can either set the position absolutely, or relatively.

Let's take a look at setting absolute positions first. We can do this by passing a single number to the function, to define what we want the position to be.

The following example demonstrates this, passing 0 to reset our output position back to the start of the stream:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream;
  Stream << "hello";

  std::cout << "Stream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();

  std::cout << "\n\nUsing seekp(0)";
  Stream.seekp(0); 
  std::cout << "\nOutput position: "
            << Stream.tellp();

  std::cout << "\n\nInserting H";
  Stream << "H";
  std::cout << "\nStream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();
}
Stream contents: hello
Output position: 5

Using seekp(0)
Output position: 0

Inserting H
Stream contents: Hello
Output position: 1

Relative Stream Positions

More commonly, we won’t want to seek an absolute position, rather, we will want to seek relative to some other position. Typical scenarios for this include:

  • Seeking forward or backward from our current position
  • Seeking to the end of the stream (which we may not know the absolute position of)

We can perform seeks like this by passing a second argument to seekp(). The second argument represents the other position that we want to base our new position on. It has three options:

  • std::ios::beg - Set the new position relative to the beginning of the stream (rarely used, as the absolute position is already relative to the beginning)
  • std::ios::cur - Set the new position relative to the current position
  • std::ios::end - Set the new position relative to the end of the stream

When providing a second argument to seekp(), the first argument then acts as an offset. For example:

  • We can advance five steps from our current position by passing 5 and std::ios::cur
  • We can seek to the end of the stream by passing 0 and std::ios::end

When using relative seek, our numeric argument can be negative, which moves the position backward.

Below, we use seekp() with an offset of -2 relative to ios::cur to replace the last 2 characters in a stream:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream;
  Stream << "Hello";

  std::cout << "Stream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();

  std::cout << "\n\nUsing seekp(-2, ios::cur)";
  Stream.seekp(-2, std::ios::cur); 
  std::cout << "\nOutput position: "
            << Stream.tellp();

  std::cout << "\n\nInserting \"p!\"";
  Stream << "p!";
  std::cout << "\nStream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();
}
Stream contents: Hello
Output position: 5

Using seekp(-2, ios::cur)
Output position: 3

Inserting "p!"
Stream contents: Help!
Output position: 5

Below, we use the str() method on our string stream, to replace the underlying content. This resets our output position to 0, but we can use seekp() with an offset of 0 relative to ios::end. This moves our position to the end of the stream:

#include <iostream>
#include <sstream>

int main() {
  std::ostringstream Stream;
  Stream.str("Hello");

  std::cout << "Stream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();

  std::cout << "\n\nUsing seekp(0, ios::end)";
  Stream.seekp(0, std::ios::end); 
  std::cout << "\nOutput position: "
            << Stream.tellp();

  std::cout << "\n\nInserting \" World\"";
  Stream << " World";
  std::cout << "\nStream contents: "
            << Stream.str();
  std::cout << "\nOutput position: "
            << Stream.tellp();
}
Stream contents: Hello
Output position: 0

Using seekp(0, ios::end)
Output position: 5

Inserting " World"
Stream contents: Hello World
Output position: 11

Input Positions: tellg() and seekg()

Input streams also have position indicators, but the method names are slightly different: tellg() and seekg() instead. They work in the same way as tellp() and seekp().

The reason for the name difference is that bidirectional streams have both input and output positions. The positions are managed independently of each other, so the way we differentiate which one we’re referring to in our code is through the method name - p for output position; g for input position.

We cover input streams in more detail in the next lesson.

Using "at end" mode with ios::ate

Streams provide various options for behavior, typically called open modes.

Most open modes are only relevant to file streams, which we cover later in this chapter. However, the "at end" (std::ios::ate) open mode is also relevant for string streams.

When we enable this, our string stream will automatically seek to the end upon construction, and every time we reset our underlying string by passing an argument to the str() method.

We provide the open modes as the first argument to the string stream constructor, or as the second argument if we’re providing an initial value.

std::ostringstream A{std::ios::ate};
std::ostringstream B{"Hello", std::ios::ate};

Several modes can be active simultaneously, as open modes are represented as a bitmask.

The default open mode for std::ostringstream is ios::out, which makes our stream writable. We typically want to maintain that, so we combine ios::out with ios::ate using the | operator:

std::ostringstream Stream{
  "Hello", std::ios::out | std::ios::ate};

Note, this is the bitwise | operator, not the boolean || operator. If the | operator is unfamiliar, we have a dedicated lesson covering it, and how it’s used in scenarios like this:

With this flag set, our stream now automatically seeks to the end upon construction, and after every reset using the str() method:

#include <iostream>
#include <sstream>

int main() {
  std::cout << "Initializing with \"Hello\"";
  std::ostringstream Stream{
      "Hello", std::ios::out | std::ios::ate}; 
  std::cout << "\nPosition: " << Stream.tellp();

  std::cout << "\n\nAppending \" World\"";
  Stream << " World";

  std::cout << "\nStream contents: "
            << Stream.str();

  std::cout << "\n\nResetting to \"Goodbye\"";
  Stream.str("Goodbye");
  std::cout << "\nPosition: " << Stream.tellp();

  std::cout << "\n\nAppending \" Everyone\"";
  Stream << " Everyone";
  std::cout << "\nStream contents: "
            << Stream.str();
}
Initializing with "Hello"
Position: 5

Appending " World"
Stream contents: Hello World

Resetting to "Goodbye"
Position: 7

Appending " Everyone"
Stream contents: Goodbye Everyone

Summary

In this lesson, we delved into C++ string streams, exploring their creation, manipulation, and advanced features like stream positions and open modes.

Main Points

  • Introduction to string streams and the types available in C++.
  • How to create and use std::ostringstream to append strings.
  • Retrieving the underlying string with the str() method.
  • Understanding and manipulating the stream's internal position using tellp() and seekp().
  • The concept of open modes such as std::ios::ate for managing stream positions automatically.

Was this lesson useful?

Next Lesson

Input Streams

A detailed introduction to C++ Input Streams using std::cin and istringstream. Starting from the basics and progressing up to advanced use cases including creating collections of custom objects from our streams.
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Strings and Streams
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Input Streams

A detailed introduction to C++ Input Streams using std::cin and istringstream. Starting from the basics and progressing up to advanced use cases including creating collections of custom objects from our streams.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved