1. Revision History
1.1. Revision 0 - October 20th, 2026
-
Initial release. ✨
2. Introduction & Motivation
We should be able to print our Unicode strings to the console, to a file, or to various other -connected interfaces without having to store that conversion in a temporary location. The best layer of the library for this would be the layer, where conversion specifiers are used to convert strings to the internal execution encoding.
There are other implementation-specific and implementation-internal mechanisms that a user cannot easily access, asides from just the internal execution encoding, that makes the C Standard Library the appropriate layer for Unicode character printing. This proposal puts forth 3 new conversion specifiers, , , and , for the reasons enumerated in the following § 3 Design.
3. Design
There’s no fancy design choices to be made, here. We simply do the same thing as the other wording for printing primitives for , where it triggers the use of . Of course, due to the well-documented issues this set of functions has, we instead use the recently standardized functions for this functionality. This requires a bit more of an in-depth description in the wording than its counterparts, but it should be sufficiently useful.
We provide a small extension on by allowing for , where the is just one of , , or (so , , and ). This appears to work well enough and does not compete with the grammar. We can’t use or instead of because this can clash with the parsing grammar and make things ambiguous or change the meaning of existing code, whereas adding a numeric designation to does not do this. We have precedent for doing this with and in C23 for the arbitrarily-wide integer types.
We do NOT propose a new or similar set of functionality (e.g., guarantees printing that goes into a UTF-8 stream). There is no appetite to fork the existing stream infrastructures a third time (see: Wide Streams for the second time) to support this. This does run the risk that the output to all of these streams is likely to be mangled by calls to either or .
The solution to that is to move C code towards using a UTF-8 execution encoding. A paper by Corentin Jabot, n3539 attempts to do that. That paper and its alternatives should be fully explored and implemented more and more often in C implementations to provide high quality, internationalization-ready interfaces that are not just "8 bit clean" but also multibyte encoding prepared.
4. Implementation Experience
While the direct integration into and its various reading and writing facilities has not been done in an existing libc before, Unicode Printing has been proposed and implemented in a spinoff library previously championed by Marcus Johnson in [n3016].
The conversion functions in for C2y were successfully used to print Unicode strings to (standard) input/output files in the [ztd.cuneicode] library.
5. Wording
The following wording is against the latest draft of the C standard.
5.1. Modify §7.24.6.2 "The fprintf function", paragraphs 7 and 8
...
7 The length modifiers and their meanings are:
...
(ell) ...l
(ell-ell) Specifies that a followingll ,b ,B ,d ,i ,o ,u , orx conversion specifier applies to aX orlong long int argument; or that a following n conversion specifier applies to a pointer to aunsigned long long int argument.long long int (ell-N) Specifies: that a followingl N conversion specifier applies to an argument ofc , wherechar N _t isN and denotes32 type (7.32); or, that a followingchar32_t conversion specifier applies to a pointer to an argument ofs , wherechar N _t is exactly one ofN ,8 , or16 and denotes32 ,char8_t , orchar16_t type (7.32), respectively.char32_t
...j ...
...
* The conversion specifiers and their meanings are:
...
,a ...A
If noc length modifier is present, thel argument is converted to anint ...unsigned char
If an
length modifier is present, thel argument is converted as if by a call to thewint_t function with a pointer to storage of at leastwcrtomb bytes, theMB_CUR_MAX argument converted towint_t , and an initial shift state.wchar_t
- If an
length modifier is present, thel32 argument is converted as if by a call to thechar32_t function with the following arguments: an output pointer to storage suitable for a sequence of at leaststdc_c32nrtomcn STDC_MC_MAX objects, an output size with a value of at leastchar , theSTDC_MC_MAX argument used as input, an input size of 1, and anchar32_t initialized to the initial shift state.mbstate_t
If nos length modifier is present, the argument shall be a pointer to storage of character type. ...l
If an
length modifier is present, the argument shall be a pointer to storage ofl type. ...wchar_t
- If an
length modifier is present, the argument shall be a pointer to storage ofl N type, wherechar N _t is one ofN ,8 or16 . The argument of the pointed to32 storage is converted as if by the following algorithm. Zero or more sequential calls tochar N _t convert the argument. The initial input is the argument of the pointer to storage. The initial input size is either the null terminated length of the argument (not including its null terminating character) or, if a precision is specified, the precision value. The inital state is anstdc_c N nrtomcn object initialized to the initial shift sequence. The output is a pointer to an appropriately typed storage suitable for a sequence of at leastmbstate_t STDC_MC_MAX objects. The output size for every call is at leastchar . A series of successful sequential calls toSTDC_MC_MAX then uses the input, input size, output, and state -- initialized to their initial values stated previously -- to convert the argument up until the input size is exhausted. If any of these calls do not result in success, the behavior is undefined. In no case is an incomplete or partial output written.stdc_c N nrtomcn
...p ...
5.2. Modify §7.33.2.2 "The fwprintf function", paragraphs 7 and 8
...
7 The length modifiers and their meanings are:
...
(ell) ...l
(ell-ell) Specifies that a followingll ,b ,B ,d ,i ,o ,u , orx conversion specifier applies to aX orlong long int argument; or that a following n conversion specifier applies to a pointer to aunsigned long long int argument.long long int (ell-N) Specifies: that a followingl N conversion specifier applies to an argument ofc , wherechar N _t isN and denotes32 type (7.32) or that a followingchar32_t conversion specifier applies to a pointer to an argument ofs , wherechar N _t is exactly one ofN ,8 , or16 and denotes pointer to32 ,char8_t , orchar16_t type (7.32), respectively.char32_t
...j ...
...
* The conversion specifiers and their meanings are:
...
,a ...A
If noc length modifier is present, thel argument is converted to a wide ...int
If an
length modifier is present, thel argument is converted towint_t ...wchar_t
- If an
length modifier is present, thel32 argument is converted as if by a call to thechar32_t function with the following arguments: an output pointer to storage suitable for a sequence of at leaststdc_c32nrtomwcn STDC_MWC_MAX objects, an output size with a value of at leastwchar_t , theSTDC_MWC_MAX argument used as the pointed to input, an input size of 1, and anchar32_t object initialized to the initial shift state.mbstate_t
If nos length modifier is present, the argument shall be a pointer to storage of character ...l
If an
length modifier is present, the argument shall be a pointer to storage ofl ...wchar_t
- If an
length modifier is present, the argument shall be a pointer to storage ofl N type, wherechar N _t is one ofN ,8 or16 . The argument of the pointed to32 storage is converted as if by the following algorithm. Zero or more sequential calls tochar N _t convert the argument. The initial input is the argument of the pointer to storage. The initial input size is either the null terminated length of the argument (not including its null terminating character) or, if a precision is specified, the precision value. The inital state is anstdc_c N nrtomwcn object initialized to the initial shift sequence. The output is a pointer to an appropriately typed storage suitable for a sequence of at leastmbstate_t STDC_MWC_MAX objects. The output size for every call is at leastwchar_t . A series of successful sequential calls toSTDC_MWC_MAX then uses the input, input size, output, and state -- initialized to their initial values stated previously -- to convert the argument up until the input size is exhausted. If any of these calls do not result in success, the behavior is undefined. In no case is an incomplete or partial output written.stdc_c N nrtomwcn
...p ...