Strings and Text

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.

std::string and std::string_view

std::string owns and manages a growable buffer of char; std::string_view (C++17) is a non-owning (pointer, length) view — pass it by value to avoid copying when a function only needs to read text:

#include <string>
#include <string_view>
#include <iostream>

void printLength(std::string_view sv) {          // accepts std::string, "literals", substrings -- no copy
    std::cout << sv.size() << '\n';
}

int main() {
    std::string s = "Hello, World!";
    s += " Goodbye.";
    printLength(s);
    printLength("a literal");                     // implicitly builds a string_view, not a std::string
    printLength(std::string_view(s).substr(0, 5)); // "Hello" -- substr on a view is itself non-allocating
}

A string_view must never outlive the buffer it points into — a common bug is returning one that views a now-destroyed temporary std::string.

Character Types and Unicode

C++ has no built-in Unicode-aware string type; std::string is a sequence of char (commonly UTF-8 encoded by convention, not enforced by the type), std::u8string (C++20) is a sequence of char8_t explicitly meant for UTF-8, and std::u16string/std::u32string hold UTF-16/UTF-32 code units:

#include <string>

std::string narrow = "caf\xc3\xa9";      // UTF-8 bytes for "café", typed as plain char
std::u8string utf8 = u8"café";            // same bytes, typed as char8_t -- distinguishable at compile time
std::u32string codepoints = U"café";       // one char32_t per Unicode code point (4 code points: c, a, f, é)

Iterating a UTF-8 std::string by char walks bytes, not code points or grapheme clusters — for real Unicode segmentation, reach for a library like ICU or a codepoint-aware view; the standard library alone does not provide one.

Raw String Literals and User-Defined Literals

See Lexical Structure and Style for the general raw-string and UDL syntax; the standard library’s own string UDLs live in std::literals::string_literals:

#include <string>
#include <string_view>
using namespace std::literals;

auto s = "hello"s;          // std::string, not const char*
auto sv = "hello"sv;         // std::string_view, zero-allocation

<charconv>: Locale-Independent Conversions

std::to_chars/std::from_chars (C++17) convert numbers to/from text without allocating, without exceptions, and without any locale dependency — the fastest and most predictable conversion path available:

#include <charconv>
#include <array>
#include <string_view>
#include <cassert>

int main() {
    std::array<char, 16> buffer{};
    auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), 42);
    assert(ec == std::errc{});
    std::string_view written(buffer.data(), ptr - buffer.data());
    assert(written == "42");

    int parsed = 0;
    auto [ptr2, ec2] = std::from_chars(written.data(), written.data() + written.size(), parsed);
    assert(ec2 == std::errc{});
    assert(parsed == 42);
    (void)ptr2;
    return 0;
}

std::format and std::print

std::format (C++20, header <format>) is a type-safe, Python-style formatting facility that replaces `printf’s format-string/argument mismatches (a common source of undefined behavior) with compile-time-checked placeholders:

#include <format>
#include <iostream>
#include <string>

int main() {
    std::string s = std::format("{} is {} years old, pi ~= {:.2f}", "Ada", 36, 3.14159);
    std::cout << s << '\n';                      // Ada is 36 years old, pi ~= 3.14
    std::cout << std::format("{0} {1} {0}", "a", "b") << '\n';   // positional args: a b a
    std::cout << std::format("{:>10}|{:<10}|{:^10}", "r", "l", "c") << '\n';  // alignment
}

std::print/std::println (C++23, header <print>) write formatted text directly to a stream, skipping the intermediate std::string:

#include <print>

int main() {
    std::println("{} is {} years old", "Ada", 36);
}
<print> is standard-conformant C++23 but is not yet in this environment’s libstdc++ 13 — it needs GCC 14+, a recent libc++, or MSVC (see Getting Started); std::format itself, used above, does compile locally.

Custom Formatters

A type opts into std::format by specializing std::formatter:

#include <format>
#include <string>

struct Point { int x, y; };

template <>
struct std::formatter<Point> : std::formatter<std::string> {
    auto format(const Point& p, std::format_context& ctx) const {
        return std::formatter<std::string>::format(
            std::format("({}, {})", p.x, p.y), ctx);
    }
};

// std::format("{}", Point{1, 2}) now yields "(1, 2)"

<regex>

#include <regex>
#include <string>
#include <iostream>

int main() {
    std::string text = "order-42, order-7";
    std::regex pattern(R"(order-(\d+))");
    for (std::sregex_iterator it(text.begin(), text.end(), pattern), end; it != end; ++it) {
        std::cout << "matched id: " << (*it)[1] << '\n';   // 42, then 7
    }
}

<regex> is notorious for compile-time cost and, on some standard libraries, run-time performance well below hand-written parsers or dedicated libraries (e.g. RE2, Boost.Xpressive) — fine for one-off scripts, worth benchmarking before using on a hot path.

See Also