On Windows, the underlying file descriptors for stdout/stdin/stderr
can be reconfigured to wide mode. In the default (narrow) mode, the
charset usually isn't utf8 (as libcxx assumes), but normally a locale
specific codepage (where each codepage only can represent a small
subset of unicode characters).
By configuring the stdout file descriptor to wide mode, the user can
output wchar_t based strings without convesion to the narrow charset.
Within libcxx, don't try to use codecvt to convert this to a narrow
character encoding, but output these strings as such with fputwc.
In wide mode, such strings could be output directly with fwrite too,
but if the file descriptor hasn't been configured in wide mode, that
breaks the output (which currently works reasonably). By always
outputting one character at a time with fputwc, it works regardless
of mode of the stdout file descriptor.
For the narrow output stream, std::cout, outputting (via fwrite)
does fail when the file descriptor is set to wide mode. This matches
how it behaves with both MS STL and GNU libstdc++ too, so this is
probably acceptable.
This fixes https://github.com/llvm/llvm-project/issues/46646, and
the downstream bugs https://github.com/mstorsjo/llvm-mingw/issues/145
and https://github.com/mstorsjo/llvm-mingw/issues/222.
The patch itself isn't very pretty, I would have preferred to get
by with much much fewer ifdefs. Initially, I considered adding
another template type for the char type to use for the stream
(which would be char on other platforms, and equal to char_type on
Windows), but codecvt doesn't support the other type being anything
other than char.
Microsoft's [[ https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmode?view=msvc-170 | documentation for _setmode() ]] states that use of a narrow print function on a Unicode stream will result in an assertion failure (when linked with a debug C run-time library) and that is the case for printf() and putc(). However, it isn't the case for Microsoft's implementation of std::cout and friends; they misbehave (the narrow char buffer gets interpreted as holding a sequence of wchar_t which, of course, does nothing useful), but they don't assert. It seems they must bypass the C functions that assert; probably by calling fwrite() (which doesn't assert).
I'm wondering if we should say more about what the ramifications are if the narrow streams are used in Unicode mode; saying "can't be used" doesn't communicate much. The suggested edit attempts to better explain what happens.