Stream classes for strings

All previous I/O examples have used std::cout and std::cin for direct interaction with the console. However, C++ provides string stream classes that let you use familiar insertion (<<) and extraction (>>) operators on strings instead of I/O channels. Like regular streams, string streams maintain an internal buffer, but unlike std::cin and std::cout, they aren't connected to hardware devices like keyboards or monitors. String streams excel at buffering output for delayed display or processing input line by line.

C++ provides six string stream classes: istringstream (derived from istream), ostringstream (derived from ostream), and stringstream (derived from iostream) work with regular characters. The wide character variants (wistringstream, wostringstream, wstringstream) handle wide characters. Include the <sstream> header to use these classes.

Inserting data into string streams

Two methods place data into a stringstream:

Method 1: Using the insertion operator

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream logBuffer{};
    logBuffer << "[INFO] Application started successfully\n";

    std::cout << logBuffer.str();

    return 0;
}

Output:

[INFO] Application started successfully

Method 2: Using the str() function

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream logBuffer{};
    logBuffer.str("[WARNING] Low memory detected");

    std::cout << logBuffer.str();

    return 0;
}

Output:

[WARNING] Low memory detected

Extracting data from string streams

Similarly, two methods retrieve data from a stringstream:

Method 1: Using the str() function to get the entire buffer

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream csvData{};
    csvData << "ProductA,299.99,15\n";

    std::cout << "CSV Record:\n" << csvData.str();

    return 0;
}

Output:

CSV Record:
ProductA,299.99,15

Method 2: Using the extraction operator to parse values

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::stringstream csvData{};
    csvData << "ProductA 299.99 15";

    std::string productName{};
    double price{};
    int quantity{};

    csvData >> productName >> price >> quantity;

    std::cout << "Product: " << productName << '\n';
    std::cout << "Price: $" << price << '\n';
    std::cout << "Quantity: " << quantity << '\n';

    return 0;
}

Output:

Product: ProductA
Price: $299.99
Quantity: 15

Note the behavioral difference: the extraction operator (>>) processes the stream sequentially, returning one value at a time. Each successive extraction returns the next item. The str() function always returns the complete buffer contents, regardless of previous extraction operations.

Converting numbers to strings

String streams provide elegant number-to-string conversion using their built-in knowledge of fundamental data types:

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::stringstream converter{};

    int serverPort{8080};
    double apiVersion{2.5};

    converter << serverPort << ' ' << apiVersion;

    std::string portStr{};
    std::string versionStr{};

    converter >> portStr >> versionStr;

    std::cout << "Server configuration:\n";
    std::cout << "  Port: " << portStr << '\n';
    std::cout << "  API Version: " << versionStr << '\n';

    return 0;
}

Output:

Server configuration:
  Port: 8080
  API Version: 2.5

Converting strings to numbers

The reverse operation converts string representations into numeric types:

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream parser{};
    parser << "8080 2.5";

    int serverPort{};
    double apiVersion{};

    parser >> serverPort >> apiVersion;

    std::cout << "Configuration loaded:\n";
    std::cout << "  Port: " << serverPort << '\n';
    std::cout << "  API Version: " << apiVersion << '\n';

    return 0;
}

Output:

Configuration loaded:
  Port: 8080
  API Version: 2.5

Practical application: Building formatted log messages

String streams shine when constructing complex formatted output:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

std::string formatLogEntry(const std::string& level, int errorCode, const std::string& message)
{
    std::stringstream log{};
    log << "[" << level << "] "
        << "Code:" << std::setw(4) << std::setfill('0') << errorCode
        << " - " << message;

    return log.str();
}

int main()
{
    std::string entry1{ formatLogEntry("ERROR", 404, "Resource not found") };
    std::string entry2{ formatLogEntry("WARNING", 42, "Cache limit reached") };
    std::string entry3{ formatLogEntry("INFO", 0, "Service started") };

    std::cout << entry1 << '\n';
    std::cout << entry2 << '\n';
    std::cout << entry3 << '\n';

    return 0;
}

Output:

[ERROR] Code:0404 - Resource not found
[WARNING] Code:0042 - Cache limit reached
[INFO] Code:0000 - Service started

Clearing and reusing string streams

To empty a stringstream for reuse, set it to an empty string. Two equivalent approaches exist:

Approach 1: Using a C-style empty string

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream buffer{};
    buffer << "Temporary data ";

    buffer.str("");  // Clear the buffer

    buffer << "New data";
    std::cout << buffer.str() << '\n';

    return 0;
}

Approach 2: Using an empty std::string object

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::stringstream buffer{};
    buffer << "Temporary data ";

    buffer.str(std::string{});  // Clear the buffer

    buffer << "New data";
    std::cout << buffer.str() << '\n';

    return 0;
}

Both produce:

New data

Best practice: Always call clear() after clearing the buffer

When reusing a stringstream, always call clear() to reset error flags:

#include <iostream>
#include <sstream>

int main()
{
    std::stringstream processor{};
    processor << "First batch of data";

    processor.str("");         // Clear buffer contents
    processor.clear();         // Reset error flags

    processor << "Second batch of data";
    std::cout << processor.str() << '\n';

    return 0;
}

The clear() function resets stream state flags that may have been set during previous operations. We'll explore stream states and error flags in detail in the next lesson.

Parsing CSV data with string streams

Here's a practical example parsing comma-separated values:

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::string csvLine{ "Laptop,1299.99,5,Electronics" };

    std::stringstream parser{};
    parser << csvLine;

    std::string productName{};
    std::string priceStr{};
    std::string quantityStr{};
    std::string category{};

    std::getline(parser, productName, ',');
    std::getline(parser, priceStr, ',');
    std::getline(parser, quantityStr, ',');
    std::getline(parser, category, ',');

    std::cout << "Product Details:\n";
    std::cout << "  Name: " << productName << '\n';
    std::cout << "  Price: $" << priceStr << '\n';
    std::cout << "  Stock: " << quantityStr << '\n';
    std::cout << "  Category: " << category << '\n';

    return 0;
}

Output:

Product Details:
  Name: Laptop
  Price: $1299.99
  Stock: 5
  Category: Electronics

String streams provide powerful, type-safe tools for string manipulation and conversion. They're particularly valuable when formatting complex output or parsing structured text data.

Summary

String stream classes: C++ provides six string stream classes: istringstream (input), ostringstream (output), and stringstream (both), plus wide character variants. Include the header to use them.

Inserting data: Use the insertion operator (<<) to add data to a stringstream, or use the str() function to set the entire buffer contents at once.

Extracting data: Use the extraction operator (>>) to parse values sequentially from the buffer, or use str() to retrieve the complete buffer contents as a string.

Number-to-string conversion: String streams provide elegant conversion using their built-in knowledge of fundamental data types, automatically handling formatting for integers, floats, and other types.

String-to-number conversion: String streams can parse string representations into numeric types by inserting the string and then extracting into numeric variables.

Clearing and reusing: Set the stringstream to an empty string using str("") or str(std::string{}), then call clear() to reset error flags before reusing the stream.

CSV parsing: Use std::getline() with a custom delimiter (comma) to parse comma-separated values into individual fields.

Behavioral differences: The extraction operator processes sequentially, returning one value at a time with each call. The str() function always returns the complete buffer contents regardless of previous extractions.

Practical applications: String streams excel at building formatted log messages, converting between types, parsing structured text, and temporarily buffering output before display.

Understanding string streams enables powerful string manipulation and data conversion without manual parsing, leveraging the familiar stream interface for in-memory operations.