Skip to content

Commit 16b8c96

Browse files
authored
[libc++][format] Fixes formatting code units as integers. (llvm#73396)
This paper was voted in as a DR, so it's retroactively enabled back to C++20; the C++ version that introduced std::format. Implements: - P2909R4 Fix formatting of code units as integers (Dude, where’s my ``char``?)
1 parent bdecfeb commit 16b8c96

File tree

12 files changed

+309
-83
lines changed

12 files changed

+309
-83
lines changed

libcxx/docs/FeatureTestMacroTable.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ Status
236236
--------------------------------------------------- -----------------
237237
``__cpp_lib_format`` *unimplemented*
238238
--------------------------------------------------- -----------------
239-
``__cpp_lib_format_uchar`` *unimplemented*
239+
``__cpp_lib_format_uchar`` ``202311L``
240240
--------------------------------------------------- -----------------
241241
``__cpp_lib_generic_unordered_lookup`` ``201811L``
242242
--------------------------------------------------- -----------------

libcxx/docs/ReleaseNotes/18.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Implemented Papers
5353
- P2918R2 - Runtime format strings II
5454
- P2871R3 - Remove Deprecated Unicode Conversion Facets from C++26
5555
- P2870R3 - Remove basic_string::reserve()
56+
- P2909R4 - Fix formatting of code units as integers (Dude, where’s my ``char``?)
5657

5758

5859
Improvements and New Features

libcxx/docs/Status/Cxx2cPapers.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"`P2546R5 <https://wg21.link/P2546R5>`__","LWG","Debugging Support","Kona November 2023","","",""
3333
"`P2905R2 <https://wg21.link/P2905R2>`__","LWG","Runtime format strings","Kona November 2023","","","|format| |DR|"
3434
"`P2918R2 <https://wg21.link/P2918R2>`__","LWG","Runtime format strings II","Kona November 2023","|Complete|","18.0","|format|"
35-
"`P2909R4 <https://wg21.link/P2909R4>`__","LWG","Fix formatting of code units as integers (Dude, where’s my ``char``?)","Kona November 2023","","","|format| |DR|"
35+
"`P2909R4 <https://wg21.link/P2909R4>`__","LWG","Fix formatting of code units as integers (Dude, where’s my ``char``?)","Kona November 2023","|Complete|","18.0","|format| |DR|"
3636
"`P0952R2 <https://wg21.link/P0952R2>`__","LWG","A new specification for ``std::generate_canonical``","Kona November 2023","","",""
3737
"`P2447R6 <https://wg21.link/P2447R6>`__","LWG","``std::span`` over an initializer list","Kona November 2023","","",""
3838
"`P2821R5 <https://wg21.link/P2821R5>`__","LWG","``span.at()``","Kona November 2023","","",""

libcxx/docs/Status/FormatIssues.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Number,Name,Standard,Assignee,Status,First released version
1919
"`P2637R3 <https://wg21.link/P2637R3>`__","Member ``visit``","C++26","","",
2020
"`P2905R2 <https://wg21.link/P2905R2>`__","Runtime format strings","C++26 DR","Mark de Wever","|In Progress|"
2121
"`P2918R2 <https://wg21.link/P2918R2>`__","Runtime format strings II","C++26","Mark de Wever","|Complete|",18.0
22-
"`P2909R4 <https://wg21.link/P2909R4>`__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|In Progress|"
22+
"`P2909R4 <https://wg21.link/P2909R4>`__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|Complete|",18.0
2323
`P1361 <https://wg21.link/P1361>`_,"Integration of chrono with text formatting","C++20",Mark de Wever,|In Progress|,
2424
`P2372 <https://wg21.link/P2372>`__,"Fixing locale handling in chrono formatters","C++20",Mark de Wever,|In Progress|,
2525
"`P2419R2 <https://wg21.link/P2419R2>`__","Clarify handling of encodings in localized formatting of chrono types","C++23",

libcxx/include/__format/format_arg_store.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,13 @@ _LIBCPP_HIDE_FROM_ABI basic_format_arg<_Context> __create_format_arg(_Tp& __valu
172172
// __basic_format_arg_value. First handle all types needing adjustment, the
173173
// final else requires no adjustment.
174174
if constexpr (__arg == __arg_t::__char_type)
175-
// On some platforms initializing a wchar_t from a char is a narrowing
176-
// conversion.
177-
return basic_format_arg<_Context>{__arg, static_cast<typename _Context::char_type>(__value)};
175+
176+
# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
177+
if constexpr (same_as<typename _Context::char_type, wchar_t> && same_as<_Dp, char>)
178+
return basic_format_arg<_Context>{__arg, static_cast<wchar_t>(static_cast<unsigned char>(__value))};
179+
else
180+
# endif
181+
return basic_format_arg<_Context>{__arg, __value};
178182
else if constexpr (__arg == __arg_t::__int)
179183
return basic_format_arg<_Context>{__arg, static_cast<int>(__value)};
180184
else if constexpr (__arg == __arg_t::__long_long)

libcxx/include/__format/formatter_char.h

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include <__format/parser_std_format_spec.h>
2222
#include <__format/write_escaped.h>
2323
#include <__type_traits/conditional.h>
24-
#include <__type_traits/is_signed.h>
24+
#include <__type_traits/make_unsigned.h>
2525

2626
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
2727
# pragma GCC system_header
@@ -51,22 +51,21 @@ struct _LIBCPP_TEMPLATE_VIS __formatter_char {
5151
return __formatter::__format_escaped_char(__value, __ctx.out(), __parser_.__get_parsed_std_specifications(__ctx));
5252
# endif
5353

54-
if constexpr (sizeof(_CharT) <= sizeof(int))
55-
// Promotes _CharT to an integral type. This reduces the number of
56-
// instantiations of __format_integer reducing code size.
54+
if constexpr (sizeof(_CharT) <= sizeof(unsigned))
5755
return __formatter::__format_integer(
58-
static_cast<conditional_t<is_signed_v<_CharT>, int, unsigned>>(__value),
56+
static_cast<unsigned>(static_cast<make_unsigned_t<_CharT>>(__value)),
5957
__ctx,
6058
__parser_.__get_parsed_std_specifications(__ctx));
6159
else
62-
return __formatter::__format_integer(__value, __ctx, __parser_.__get_parsed_std_specifications(__ctx));
60+
return __formatter::__format_integer(
61+
static_cast<make_unsigned_t<_CharT>>(__value), __ctx, __parser_.__get_parsed_std_specifications(__ctx));
6362
}
6463

6564
template <class _FormatContext>
6665
_LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(char __value, _FormatContext& __ctx) const
6766
requires(same_as<_CharT, wchar_t>)
6867
{
69-
return format(static_cast<wchar_t>(__value), __ctx);
68+
return format(static_cast<wchar_t>(static_cast<unsigned char>(__value)), __ctx);
7069
}
7170

7271
# if _LIBCPP_STD_VER >= 23
@@ -84,8 +83,7 @@ template <>
8483
struct _LIBCPP_TEMPLATE_VIS formatter<char, wchar_t> : public __formatter_char<wchar_t> {};
8584

8685
template <>
87-
struct _LIBCPP_TEMPLATE_VIS formatter<wchar_t, wchar_t> : public __formatter_char<wchar_t> {
88-
};
86+
struct _LIBCPP_TEMPLATE_VIS formatter<wchar_t, wchar_t> : public __formatter_char<wchar_t> {};
8987

9088
# endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
9189

libcxx/include/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ __cpp_lib_within_lifetime 202306L <type_traits>
384384
# undef __cpp_lib_execution
385385
// # define __cpp_lib_execution 201902L
386386
// # define __cpp_lib_format 202106L
387-
// # define __cpp_lib_format_uchar 202311L
387+
# define __cpp_lib_format_uchar 202311L
388388
# define __cpp_lib_generic_unordered_lookup 201811L
389389
# define __cpp_lib_int_pow2 202002L
390390
# define __cpp_lib_integer_comparison_functions 202002L

libcxx/test/std/language.support/support.limits/support.limits.general/.version.compile.pass.cpp

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,11 @@
5555

5656
#elif TEST_STD_VER == 20
5757

58-
# if !defined(_LIBCPP_VERSION)
59-
# ifndef __cpp_lib_format_uchar
60-
# error "__cpp_lib_format_uchar should be defined in c++20"
61-
# endif
62-
# if __cpp_lib_format_uchar != 202311L
63-
# error "__cpp_lib_format_uchar should have the value 202311L in c++20"
64-
# endif
65-
# else // _LIBCPP_VERSION
66-
# ifdef __cpp_lib_format_uchar
67-
# error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
68-
# endif
58+
# ifndef __cpp_lib_format_uchar
59+
# error "__cpp_lib_format_uchar should be defined in c++20"
60+
# endif
61+
# if __cpp_lib_format_uchar != 202311L
62+
# error "__cpp_lib_format_uchar should have the value 202311L in c++20"
6963
# endif
7064

7165
# ifdef __cpp_lib_saturation_arithmetic
@@ -74,17 +68,11 @@
7468

7569
#elif TEST_STD_VER == 23
7670

77-
# if !defined(_LIBCPP_VERSION)
78-
# ifndef __cpp_lib_format_uchar
79-
# error "__cpp_lib_format_uchar should be defined in c++23"
80-
# endif
81-
# if __cpp_lib_format_uchar != 202311L
82-
# error "__cpp_lib_format_uchar should have the value 202311L in c++23"
83-
# endif
84-
# else // _LIBCPP_VERSION
85-
# ifdef __cpp_lib_format_uchar
86-
# error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
87-
# endif
71+
# ifndef __cpp_lib_format_uchar
72+
# error "__cpp_lib_format_uchar should be defined in c++23"
73+
# endif
74+
# if __cpp_lib_format_uchar != 202311L
75+
# error "__cpp_lib_format_uchar should have the value 202311L in c++23"
8876
# endif
8977

9078
# ifdef __cpp_lib_saturation_arithmetic
@@ -93,17 +81,11 @@
9381

9482
#elif TEST_STD_VER > 23
9583

96-
# if !defined(_LIBCPP_VERSION)
97-
# ifndef __cpp_lib_format_uchar
98-
# error "__cpp_lib_format_uchar should be defined in c++26"
99-
# endif
100-
# if __cpp_lib_format_uchar != 202311L
101-
# error "__cpp_lib_format_uchar should have the value 202311L in c++26"
102-
# endif
103-
# else // _LIBCPP_VERSION
104-
# ifdef __cpp_lib_format_uchar
105-
# error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
106-
# endif
84+
# ifndef __cpp_lib_format_uchar
85+
# error "__cpp_lib_format_uchar should be defined in c++26"
86+
# endif
87+
# if __cpp_lib_format_uchar != 202311L
88+
# error "__cpp_lib_format_uchar should have the value 202311L in c++26"
10789
# endif
10890

10991
# if !defined(_LIBCPP_VERSION)

libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp

Lines changed: 15 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3383,17 +3383,11 @@
33833383
# error "__cpp_lib_format_ranges should not be defined before c++23"
33843384
# endif
33853385

3386-
# if !defined(_LIBCPP_VERSION)
3387-
# ifndef __cpp_lib_format_uchar
3388-
# error "__cpp_lib_format_uchar should be defined in c++20"
3389-
# endif
3390-
# if __cpp_lib_format_uchar != 202311L
3391-
# error "__cpp_lib_format_uchar should have the value 202311L in c++20"
3392-
# endif
3393-
# else // _LIBCPP_VERSION
3394-
# ifdef __cpp_lib_format_uchar
3395-
# error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
3396-
# endif
3386+
# ifndef __cpp_lib_format_uchar
3387+
# error "__cpp_lib_format_uchar should be defined in c++20"
3388+
# endif
3389+
# if __cpp_lib_format_uchar != 202311L
3390+
# error "__cpp_lib_format_uchar should have the value 202311L in c++20"
33973391
# endif
33983392

33993393
# ifdef __cpp_lib_formatters
@@ -4778,17 +4772,11 @@
47784772
# error "__cpp_lib_format_ranges should have the value 202207L in c++23"
47794773
# endif
47804774

4781-
# if !defined(_LIBCPP_VERSION)
4782-
# ifndef __cpp_lib_format_uchar
4783-
# error "__cpp_lib_format_uchar should be defined in c++23"
4784-
# endif
4785-
# if __cpp_lib_format_uchar != 202311L
4786-
# error "__cpp_lib_format_uchar should have the value 202311L in c++23"
4787-
# endif
4788-
# else // _LIBCPP_VERSION
4789-
# ifdef __cpp_lib_format_uchar
4790-
# error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
4791-
# endif
4775+
# ifndef __cpp_lib_format_uchar
4776+
# error "__cpp_lib_format_uchar should be defined in c++23"
4777+
# endif
4778+
# if __cpp_lib_format_uchar != 202311L
4779+
# error "__cpp_lib_format_uchar should have the value 202311L in c++23"
47924780
# endif
47934781

47944782
# if !defined(_LIBCPP_VERSION)
@@ -6389,17 +6377,11 @@
63896377
# error "__cpp_lib_format_ranges should have the value 202207L in c++26"
63906378
# endif
63916379

6392-
# if !defined(_LIBCPP_VERSION)
6393-
# ifndef __cpp_lib_format_uchar
6394-
# error "__cpp_lib_format_uchar should be defined in c++26"
6395-
# endif
6396-
# if __cpp_lib_format_uchar != 202311L
6397-
# error "__cpp_lib_format_uchar should have the value 202311L in c++26"
6398-
# endif
6399-
# else // _LIBCPP_VERSION
6400-
# ifdef __cpp_lib_format_uchar
6401-
# error "__cpp_lib_format_uchar should not be defined because it is unimplemented in libc++!"
6402-
# endif
6380+
# ifndef __cpp_lib_format_uchar
6381+
# error "__cpp_lib_format_uchar should be defined in c++26"
6382+
# endif
6383+
# if __cpp_lib_format_uchar != 202311L
6384+
# error "__cpp_lib_format_uchar should have the value 202311L in c++26"
64036385
# endif
64046386

64056387
# if !defined(_LIBCPP_VERSION)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//===----------------------------------------------------------------------===//
2+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
// UNSUPPORTED: c++03, c++11, c++14, c++17
9+
// ADDITIONAL_COMPILE_FLAGS: -fsigned-char
10+
11+
// <format>
12+
13+
// C++23 the formatter is a debug-enabled specialization.
14+
// [format.formatter.spec]:
15+
// Each header that declares the template `formatter` provides the following
16+
// enabled specializations:
17+
// template<> struct formatter<char, char>;
18+
// template<> struct formatter<char, wchar_t>;
19+
// template<> struct formatter<wchar_t, wchar_t>;
20+
21+
// P2909R4 "Fix formatting of code units as integers (Dude, where’s my char?)"
22+
// changed the behaviour of char (and wchar_t) when their underlying type is signed.
23+
24+
#include <format>
25+
#include <cassert>
26+
#include <concepts>
27+
#include <iterator>
28+
#include <type_traits>
29+
30+
#include "test_format_context.h"
31+
#include "test_macros.h"
32+
#include "make_string.h"
33+
#include "assert_macros.h"
34+
#include "concat_macros.h"
35+
36+
#define STR(S) MAKE_STRING(CharT, S)
37+
#define SV(S) MAKE_STRING_VIEW(CharT, S)
38+
39+
template <class StringT, class StringViewT, class ArgumentT>
40+
void test(StringT expected, StringViewT fmt, ArgumentT arg) {
41+
using CharT = typename StringT::value_type;
42+
auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
43+
std::formatter<ArgumentT, CharT> formatter;
44+
static_assert(std::semiregular<decltype(formatter)>);
45+
46+
formatter.parse(parse_ctx);
47+
48+
StringT result;
49+
auto out = std::back_inserter(result);
50+
using FormatCtxT = std::basic_format_context<decltype(out), CharT>;
51+
52+
FormatCtxT format_ctx = test_format_context_create<decltype(out), CharT>(out, std::make_format_args<FormatCtxT>(arg));
53+
formatter.format(arg, format_ctx);
54+
TEST_REQUIRE(result == expected,
55+
TEST_WRITE_CONCATENATED(
56+
"\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", result, '\n'));
57+
}
58+
59+
template <class CharT>
60+
void test() {
61+
test(STR("\x00"), STR("}"), '\x00');
62+
test(STR("a"), STR("}"), 'a');
63+
test(STR("\x80"), STR("}"), '\x80');
64+
test(STR("\xff"), STR("}"), '\xff');
65+
66+
test(STR("\x00"), STR("c}"), '\x00');
67+
test(STR("a"), STR("c}"), 'a');
68+
test(STR("\x80"), STR("c}"), '\x80');
69+
test(STR("\xff"), STR("c}"), '\xff');
70+
71+
#if TEST_STD_VER > 20
72+
test(STR(R"('\u{0}')"), STR("?}"), '\x00');
73+
test(STR("'a'"), STR("?}"), 'a');
74+
if constexpr (std::same_as<CharT, char>) {
75+
test(STR(R"('\x{80}')"), STR("?}"), '\x80');
76+
test(STR(R"('\x{ff}')"), STR("?}"), '\xff');
77+
}
78+
# ifndef TEST_HAS_NO_WIDE_CHARACTERS
79+
else {
80+
test(STR(R"('\u{80}')"), STR("?}"), '\x80');
81+
test(STR("'\u00ff'"), STR("?}"), '\xff');
82+
}
83+
# endif // TEST_HAS_NO_WIDE_CHARACTERS
84+
#endif // TEST_STD_VER > 20
85+
86+
test(STR("10000000"), STR("b}"), char(-128));
87+
test(STR("11111111"), STR("b}"), char(-1));
88+
test(STR("0"), STR("b}"), char(0));
89+
test(STR("1"), STR("b}"), char(1));
90+
test(STR("1111111"), STR("b}"), char(127));
91+
92+
test(STR("10000000"), STR("B}"), char(-128));
93+
test(STR("11111111"), STR("B}"), char(-1));
94+
test(STR("0"), STR("B}"), char(0));
95+
test(STR("1"), STR("B}"), char(1));
96+
test(STR("1111111"), STR("B}"), char(127));
97+
98+
test(STR("128"), STR("d}"), char(-128));
99+
test(STR("255"), STR("d}"), char(-1));
100+
test(STR("0"), STR("d}"), char(0));
101+
test(STR("1"), STR("d}"), char(1));
102+
test(STR("127"), STR("d}"), char(127));
103+
104+
test(STR("200"), STR("o}"), char(-128));
105+
test(STR("377"), STR("o}"), char(-1));
106+
test(STR("0"), STR("o}"), char(0));
107+
test(STR("1"), STR("o}"), char(1));
108+
test(STR("177"), STR("o}"), char(127));
109+
110+
test(STR("80"), STR("x}"), char(-128));
111+
test(STR("ff"), STR("x}"), char(-1));
112+
test(STR("0"), STR("x}"), char(0));
113+
test(STR("1"), STR("x}"), char(1));
114+
test(STR("7f"), STR("x}"), char(127));
115+
116+
test(STR("80"), STR("X}"), char(-128));
117+
test(STR("FF"), STR("X}"), char(-1));
118+
test(STR("0"), STR("X}"), char(0));
119+
test(STR("1"), STR("X}"), char(1));
120+
test(STR("7F"), STR("X}"), char(127));
121+
}
122+
123+
int main(int, char**) {
124+
test<char>();
125+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
126+
test<wchar_t>();
127+
#endif
128+
129+
return 0;
130+
}

0 commit comments

Comments
 (0)