Chapter 16: The Standard Library
The standard library gives C++ its broad practical reach. It provides memory utilities, containers, algorithms, time facilities, error handling tools, filesystem access, and countless small helpers that turn raw language features into a comfortable working environment. This chapter offers a guided walk through major areas so that readers gain a sense of the layout and naming patterns before diving deeper.
Header map and naming patterns
The standard library uses simple header names such as <vector>, <tuple>, and <filesystem>. These contain declarations in the std namespace (with rare auxiliary namespaces for traits or literals). Identifiers often follow consistent suffixes such as _t for type aliases or _v for variable templates, and traits frequently end with _traits or _type.
Categories of library headers
Headers fall into broad groups. There are containers (<vector>, <map>), algorithms (<algorithm>, <numeric>), iterators (<iterator>), utilities (<utility>), I/O streams (<iostream>, <fstream>), multithreading (<thread>, <mutex>), and support libraries such as <type_traits> or <functional>. Each header keeps its domain small which helps you find features quickly and avoid large includes.
#include <utility> // pair, move helpers, forward
#include <tuple> // tuple and tie
#include <chrono> // time and durations
#include <filesystem> // paths, directories, files
Utility types
These utility types model small flexible containers for values. They enable structured binding, safe absence, tagged unions, and erased types. Each fits a distinct shape of problem and keeps code expressive without custom boilerplate.
pair and tuple for structured values
pair holds two values often used for maps or return types. tuple generalizes this to an arbitrary number of elements. Both support structured binding which lets you unpack them into named locals for clarity.
std::pair<int, std::string> p{7, "seven"};
auto [n, word] = p;
std::tuple t{3.14, 42, std::string{"pi-ish"}};
auto [x, y, label] = t;
optional for possibly absent values
optional<T> represents either a T or no value. It prevents sentinel clutter and makes absence explicit. Its interface mirrors pointers in some ways, but the semantics describe presence rather than ownership.
std::optional<int> read_int(std::string_view s) {
if (auto pos = s.find_first_not_of("0123456789"); pos == std::string_view::npos)
return std::stoi(std::string{s});
return std::nullopt;
}
variant and any for dynamic shape
variant holds one value from a fixed set of types and enforces indexed access through std::visit. any holds any type that is copyable and hides its concrete form. variant is type safe and structured; any is flexible but relies on runtime casting.
std::variant<int, std::string> v = 42;
v = std::string{"answer"};
std::any a = 3.14;
double d = std::any_cast<double>(a);
variant when the set of possible types is small and known. Reserve any for plugin interfaces or cases where the type set is open ended.
Error reporting
Errors in C++ can be signalled in several ways. error_code provides a lightweight value based mechanism for reporting system or library errors. expected represents either a successful result or an error value and offers a structured alternative to exceptions.
error_code for portable system style errors
error_code pairs an integer condition with a category that interprets it. Functions that may fail can take an std::error_code& parameter to report problems directly without throwing. This pattern keeps control flow explicit.
std::error_code ec;
auto sz = std::filesystem::file_size("data.txt", ec);
if (ec) {
std::cerr << "cannot get size: " << ec.message();
}
expected for explicit success or failure
expected<T,E> represents either a value of type T or an error of type E. It avoids exceptions while preserving a clear structure for failure paths. C++23 introduces std::expected which brings this style into the standard library.
std::expected<int, std::string> parse_number(std::string_view s) {
if (auto pos = s.find_first_not_of("0123456789"); pos == std::string_view::npos)
return std::stoi(std::string{s});
return std::unexpected("invalid number");
}
expected when you want structured flow without exceptions and when callers must acknowledge both success and failure paths.
Time utilities: chrono
The chrono library models time points, durations, and clocks with strong types. It prevents unit mix ups by requiring explicit conversions and encourages clear reasoning about rates, intervals, and wall clock vs steady time.
Durations and time points
A duration is a count of ticks paired with a unit such as seconds or milliseconds. A time point represents a moment measured from a clock’s epoch. Arithmetic between them follows simple rules: time point plus duration yields another time point; difference of two time points yields a duration.
using namespace std::chrono;
auto t0 = steady_clock::now();
auto t1 = steady_clock::now();
auto dt = t1 - t0; // duration
auto later = t0 + 250ms; // time point plus duration
Clocks and conversions
system_clock tracks real world time; steady_clock guarantees monotonic progress; high_resolution_clock refers to the most precise available clock. Converting between durations uses duration_cast when precision must be controlled.
auto d = 3s;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(d);
system_clock and steady_clock time points. Their epochs and purposes differ so arithmetic between them is ill formed.
Filesystem and paths
The filesystem library gives portable tools for navigating directories, managing paths, querying file status, and performing common operations such as copying, moving, or creating directories. It abstracts platform differences and offers value semantics similar to other library components.
Path manipulation
std::filesystem::path models a filesystem path with separators, extensions, and lexical operations. It keeps platform specific rules inside the type so your code can remain portable.
namespace fs = std::filesystem;
fs::path p = "output";
p /= "log.txt"; // append path component
auto ext = p.extension();
Queries and operations
The library provides functions for existence checks, file size, directory iteration, and file copying. All operations have overloads that accept error_code to report problems without throwing.
for (auto& entry : fs::directory_iterator("data")) {
if (entry.is_regular_file()) {
std::cout << entry.path() << "\n";
}
}
directory_iterator for simple one level scans and recursive_directory_iterator for deep walks. Both provide clean range based loops and clear intent.