Document Number: D3673R0
Date: 26.10.2025

std::wprint / std::wprintln

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 introduced std::print and std::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, accepting std::wformat_string, to provide consistent support for wide-character formatted output.

Introduction

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.

Revision History

Motivation and Scope

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.

Impact on the Standard

New Functionality

This proposal introduces additional function template overloads, no existing functionality is removed or changed, the impact is additive only.

Wording

Change the __cpp_lib_print entry's value in section "Header synopsis [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);

Effects: Behaves as a formatted output function of os, except that:

After constructing a sentry object, the function initializes an automatic variable via:

wstring out = vformat(os.getloc(), fmt, args);

If the function is 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).

Recommended practice: For 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);
          

Effects: Equivalent to:

print(stdout, fmt, std::forward<Args>(args)...);
template<class... Args>
          void print(FILE* stream, wformat_string<Args...> fmt, Args&&... args);
          

Effects: If the ordinary literal encoding is UTF-8, UTF-16 or UTF-32, equivalent to:

vprint_unicode(stream, fmt.str, make_wformat_args(std::forward<Args>(args)...));

Otherwise, equivalent to:

vprint_nonunicode(stream, fmt.str, make_wformat_args(std::forward<Args>(args)...));
template<class... Args>
          void println(wformat_string<Args...> fmt, Args&&... args);
          

Effects: Equivalent to:

println(stdout, fmt, std::forward<Args>(args)...);
template<class... Args>
          void println(FILE* stream, wformat_string<Args...> fmt, Args&&... args);
          

Effects: Equivalent to:

print(stream, L"{}\n", format(fmt, std::forward<Args>(args)...));
void vprint_unicode(wstring_view fmt, format_args args);
          

Effects: Equivalent to:

vprint_unicode(stdout, fmt, args);

Technical Specification

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.

Implementation Experience

No production implementations are yet available.

Work is in progress to add these overloads to MSVC, ECPPS and Clang.

Design Decisions

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:

  1. std::print and std::format are designed to be consistent; overloads better match this design than distinct function names.
  2. Introducing new overload sets would further unnecessarily increase the complexity and ambiguity.

Support for additional character types (char8_t, char16_t, char32_t) is conceivable but outside the scope of this paper.

References

Appendix A. Examples

WinAPI

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

Unicode characters in paths

void PrintPath(std::filesystem::path path)
{
     std::println(L"Path = {}", path);
}
     

Displaying emojis

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