Skip to content
C++
Library
since C++20
Basic

std::format

Type-safe, compile-time-checked text formatting introduced in C++20, replacing printf and stream-based output with Python-style format strings.

std::formatsince C++20

A type-safe, extensible string formatting function from <format> that accepts a compile-time format string with {} replacement fields and returns a std::string.

Overview

std::format models Python's str.format() with compile-time type guarantees. Unlike printf, type mismatches are caught at build time. Unlike std::ostringstream, formatting logic is decoupled from the value — custom types are formatted via a specialization point, not operator<<.

The header is <format>. C++23 adds <print> for std::print and std::println, and extends range formatting (std::vector, std::tuple, etc.) directly through <format>.

C++20 vs. C++23 enforcement: C++20 compilers required the format string to be a constant expression by convention; enforcement was implementation-defined. C++23 (P2216) formally mandates it — passing a runtime std::string as the first argument is now a compile error. Runtime format strings require std::vformat.

Syntax

cpp
// <format> — C++20
std::string std::format(format-string fmt, Args&&... args);
void        std::format_to(OutputIt out, format-string fmt, Args&&... args);
std::size_t std::formatted_size(format-string fmt, Args&&... args);
auto        std::format_to_n(OutputIt out, std::size_t n, format-string fmt, Args&&... args);

// Runtime format string — C++20
std::string std::vformat(std::string_view fmt, std::format_args args);
void        std::vformat_to(OutputIt out, std::string_view fmt, std::format_args args);

// <print> — C++23
void std::print(format-string fmt, Args&&... args);          // stdout
void std::println(format-string fmt, Args&&... args);        // stdout + '\n'
void std::print(std::FILE* f, format-string fmt, ...);       // FILE* overload
void std::print(std::ostream& os, format-string fmt, ...);   // ostream overload

Examples

Format specifier mini-language

The spec syntax inside braces is {[index]:[fill][align][sign][#][0][width][.precision][L][type]}.

cpp
#include <format>

// Integers
std::format("{:d}", 255);        // "255"       decimal
std::format("{:x}", 255);        // "ff"        lowercase hex
std::format("{:#x}", 255);       // "0xff"      alternate form adds prefix
std::format("{:08x}", 255);      // "000000ff"  zero-padded to width 8
std::format("{:b}", 255);        // "11111111"  binary
std::format("{:#010b}", 3);      // "0b00000011"

// Floating point
std::format("{:.4f}", 3.14159265); // "3.1416"    fixed, 4 decimal places
std::format("{:.4e}", 3.14159265); // "3.1416e+00" scientific
std::format("{:.4g}", 3.14159265); // "3.142"     shortest representation
std::format("{:+.2f}", 3.14);      // "+3.14"     force sign

// Alignment and fill
std::format("{:>10}", "hi");    // "        hi"  right (number default)
std::format("{:<10}", 42);      // "42        "  left
std::format("{:^10}", 42);      // "    42    "  centered
std::format("{:*^10}", 42);     // "****42****"  custom fill char

// Strings with precision — truncation, not rounding
std::format("{:.5}", "truncate"); // "trunc"

// Boolean
std::format("{}", true);          // "true"   (default: alpha)
std::format("{:d}", true);        // "1"      integer representation

Positional indices and dynamic arguments

cpp
// Reuse an argument multiple times
std::format("{0}, {1}, {0}", "ping", "pong");  // "ping, pong, ping"

// Dynamic width and precision — referenced argument must be integral
int width = 12, prec = 3;
std::format("{:{}.{}f}", 3.14159, width, prec); // "       3.142"

// Locale-aware formatting — C++20; {:L} uses the thread locale
std::format(std::locale("en_US.UTF-8"), "{:L}", 1'000'000); // "1,000,000"
std::format(std::locale("de_DE.UTF-8"), "{:L}", 1'000'000); // "1.000.000"
// Avoid {:L} for machine-readable output (logs, serialisation)

Output without heap allocation

cpp
#include <format>
#include <array>

// format_to — write into any OutputIterator; no intermediate std::string
std::string buf;
buf.reserve(64);
std::format_to(std::back_inserter(buf), "({:.2f}, {:.2f})", 1.0, 2.0);

// format_to_n — bounded write; returns {out_iterator, total_size}
std::array<char, 32> fixed;
auto [end, total] = std::format_to_n(fixed.data(), fixed.size() - 1, "{:.6f}", 3.14);
*end = '\0';  // null-terminate manually

// formatted_size — determine required buffer without writing
std::size_t needed = std::formatted_size("{:08.3f}", 42.7); // 8

// C++23: direct output — no std::cout, no manual '\n' on println
std::print(std::cerr, "[error] {}: {}\n", errno, strerror(errno));
std::println("Finished in {:.2f}s", elapsed);

Custom type formatting

Specialize std::formatter<T>. parse receives a format_parse_context pointing just after the : in the spec; advance and return the iterator. format writes to ctx.out().

cpp
#include <format>

struct Rgb { uint8_t r, g, b; };

template <>
struct std::formatter<Rgb> {
    bool hex = false;

    constexpr auto parse(std::format_parse_context& ctx) {
        auto it = ctx.begin();
        if (it != ctx.end() && *it == 'x') {
            hex = true;
            ++it;
        }
        if (it != ctx.end() && *it != '}')
            throw std::format_error("invalid Rgb format spec");
        return it;
    }

    auto format(const Rgb& c, std::format_context& ctx) const {
        if (hex)
            return std::format_to(ctx.out(), "#{:02X}{:02X}{:02X}", c.r, c.g, c.b);
        return std::format_to(ctx.out(), "rgb({}, {}, {})", c.r, c.g, c.b);
    }
};

std::format("{}", Rgb{255, 0, 0});   // "rgb(255, 0, 0)"
std::format("{:x}", Rgb{255, 0, 0}); // "#FF0000"

For wrapper types, inherit from an existing formatter to get alignment and width for free:

cpp
template <>
struct std::formatter<MyPath> : std::formatter<std::string> {
    auto format(const MyPath& p, std::format_context& ctx) const {
        return std::formatter<std::string>::format(p.native(), ctx);
    }
    // parse() is inherited — all string specs (width, align, truncation) work
};

Runtime format strings

When the format string is not a compile-time constant (logging frameworks, config-driven output), use std::vformat:

cpp
#include <format>

void emit(std::string_view level, std::string_view fmt, std::format_args args) {
    std::string msg = std::vformat(fmt, args);
    write_log(level, msg);
}

// Caller:
emit("INFO", "{} connections, {:.1f}% capacity",
     std::make_format_args(conn_count, pct));

std::make_format_args captures references — the resulting std::format_args is valid only for the lifetime of its arguments.

Range formatting (C++23)

cpp
#include <format>
#include <vector>
#include <map>

std::vector<int> v{1, 2, 3};
std::format("{}", v);       // "[1, 2, 3]"
std::format("{:n}", v);     // "1, 2, 3"   no brackets
std::format("{::d}", v);    // "[1, 2, 3]" per-element spec after '::'
std::format("{::08b}", v);  // "[00000001, 00000010, 00000011]"

std::map<std::string, int> m{{"b", 2}, {"a", 1}};
std::format("{}", m);       // "{'a': 1, 'b': 2}"

Best Practices

Use format_to on pre-reserved buffers for hot paths. std::format always allocates. std::format_to with a std::string after reserve(), or a stack std::array<char, N>, avoids extra allocation. Call std::formatted_size first when the output size varies.

Inherit from existing formatters for wrapper types. Rather than re-implementing fill, align, and width logic, inherit from std::formatter<std::string> or std::formatter<double> and delegate parse() to the base.

Gate {:L} to user-visible output. Locale-aware formatting inserts locale-correct separators and decimal points. Never use it for serialised data, log files, or protocol output where a specific format is required.

Prefer std::println over std::cout in C++23. std::println doesn't synchronise with C stdio by default, has no state to corrupt (unlike std::ios flags), and composes naturally with std::format's compile-time checking.

Common Pitfalls

Format string must be a compile-time constant (C++23)

cpp
std::string fmt = "{} {}";
auto s = std::format(fmt, a, b);  // compile error in C++23

// Fix: use vformat for runtime strings
auto s = std::vformat(fmt, std::make_format_args(a, b));

std::format_args holds references, not copies

cpp
// UB: format_args outlives the arguments it references
std::format_args bad() {
    int x = 42;
    return std::make_format_args(x);  // x is destroyed on return
}

Construct std::format_args and call vformat in the same expression or scope.

std::vformat throws on bad specs — std::format does not

std::format diagnoses type/spec mismatches at compile time. With std::vformat, a malformed or type-incompatible format string throws std::format_error at runtime. Wrap accordingly in any code that takes user-supplied format strings:

cpp
try {
    result = std::vformat(user_fmt, std::make_format_args(value));
} catch (const std::format_error& e) {
    // reject or sanitise
}

No named arguments

The standard does not support {name} syntax. Use positional indices {0}, {1} for argument reuse, or define a struct with a custom std::formatter specialization if labelled output is required.

See Also

  • std::print, std::println — C++23 output built directly on std::format
  • std::formatter — customization point for user-defined type formatting
  • std::vformat, std::make_format_args — runtime format string support
  • <chrono> formatting — std::chrono::time_point, duration, and calendar types have built-in std::formatter specializations since C++20
  • std::to_string — single-value conversion with no format control