Skip to content
C++

std::print and std::println — Direct Console Output (C++23)

C++20 gave us std::format for building formatted strings. C++23 completes the picture with std::print and std::println: functions that format and write to a stream in one call, eliminating the std::cout << std::format(...) ceremony. They use the same format strings as std::format, are type-safe, handle Unicode correctly, and compile-time-verify format strings against their arguments. They are now the recommended way to write text to the console in modern C++.

The history: printf → streams → format → print

C-style printf is concise and readable — the format string and values are clearly separated — but it is not type-safe and cannot be extended to user-defined types. C++ I/O streams fixed both problems with std::cout <<, but interleaved values and operator calls make complex output verbose and hard to translate to other languages. C++20's std::format brought back the readable format-string style while keeping type safety and extensibility. C++23's std::print/println make the common case — writing formatted output to stdout or stderr — a single, direct call.

ApproachExampleType-safeExtensibleReadableUnicode
printf (C)printf("x=%d y=%d\n", x, y);NoNoYesPartial
cout (C++)cout << "x=" << x << " y=" << y << "\n";YesYesNoPartial
cout+format (C++20)cout << format("x={} y={}\n", x, y);YesYesYesPartial
println (C++23)println("x={} y={}.", x, y);YesYesYesYes

Basic usage

Both functions are defined in <print> (C++23). std::print writes the formatted string without a trailing newline; std::println appends a newline character. Both write to standard output by default. Calling std::println() with no arguments prints a single newline — equivalent to the old std::cout << '\n'.

#include <print>

int x { 42 };
double pi { 3.14159 };
std::string name { "world" };

std::println("Hello, {}!", name);         // Hello, world!
std::print("x = {}  pi = {:.2f}", x, pi); // x = 42  pi = 3.14  (no newline)
std::println();                            // just a newline

// All format specifiers from std::format work identically
std::println("{:>10} | {:<10} | {:^10}", "right", "left", "center");
//     right | left       |   center

// Cannot pass a value directly — format string is required
// std::println(x);       // Error: no matching overload
std::println("{}", x);    // OK
// Custom types with formatters work transparently
struct Point { double x, y; };
// (assuming formatter<Point> specialisation exists)
Point p { 1.5, 2.7 };
std::println("Point: {}", p);    // Point: (1.5, 2.7)

Writing to a specific stream

Both functions accept an optional first argument: a FILE* or std::FILE* stream. When omitted, they write to stdout (same as std::cout). To write to standard error, pass stderr. This matches the common need to separate diagnostic output from program output without switching to a different API.

#include <print>

// Default: write to stdout
std::println("Normal output: {}", value);

// Write to stderr
std::println(stderr, "Error: {}", error_message);
std::print(stderr, "Warning: {} at line {}", msg, line);

// In practice: use stderr for diagnostics
void log_error(const std::string& msg) {
    std::println(stderr, "[ERROR] {}", msg);
}

void log_info(const std::string& msg) {
    std::println("[INFO] {}", msg);
}

Compile-time format string verification (C++23)

In C++23, the format string for std::format, std::print, and std::println must be a compile-time constant. The compiler verifies the format string against the supplied arguments at compile time: mismatched argument counts, invalid specifiers, and type mismatches all become compile errors rather than runtime exceptions. This is a significant safety improvement over printf, which silently produces undefined behaviour on type mismatches.

int n { 42 };
std::string s { "Hello World!" };

std::println("{}", s);       // OK
std::println(s);             // Error: format string must be a constant, not a variable
std::println("{} {}", n);    // Error: too few arguments for two replacement fields

// constexpr format strings are also fine
constexpr auto fmt = "Value: {}";
std::println(fmt, n);        // OK — constexpr is a compile-time constant
Note: This is a breaking change from C++20, where std::format allowed non-constant format strings (they threw std::format_error at runtime on bad input). In C++23, both format and print/println require compile-time constants.

Runtime format strings with vprint_unicode

Sometimes the format string cannot be a compile-time constant — for example, when it is loaded from a localisation database at runtime and varies by locale. For this case C++23 provides std::vprint_unicode (for Unicode output) and std::vprint_nonunicode. These functions accept a runtime string and a type-erased argument pack built with std::make_format_args. They verify the format string at runtime and throw std::format_error if it is invalid.

#include <print>
#include <format>

enum class Language { English, Dutch };

std::string_view getLocalizedFormat(Language lang)
{
    switch (lang) {
        case Language::English: return "Numbers: {0} and {1}.";
        case Language::Dutch:   return "Getallen: {0} en {1}.";
    }
    return "";
}

int main()
{
    Language lang { Language::English };
    std::vprint_unicode(getLocalizedFormat(lang), std::make_format_args(1, 2));
    std::println();   // newline
    lang = Language::Dutch;
    std::vprint_unicode(getLocalizedFormat(lang), std::make_format_args(1, 2));
    std::println();
}
// Output:
// Numbers: 1 and 2.
// Getallen: 1 en 2.

// Error handling for bad runtime format strings
try {
    std::vprint_unicode("An integer: {5}", std::make_format_args(42));
} catch (const std::format_error& e) {
    std::println("{}", e.what());   // "Argument not found."
}

Unicode and UTF-8 correctness

One concrete advantage of std::print and std::println over std::cout is correct UTF-8 output on Unicode-compliant consoles. When writing non-ASCII text through cout, the output may be garbled on some platforms due to locale and code-page conversions. The print functions output UTF-8 directly to the underlying stream, bypassing those conversions.

// UTF-8 output — works correctly with println on Unicode-compliant consoles
std::println("こんにちは世界");    // "Hello World" in Japanese ✓
// cout << "こんにちは世界" << endl;  // May produce garbled output on Windows

// Even emoji work if the terminal supports it
std::println("😊");    // Prints a smiley face ✓

Quick reference

FunctionHeaderNewlineNotes
std::print(fmt, args...)<print>NoWrites to stdout
std::println(fmt, args...)<print>YesWrites to stdout
std::println()<print>YesBlank line to stdout
std::print(stream, fmt, args...)<print>NoWrites to stream (e.g. stderr)
std::println(stream, fmt, args...)<print>YesWrites to stream
std::vprint_unicode(fmt, args)<print>NoRuntime format string, Unicode output
std::vprint_nonunicode(fmt, args)<print>NoRuntime format string, raw output
std::make_format_args(args...)<format>Builds argument pack for vprint functions
Sign in to track progress