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

std::format and std::print

C++20 std::format and C++23 std::print — type-safe formatted output with compile-time checking, custom formatters, and efficient direct printing.

std::formatsince C++20

A compile-time-checked, type-safe function that formats arguments into a string according to a format specifier, replacing sprintf and ostringstream for most use cases.

Overview

std::format (C++20) formats values into a std::string with compile-time validation of the format string and argument count. std::print and std::println (C++23) perform the same formatting but write directly to an output stream, eliminating the intermediate string allocation.

Key advantages over legacy approaches:

  • Compile-time safety: Format specifiers and argument counts are checked at compile time (if the format string is a string literal)
  • Type-safe: Unlike sprintf, format specifiers cannot mismatch their arguments
  • Locale-independent by default: No setlocale side effects; use :L to enable locale-aware formatting
  • Performance: Significantly faster than sprintf and ostringstream for typical formatting tasks
  • Familiar syntax: Similar to Python's str.format() and JavaScript template literals

Syntax

Basic Format String

cpp
#include <format>    // C++20
#include <print>     // C++23

std::string s = std::format(format_string, arg0, arg1, ...);
std::print(format_string, arg0, arg1, ...);        // C++23
std::println(format_string, arg0, arg1, ...);      // C++23; adds newline

Format Specification Mini-Language

cpp
{[arg-id][:[fill][align][sign][#][0][width][grouping][.precision][type]]}
  • arg-id: 0, 1, 2, ... or omitted for sequential
  • fill/align: padding character and alignment (<, >, ^ for left, right, center)
  • sign: + (always show), - (negative only, default), space (space for positive)
  • #: alternate form (prefix 0x, 0b, 0o for integers; decimal point for floats)
  • 0: zero-pad (shorthand for fill='0' with align='0')
  • width: minimum field width
  • grouping: , for thousands separators (C++26; use custom formatter for C++20–23)
  • precision: decimal places (floats) or max width (strings)
  • type: d, b, o, x, X (integers); f, e, E, g, G (floats); s (strings); c (char); B, b (bool)

Examples

Positional and Sequential Arguments

cpp
// Sequential (automatic indexing)
std::format("{} + {} = {}", 1, 2, 3);           // "1 + 2 = 3"

// Positional indexing
std::format("{2} {0} {1}", "a", "b", "c");     // "c a b"

// Reuse arguments
std::format("{0} {0}", "hello");                // "hello hello"

Integer Formatting

cpp
std::format("{}", 42);                          // "42"
std::format("{:d}", 42);                        // "42" (decimal, explicit)
std::format("{:b}", 42);                        // "101010" (binary; C++20)
std::format("{:o}", 42);                        // "52" (octal)
std::format("{:x}", 255);                       // "ff" (hex lowercase)
std::format("{:X}", 255);                       // "FF" (hex uppercase)
std::format("{:#x}", 0xFF);                     // "0xff" (alternate form)
std::format("{:#b}", 42);                       // "0b101010" (alternate form)
std::format("{:+d}", 42);                       // "+42" (always show sign)
std::format("{:010d}", 42);                     // "0000000042" (zero-padded width 10)
std::format("{:_^10}", "x");                    // "____x_____" (custom fill char)

Float Formatting

cpp
std::format("{}", 3.14159);                     // "3.14159" (shortest round-trip)
std::format("{:f}", 3.14);                      // "3.140000" (fixed, 6 decimals)
std::format("{:.2f}", 3.14159);                 // "3.14" (fixed, 2 decimals)
std::format("{:e}", 3.14);                      // "3.140000e+00" (scientific)
std::format("{:.2e}", 3.14);                    // "3.14e+00"
std::format("{:g}", 3.14);                      // "3.14" (shortest of f or e)
std::format("{:g}", 1e15);                      // "1e+15" (switches to e for large values)
std::format("{:8.3f}", 3.14);                   // "   3.140" (width 8, 3 decimals, right-aligned)
std::format("{:<8.3f}", 3.14);                  // "3.140   " (left-aligned)
std::format("{:+.1f}", 3.14);                   // "+3.1" (show sign)

String and Character Formatting

cpp
std::format("{}", "hello");                     // "hello"
std::format("{:10}", "hi");                     // "hi        " (left-aligned, width 10)
std::format("{:>10}", "hi");                    // "        hi" (right-aligned)
std::format("{:.5}", "hello world");            // "hello" (truncate to 5 chars)
std::format("{:>10.3}", "hello");               // "       hel" (right-align, width 10, max 3 chars)
std::format("{:c}", 65);                        // "A" (int to char)
std::format("{:b}", true);                      // "true" (boolean)
std::format("{:s}", true);                      // "1" (boolean as integer; avoid, use :b or :d)

std::print and std::println (C++23)

cpp
#include <print>

std::print("Value: {}\n", 42);                  // writes to stdout, no newline added
std::println("Value: {}", 42);                  // writes to stdout, adds '\n'

std::println(std::cerr, "Error: {}", msg);      // write to stderr
std::println(stdout, "C-style: {}", 42);        // write to FILE*

// Direct printing is faster than std::format + std::cout
// because it avoids intermediate string allocation

Runtime Format Strings

cpp
// std::format checks format string at compile time only if it's a string literal
std::string user_format = "{} + {} = {}";
std::string result = std::vformat(user_format, std::make_format_args(1, 2, 3));

Custom Formatters

cpp
struct Point { double x, y; };

template<>
struct std::formatter<Point> {
    bool use_polar = false;

    constexpr auto parse(std::format_parse_context& ctx) {
        auto it = ctx.begin();
        if (it != ctx.end() && *it == 'p') {
            use_polar = true;
            ++it;
        }
        return it;
    }

    auto format(const Point& p, std::format_context& ctx) const {
        if (use_polar) {
            double r = std::sqrt(p.x * p.x + p.y * p.y);
            double theta = std::atan2(p.y, p.x);
            return std::format_to(ctx.out(), "({:.2f}∠{:.2f}°)", r, theta * 180 / M_PI);
        } else {
            return std::format_to(ctx.out(), "({:.2f}, {:.2f})", p.x, p.y);
        }
    }
};

Point p{3.0, 4.0};
std::println("{}", p);      // "(3.00, 4.00)"
std::println("{:p}", p);    // "(5.00∠53.13°)"

Best Practices

  1. Prefer std::print over std::format + std::cout (C++23): Direct output is faster and avoids intermediate string allocation.

  2. Use compile-time format strings: String literals enable compile-time format checking. For runtime strings, use std::vformat and document why.

  3. Avoid locale-aware formatting unless needed: The default locale-independent formatting is faster and more predictable. Use :L only when explicitly required for user-facing output.

  4. For custom types, specialize std::formatter: More efficient and consistent than operator overloading with operator<<.

  5. Specify precision for floats explicitly: Default precision can vary; use .2f or .3g to be clear about output format.

Common Pitfalls

Compile-time checking requires string literal format:

cpp
std::format("{}", 42);                          // ✓ OK, compiles with check
std::string fmt = "{}";
std::vformat(fmt, std::make_format_args(42));   // ✓ OK, but no compile-time check

Format specifier mismatch is a compile error:

cpp
// std::format("{:d}", "hello");  // ✗ compile error: d is not valid for string

Boolean formatting defaults to 1/0 with {:d}:

cpp
std::format("{}", true);        // "true" (default)
std::format("{:d}", true);      // "1" (integer format)
std::format("{:b}", true);      // "true" (explicit boolean)

Precision truncates strings, not rounds:

cpp
std::format("{:.3}", "hello");  // "hel" (truncates to 3 characters)
std::format("{:.2f}", 3.999);   // "4.00" (rounds for floats only)

Locale-independent by default—use :L for locale-aware output:

cpp
std::format("{:,}", 1000000);   // "1000000" (no separator, format ignores locale)
std::format("{:L}", 1000000);   // locale-aware, e.g., "1,000,000" in en_US (C++26)

See Also

  • std::string_view — lightweight non-owning string reference (C++17)
  • std::stringstream — older alternative; slower and more verbose
  • fmt library — predecessor to std::format; backport available for older C++ standards
  • sprintf, printf — C-style formatting; unsafe and locale-dependent