Skip to content

Commit cd91dc3

Browse files
authored
Merge pull request #1170 from cppalliance/format_wide
2 parents f3c594a + 5879222 commit cd91dc3

File tree

3 files changed

+70
-16
lines changed

3 files changed

+70
-16
lines changed

doc/modules/ROOT/pages/format.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ Note the space at the front of these string to keep with width at 10 characters
7575
If you want the result to be a different string width than `char` you can specify this with the format string.
7676
For example if you want the result to be `wchar_t` you can use `L"{}"`.
7777
`wchar_t`, `char16_t`, `char32_t` are supported from pass:[C++17].
78-
`char8_t` is supported from pass:[C++20].
78+
`char8_t` is supported from pass:[C++23].
79+
80+
IMPORTANT: `std::format` only supports `char` and `wchar_t` types per the pass:[C++] specification.
7981

8082
=== Putting it All Together
8183

include/boost/decimal/format.hpp

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ enum class format_sign_option
4242
template <typename ParseContext>
4343
constexpr auto parse_impl(ParseContext &ctx)
4444
{
45+
using CharType = typename ParseContext::char_type;
46+
4547
auto sign_character = format_sign_option::minus;
4648
auto it {ctx.begin()};
4749
int ctx_precision = -1;
@@ -51,7 +53,7 @@ constexpr auto parse_impl(ParseContext &ctx)
5153
bool use_locale = false;
5254

5355
// Check for the locale character
54-
if (*it == 'L')
56+
if (*it == static_cast<CharType>('L'))
5557
{
5658
use_locale = true;
5759
++it;
@@ -62,15 +64,15 @@ constexpr auto parse_impl(ParseContext &ctx)
6264
{
6365
switch (*it)
6466
{
65-
case '-':
67+
case static_cast<CharType>('-'):
6668
sign_character = format_sign_option::minus;
6769
++it;
6870
break;
69-
case '+':
71+
case static_cast<CharType>('+'):
7072
sign_character = format_sign_option::plus;
7173
++it;
7274
break;
73-
case ' ':
75+
case static_cast<CharType>(' '):
7476
sign_character = format_sign_option::space;
7577
++it;
7678
break;
@@ -80,26 +82,26 @@ constexpr auto parse_impl(ParseContext &ctx)
8082
}
8183

8284
// Check for a padding character
83-
while (it != ctx.end() && *it >= '0' && *it <= '9')
85+
while (it != ctx.end() && *it >= static_cast<CharType>('0') && *it <= static_cast<CharType>('9'))
8486
{
85-
padding_digits = padding_digits * 10 + (*it - '0');
87+
padding_digits = padding_digits * 10 + (*it - static_cast<CharType>('0'));
8688
++it;
8789
}
8890

8991
// If there is a . then we need to capture the precision argument
90-
if (it != ctx.end() && *it == '.')
92+
if (it != ctx.end() && *it == static_cast<CharType>('.'))
9193
{
9294
++it;
9395
ctx_precision = 0;
94-
while (it != ctx.end() && *it >= '0' && *it <= '9')
96+
while (it != ctx.end() && *it >= static_cast<CharType>('0') && *it <= static_cast<CharType>('9'))
9597
{
96-
ctx_precision = ctx_precision * 10 + (*it - '0');
98+
ctx_precision = ctx_precision * 10 + (*it - static_cast<CharType>('0'));
9799
++it;
98100
}
99101
}
100102

101103
// Lastly we capture the format to include if it's upper case
102-
if (it != ctx.end() && *it != '}')
104+
if (it != ctx.end() && *it != static_cast<CharType>('}'))
103105
{
104106
switch (*it)
105107
{
@@ -152,20 +154,33 @@ constexpr auto parse_impl(ParseContext &ctx)
152154
}
153155

154156
// Verify we're at the closing brace
155-
if (it != ctx.end() && *it != '}')
157+
if (it != ctx.end() && *it != static_cast<CharType>('}'))
156158
{
157159
BOOST_DECIMAL_THROW_EXCEPTION(std::format_error("Expected '}' in format string")); // LCOV_EXCL_LINE
158160
}
159161

160162
return std::make_tuple(ctx_precision, fmt, is_upper, padding_digits, sign_character, use_locale, it);
161163
}
162164

165+
template <typename>
166+
struct formattable_character_type : std::false_type {};
167+
168+
template <>
169+
struct formattable_character_type<char> : std::true_type {};
170+
171+
template <>
172+
struct formattable_character_type<wchar_t> : std::true_type {};
173+
174+
template <typename CharT>
175+
inline constexpr bool is_formattable_character_type_v = formattable_character_type<CharT>::value;
176+
163177
} // Namespace boost::decimal::detail
164178

165179
namespace std {
166180

167-
template <boost::decimal::detail::concepts::decimal_floating_point_type T>
168-
struct formatter<T>
181+
template <boost::decimal::detail::concepts::decimal_floating_point_type T, typename CharT>
182+
requires boost::decimal::detail::is_formattable_character_type_v<CharT>
183+
struct formatter<T, CharT>
169184
{
170185
boost::decimal::chars_format fmt;
171186
boost::decimal::detail::format_sign_option sign;
@@ -182,7 +197,7 @@ struct formatter<T>
182197
use_locale(false)
183198
{}
184199

185-
constexpr auto parse(format_parse_context &ctx)
200+
constexpr auto parse(basic_format_parse_context<CharT>& ctx)
186201
{
187202
const auto res {boost::decimal::detail::parse_impl(ctx)};
188203

@@ -202,6 +217,9 @@ struct formatter<T>
202217
using namespace boost::decimal;
203218
using namespace boost::decimal::detail;
204219

220+
using CharType = FormatContext::char_type;
221+
static_assert(is_formattable_character_type_v<CharType>, "This is an unsupported character type. Only char and wchar_t can be used with std::format");
222+
205223
std::array<char, 128> buffer {};
206224
auto buffer_front = buffer.data();
207225
bool has_sign {false};
@@ -276,7 +294,22 @@ struct formatter<T>
276294
s.resize(initial_length + offset);
277295
}
278296

279-
return std::format_to(ctx.out(), "{}", s);
297+
// std <format> only supports char and wchar_t
298+
if constexpr (std::is_same_v<CharType, char>)
299+
{
300+
return std::format_to(ctx.out(), "{}", s);
301+
}
302+
else
303+
{
304+
std::wstring result;
305+
result.reserve(s.size());
306+
for (const char c : s)
307+
{
308+
result.push_back(static_cast<CharType>(static_cast<unsigned char>(c)));
309+
}
310+
311+
return std::format_to(ctx.out(), L"{}", result);
312+
}
280313
}
281314
};
282315

test/test_format.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,18 @@ void test_locale_conversion(const char* locale, const std::string& result)
265265
// LCOV_EXCL_STOP
266266
}
267267

268+
template <typename T>
269+
void test_wide_strings()
270+
{
271+
BOOST_TEST(std::format(L"{}", T{1}) == L"1");
272+
BOOST_TEST(std::format(L"{}", T{10}) == L"10");
273+
BOOST_TEST(std::format(L"{}", T{100}) == L"100");
274+
BOOST_TEST(std::format(L"{}", T{1000}) == L"1000");
275+
BOOST_TEST(std::format(L"{}", T{10000}) == L"10000");
276+
BOOST_TEST(std::format(L"{}", T{210000}) == L"210000");
277+
BOOST_TEST(std::format(L"{}", T{2100000}) == L"2100000");
278+
}
279+
268280
int main()
269281
{
270282
test_general<decimal32_t>();
@@ -317,6 +329,13 @@ int main()
317329

318330
#endif
319331

332+
test_wide_strings<decimal32_t>();
333+
test_wide_strings<decimal_fast32_t>();
334+
test_wide_strings<decimal64_t>();
335+
test_wide_strings<decimal_fast64_t>();
336+
test_wide_strings<decimal128_t>();
337+
test_wide_strings<decimal_fast128_t>();
338+
320339
return boost::report_errors();
321340
}
322341

0 commit comments

Comments
 (0)