Input, Output, and Streams

This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com.

This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

The Iostreams Hierarchy

std::ios_base at the root; std::basic_ios below it; std::basic_istream and std::basic_ostream inherit from basic_ios; std::basic_iostream inherits from both; file

std::cin/std::cout/std::cerr are pre-constructed istream/ostream objects bound to the process’s standard streams; every other stream type (file, string, span) plugs a different backing medium into the same istream/ostream interface.

Formatted vs. Unformatted I/O

#include <iostream>
#include <string>

int main() {
    int n;
    std::cin >> n;                          // formatted: skips leading whitespace, parses per n's type

    std::string line;
    std::getline(std::cin, line);            // unformatted-ish: reads a whole line verbatim, including spaces

    char buffer[16];
    std::cin.read(buffer, sizeof(buffer));    // fully unformatted: raw bytes, no parsing at all
    std::cout.write(buffer, std::cin.gcount()); // gcount(): how many bytes the last unformatted read actually got
}

Always check a stream’s state after reading (if (std::cin >> n) or if (std::cin.fail())) — a failed extraction leaves the target variable unspecified and the stream in a failed state that silently skips every subsequent operation until cleared.

Manipulators and <iomanip>

#include <iostream>
#include <iomanip>

int main() {
    std::cout << std::fixed << std::setprecision(2) << 3.14159 << '\n';   // 3.14
    std::cout << std::hex << 255 << '\n';                                    // ff
    std::cout << std::dec << std::setw(10) << std::setfill('*') << 42 << '\n';   // ********42
    std::cout << std::boolalpha << true << '\n';                              // true, not 1
}

Most manipulators (std::hex, std::fixed, std::boolalpha) are sticky — they persist until changed again; std::setw is the notable exception, applying to only the next single insertion.

String Streams

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

int main() {
    std::ostringstream out;
    out << "x=" << 5 << ", y=" << 10;
    std::string built = out.str();
    std::cout << built << '\n';

    std::istringstream in("42 3.14 hello");
    int i; double d; std::string s;
    in >> i >> d >> s;
    std::cout << i << ' ' << d << ' ' << s << '\n';
}

Binary File I/O

#include <fstream>
#include <vector>

void writeInts(const std::vector<int>& data, const char* path) {
    std::ofstream out(path, std::ios::binary);
    out.write(reinterpret_cast<const char*>(data.data()), data.size() * sizeof(int));
}

std::vector<int> readInts(const char* path, std::size_t count) {
    std::vector<int> data(count);
    std::ifstream in(path, std::ios::binary);
    in.read(reinterpret_cast<char*>(data.data()), count * sizeof(int));
    return data;
}

std::ios::binary disables text-mode newline translation (relevant on Windows); always open in binary mode when reading/writing raw bytes rather than text.

std::span Buffers (<spanstream>)

C++23’s <spanstream> streams directly over an existing, fixed, non-owning buffer (a std::span) — no internal allocation, unlike stringstream’s owned `std::string buffer:

#include <spanstream>
#include <array>

int main() {
    std::array<char, 64> buffer{};
    std::ospanstream out{std::span<char>(buffer)};   // braces, not parens -- parens here would be parsed as a
                                                        // function declaration (the "most vexing parse")
    out << "x=" << 5;
    auto written = out.span();          // the portion of "buffer" actually written, no separate allocation
    (void)written;
}

Locales

A std::locale controls culture-sensitive formatting (thousands separators, decimal points, currency symbols, date formats) for a stream:

#include <iostream>
#include <locale>

int main() {
    std::cout.imbue(std::locale(""));    // the user's default OS locale, instead of the always-active "C" locale
    std::cout << 1234567 << '\n';          // e.g. "1,234,567" or "1.234.567" depending on the imbued locale
}

std::locale("") depends on locale data actually being installed/configured on the host — it throws std::runtime_error if the requested locale isn’t available, so wrap it accordingly in production code.

std::osyncstream

C++20’s std::osyncstream buffers output and flushes it as one atomic block on destruction, preventing interleaved garbled output when multiple threads write to the same stream concurrently (see Threads and Synchronization):

#include <syncstream>
#include <iostream>
#include <thread>

void worker(int id) {
    std::osyncstream(std::cout) << "worker " << id << " done\n";   // the whole line is flushed atomically
}

std::print vs. Streams

std::print/std::println (see Strings and Text) write formatted text more concisely than chained << operators, and — on platforms where the standard library implements it — correctly translate encoding to a Windows console without extra setup, something std::cout with narrow char does not always do correctly:

// streams:
std::cout << "x=" << x << ", y=" << y << '\n';
// std::print:
std::println("x={}, y={}", x, y);
this comparison itself is illustrative — std::print/std::println need <print>, which is not yet in this environment’s libstdc++ 13 (see Getting Started); the std::cout form and every other example on this page do compile locally.

See Also

  • C: Input, Output and Files — the <stdio.h> FILE* API, still available in C++ as <cstdio> and often still the pragmatic choice.