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.
| Approach | Example | Type-safe | Extensible | Readable | Unicode |
|---|---|---|---|---|---|
| printf (C) | printf("x=%d y=%d\n", x, y); | No | No | Yes | Partial |
| cout (C++) | cout << "x=" << x << " y=" << y << "\n"; | Yes | Yes | No | Partial |
| cout+format (C++20) | cout << format("x={} y={}\n", x, y); | Yes | Yes | Yes | Partial |
| println (C++23) | println("x={} y={}.", x, y); | Yes | Yes | Yes | Yes |
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 constantstd::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
| Function | Header | Newline | Notes |
|---|---|---|---|
| std::print(fmt, args...) | <print> | No | Writes to stdout |
| std::println(fmt, args...) | <print> | Yes | Writes to stdout |
| std::println() | <print> | Yes | Blank line to stdout |
| std::print(stream, fmt, args...) | <print> | No | Writes to stream (e.g. stderr) |
| std::println(stream, fmt, args...) | <print> | Yes | Writes to stream |
| std::vprint_unicode(fmt, args) | <print> | No | Runtime format string, Unicode output |
| std::vprint_nonunicode(fmt, args) | <print> | No | Runtime format string, raw output |
| std::make_format_args(args...) | <format> | — | Builds argument pack for vprint functions |