Document Number: D3673R0
Date: 26.10.2025
Project: ISO/IEC JTC1/SC22/WG21 14882: Programming Language - C++
Audience: Library Evolution Working Group Incubator (LEWGI, SG18), SG16
Reply-To: Tymi <[email protected]>
Abstract
C++23 introducedstd::printandstd::println. While most standard library facilities support wide strings, these functions currently do not. This paper proposes the addition of wide-string overloads of these functions, acceptingstd::wformat_string, to provide consistent support for wide-character formatted output.
This proposal extends the interface of the standard printing utilities by introducing wide-string versions of std::print and std::println.
The addition allows seamless integration with systems that use wide-characters and supports codebases where wide-character-strings are the preferred representation.
C++23 introduced std::print and std::println function templates, which provide efficient formatted text output. These functions are currently limited to narrow string formats.
Wide-strings are widely used throughout many codebases, particularly on Windows and in specialised environments that employ non-UTF-8 encodings. The lack of direct wide-string support in the formatted printing utilities provides confusion and leads to inconsistencies across APIs.
This proposal aims to narrow the gap by providing overloads for the std::print and std::println that accept std::wformat_args, ensuring that developers can use the formatted output facilities with wide-strings.
This proposal introduces additional function template overloads, no existing functionality is removed or changed, the impact is additive only.
Change the
Effects: Behaves as a formatted output function of After constructing a sentry object, the function initializes an automatic variable via: If the function is Recommended practice: For Effects: Equivalent to: Effects: If the ordinary literal encoding is UTF-8, UTF-16 or UTF-32, equivalent to: Otherwise, equivalent to: Effects: Equivalent to: Effects: Equivalent to: Effects: Equivalent to:__cpp_lib_print entry's value in section "Header [version.syn]" to represent the current standard revision:
#define __cpp_lib_print placeholder // also in <print> and <ostream>
Add the following definitions to
[print.fun]void vprint_unicode(wostream& os, wstring_view fmt, wformat_args args);
void vprint_nonunicode(wostream& os, wstring_view fmt, wformat_args args);
os, except that:
vformat is propagated without regard to the value of os.exceptions() and without turning on ios_base::badbit in the error state of os.wstring out = vformat(os.getloc(), fmt, args);vprint_unicode and os is a stream that refers to a terminal capable of displaying Unicode (determined in an implementation-defined manner), it writes out to the terminal using the native Unicode API; if out contains invalid code units, the behaviour is undefined and implementations are encouraged to diagnose it. If the native Unicode API is used, the function flushes os before writing out. Otherwise (if os is not such a stream or the function is vprint_nonunicode), inserts the character sequence [out.begin(), out.end()) into os. If writing to the terminal or inserting into os fails, calls os.setstate(ios_base::badbit) (which may throw ios_base::failure).vprint_unicode, if invoking the native Unicode API requires transcoding, implementations should substitute invalid code units with u+fffd replacement character per the Unicode Standard, Chapter 3.9 u+fffd Substitution in Conversion.Templates
template<class... Args>
void print(wformat_string<Args...> fmt, Args&&... args);
print(stdout, fmt, std::forward<Args>(args)...);template<class... Args>
void print(FILE* stream, wformat_string<Args...> fmt, Args&&... args);
vprint_unicode(stream, fmt.str, make_wformat_args(std::forward<Args>(args)...));vprint_nonunicode(stream, fmt.str, make_wformat_args(std::forward<Args>(args)...));template<class... Args>
void println(wformat_string<Args...> fmt, Args&&... args);
println(stdout, fmt, std::forward<Args>(args)...);template<class... Args>
void println(FILE* stream, wformat_string<Args...> fmt, Args&&... args);
print(stream, L"{}\n", format(fmt, std::forward<Args>(args)...));void vprint_unicode(wstring_view fmt, format_args args);
vprint_unicode(stdout, fmt, args);
namespace std { template <typename... Args> void print(wformat_string<Args...> fmt, Args&&... args);
template <typename... Args>
void print(FILE* stream, wformat_string<Args...> fmt, Args&&... args);
template <typename... Args>
void println(wformat_string<Args...> fmt, Args&&... args);
template <typename... Args>
void println(FILE* stream, wformat_string<Args...> fmt, Args&&... args);
void vprint_unicode(wostream& os, wstring_view fmt, wformat_args args);
void vprint_nonunicode(wostream& os, wstring_view fmt, wformat_args args);
}
These overloads provide wide-string support for std::print and std::println functions.
No production implementations are yet available.
Work is in progress to add these overloads to MSVC, ECPPS and Clang.
An alternative design to additional overloads would introduce distinct functions named wprint and wprintln, aligning with the C naming style (printf / wprintf) and the cout/wcout precedent.
There could also exist overloads for other strings (std::basic_format_string <charN_t>), but this proposal does not cover those. Maybe in a future revision or a separate paper.
This paper rejects that approach for the following reasons:
std::print and std::format are designed to be consistent; overloads better match this design than distinct function names.
Support for additional character types (char8_t, char16_t, char32_t) is conceivable but outside the scope of this paper.
int main(void) { std::wstring locale{}; locale.resize(LOCALE_NAME_MAX_LENGTH); locale.resize(GetUserDefaultLocaleName(locale.data(), locale.size()) - 1); std::println(L"The System Locale is {}", locale); }
Expected output:
The System Locale is en-GB
void PrintPath(std::filesystem::path path) { std::println(L"Path = {}", path); }
void PrintPath(std::filesystem::path path) { std::println(L"Path = {}", path.wstring()); } int main(void) { using std::operator""s; SetConsoleOutputCP(CP_UTF8); std::wstring locale{}; locale.resize(LOCALE_NAME_MAX_LENGTH); locale.resize(GetUserDefaultLocaleName(locale.data(), static_cast<DWORD>(locale.size())) - 1); std::println(L"The System Locale is {}", locale); std::println(L"Pair: 🍋\x200D🟩"); static_assert(sizeof(L"🍋\x200D🟩") == 12); PrintPath(std::filesystem::current_path()); }
Expected output:
The System Locale is en-GB Pair: 🍋🟩 Path = F:\source\P3673\P3673