From 377cf203e346f684df919f3046907af64ce5bdaa Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 25 Aug 2024 09:20:40 -0700 Subject: [PATCH] Add opt out for built-in types --- include/fmt/base.h | 50 +++++++++++++++++++++++++++++++++++++------- include/fmt/format.h | 41 +++++++++++++++++++++++++++--------- include/fmt/printf.h | 21 ++++++++++++------- test/base-test.cc | 36 +++++++++++++++---------------- test/chrono-test.cc | 22 +++++++------------ test/format-test.cc | 33 ++++++++++++++++++++--------- test/os-test.cc | 2 +- test/scan-test.cc | 6 +++--- test/std-test.cc | 6 ++++-- test/util.cc | 9 ++++---- test/xchar-test.cc | 4 ++-- 11 files changed, 149 insertions(+), 81 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 6222142c..6830db22 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -291,6 +291,16 @@ # define FMT_UNICODE 1 #endif +// Specifies whether to handle built-in and string types specially. +// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher +// per-call binary size. +#ifndef FMT_BUILTIN_TYPES +# define FMT_BUILTIN_TYPES 1 +#endif +#if !FMT_BUILTIN_TYPES && !defined(__cpp_if_constexpr) +# error FMT_BUILTIN_TYPES=0 requires constexpr if support +#endif + // Check if rtti is available. #ifndef FMT_USE_RTTI // __RTTI is for EDG compilers. _CPPRTTI is for MSVC. @@ -1317,9 +1327,19 @@ template struct named_arg_info { template struct is_named_arg : std::false_type {}; template struct is_statically_named_arg : std::false_type {}; -template +template struct is_named_arg> : std::true_type {}; +template +auto unwrap_named_arg(const named_arg& arg) -> const T& { + return arg.value; +} +template >::value)> +auto unwrap_named_arg(T&& value) -> T&& { + return value; +} + template constexpr auto count() -> size_t { return B ? 1 : 0; } template constexpr auto count() -> size_t { return (B1 ? 1 : 0) + count(); @@ -1354,6 +1374,8 @@ template struct custom_value { void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); }; +enum class custom_tag {}; + // A formatting argument value. template class value { public: @@ -1400,24 +1422,26 @@ template class value { string.size = val.size(); } FMT_ALWAYS_INLINE value(const void* val) : pointer(val) {} - FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) - : named_args{args, size} {} - template FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val) { - using value_type = remove_const_t; + template + FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val, custom_tag = {}) { + using value_type = typename std::remove_cv::type; // T may overload operator& e.g. std::vector::reference in libc++. #if defined(__cpp_if_constexpr) if constexpr (std::is_same::value) custom.value = const_cast(&val); #endif if (!is_constant_evaluated()) - custom.value = const_cast(&reinterpret_cast(val)); + custom.value = + const_cast(&reinterpret_cast(val)); // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = format_custom_arg< value_type, typename Context::template formatter_type>; } + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} value(unformattable); value(unformattable_char); value(unformattable_pointer); @@ -1606,6 +1630,12 @@ using mapped_type_constant = type_constant().map(std::declval())), typename Context::char_type>; +template ::value> +using stored_type_constant = std::integral_constant< + type, Context::builtin_types || TYPE == type::int_type ? TYPE + : type::custom_type>; + enum { packed_arg_bits = 4 }; // Maximum number of arguments with packed types. enum { max_packed_args = 62 / packed_arg_bits }; @@ -1643,7 +1673,7 @@ template constexpr auto encode_types() -> unsigned long long { template constexpr auto encode_types() -> unsigned long long { - return static_cast(mapped_type_constant::value) | + return static_cast(stored_type_constant::value) | (encode_types() << packed_arg_bits); } @@ -1686,6 +1716,8 @@ FMT_CONSTEXPR auto make_arg(T& val) -> value { #if defined(__cpp_if_constexpr) if constexpr (!formattable) type_is_unformattable_for _; + if constexpr (!Context::builtin_types && !std::is_same::value) + return {unwrap_named_arg(val), custom_tag()}; #endif static_assert( formattable, @@ -1697,7 +1729,7 @@ FMT_CONSTEXPR auto make_arg(T& val) -> value { template FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg { auto arg = basic_format_arg(); - arg.type_ = mapped_type_constant::value; + arg.type_ = stored_type_constant::value; arg.value_ = make_arg(val); return arg; } @@ -1781,6 +1813,7 @@ template class basic_format_arg { friend class basic_format_args; friend class dynamic_format_arg_store; + friend class loc_value; using char_type = typename Context::char_type; @@ -1999,6 +2032,7 @@ class context { using format_arg = basic_format_arg; using parse_context_type = basic_format_parse_context; template using formatter_type = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; /// Constructs a `basic_format_context` object. References to the arguments /// are stored in the object so make sure they have appropriate lifetimes. diff --git a/include/fmt/format.h b/include/fmt/format.h index c3ffef6a..b70c766c 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1037,6 +1037,7 @@ template class generic_context { using iterator = OutputIt; using parse_context_type = basic_format_parse_context; template using formatter_type = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; constexpr generic_context(OutputIt out, basic_format_args ctx_args, @@ -1074,7 +1075,10 @@ class loc_value { public: template ::value)> - loc_value(T value) : value_(detail::make_arg(value)) {} + loc_value(T value) { + value_.type_ = detail::mapped_type_constant::value; + value_.value_ = detail::arg_mapper().map(value); + } template ::value)> loc_value(T) {} @@ -3672,6 +3676,10 @@ FMT_CONSTEXPR auto write(OutputIt out, const T& value) return formatter.format(value, ctx); } +template +using is_builtin = + bool_constant::value || FMT_BUILTIN_TYPES>; + // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. template struct default_arg_formatter { @@ -3681,7 +3689,15 @@ template struct default_arg_formatter { void operator()(monostate) { report_error("argument not found"); } - template void operator()(T value) { write(out, value); } + template ::value)> + void operator()(T value) { + write(out, value); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } void operator()(typename basic_format_arg::handle h) { // Use a null locale since the default format must be unlocalized. @@ -3699,10 +3715,17 @@ template struct arg_formatter { const format_specs& specs; locale_ref locale; - template + template ::value)> FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { return detail::write(out, value, specs, locale); } + + template ::value)> + auto operator()(T) -> iterator { + FMT_ASSERT(false, ""); + return out; + } + auto operator()(typename basic_format_arg::handle) -> iterator { // User-defined types are handled separately because they require access // to the parse context. @@ -3939,17 +3962,17 @@ FMT_FORMAT_AS(unsigned short, unsigned); FMT_FORMAT_AS(long, detail::long_type); FMT_FORMAT_AS(unsigned long, detail::ulong_type); FMT_FORMAT_AS(Char*, const Char*); -FMT_FORMAT_AS(std::nullptr_t, const void*); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); +FMT_FORMAT_AS(std::nullptr_t, const void*); FMT_FORMAT_AS(void*, const void*); +template +struct formatter : formatter, Char> {}; + template class formatter, Char> : public formatter, Char> {}; -template -struct formatter : formatter, Char> {}; - template struct formatter::is_formattable)>> @@ -4208,8 +4231,6 @@ template struct format_handler { specs.precision_ref, context); } - if (begin == end || *begin != '}') - report_error("missing '}' in format string"); arg.visit(arg_formatter{context.out(), specs, context.locale()}); return begin; } @@ -4246,7 +4267,7 @@ FMT_END_EXPORT template template -FMT_CONSTEXPR FMT_INLINE auto native_formatter::format( +FMT_CONSTEXPR auto native_formatter::format( const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { if (!specs_.dynamic()) return write(ctx.out(), val, specs_, ctx.locale()); diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 77f7e9b1..a5964b9a 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -35,6 +35,7 @@ template class basic_printf_context { using char_type = Char; using parse_context_type = basic_format_parse_context; template using formatter_type = printf_formatter; + enum { builtin_types = 1 }; /// Constructs a `printf_context` object. References to the arguments are /// stored in the context object so make sure they have appropriate lifetimes. @@ -238,19 +239,23 @@ class printf_arg_formatter : public arg_formatter { write_bytes(this->out, is_string ? "(null)" : "(nil)", s); } + template void write(T value) { + detail::write(this->out, value, this->specs, this->locale); + } + public: printf_arg_formatter(basic_appender iter, format_specs& s, context_type& ctx) : base(make_arg_formatter(iter, s)), context_(ctx) {} - void operator()(monostate value) { base::operator()(value); } + void operator()(monostate value) { write(value); } template ::value)> void operator()(T value) { // MSVC2013 fails to compile separate overloads for bool and Char so use // std::is_same instead. if (!std::is_same::value) { - base::operator()(value); + write(value); return; } format_specs s = this->specs; @@ -265,33 +270,33 @@ class printf_arg_formatter : public arg_formatter { // ignored for non-numeric types if (s.align() == align::none || s.align() == align::numeric) s.set_align(align::right); - write(this->out, static_cast(value), s); + detail::write(this->out, static_cast(value), s); } template ::value)> void operator()(T value) { - base::operator()(value); + write(value); } void operator()(const char* value) { if (value) - base::operator()(value); + write(value); else write_null_pointer(this->specs.type() != presentation_type::pointer); } void operator()(const wchar_t* value) { if (value) - base::operator()(value); + write(value); else write_null_pointer(this->specs.type() != presentation_type::pointer); } - void operator()(basic_string_view value) { base::operator()(value); } + void operator()(basic_string_view value) { write(value); } void operator()(const void* value) { if (value) - base::operator()(value); + write(value); else write_null_pointer(); } diff --git a/test/base-test.cc b/test/base-test.cc index 54c97024..edfb96bd 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -372,15 +372,19 @@ VISIT_TYPE(long, long long); VISIT_TYPE(unsigned long, unsigned long long); #endif -#define CHECK_ARG(Char, expected, value) \ - { \ - testing::StrictMock> visitor; \ - EXPECT_CALL(visitor, visit(expected)); \ - using iterator = fmt::basic_appender; \ - auto var = value; \ - fmt::detail::make_arg>(var) \ - .visit(visitor); \ - } +#if FMT_BUILTIN_TYPES +# define CHECK_ARG(Char, expected, value) \ + { \ + testing::StrictMock> visitor; \ + EXPECT_CALL(visitor, visit(expected)); \ + using iterator = fmt::basic_appender; \ + auto var = value; \ + fmt::detail::make_arg>(var) \ + .visit(visitor); \ + } +#else +# define CHECK_ARG(Char, expected, value) +#endif #define CHECK_ARG_SIMPLE(value) \ { \ @@ -391,10 +395,14 @@ VISIT_TYPE(unsigned long, unsigned long long); template class numeric_arg_test : public testing::Test {}; +#if FMT_BUILTIN_TYPES using test_types = testing::Types; +#else +using test_types = testing::Types; +#endif TYPED_TEST_SUITE(numeric_arg_test, test_types); template ::value, int> = 0> @@ -757,20 +765,12 @@ TEST(base_test, format_to_array) { EXPECT_TRUE(result.truncated); EXPECT_EQ("ABCD", fmt::string_view(buffer, 4)); - result = fmt::format_to(buffer, "{}", std::string(1000, '*')); + result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str()); EXPECT_EQ(4, std::distance(&buffer[0], result.out)); EXPECT_TRUE(result.truncated); EXPECT_EQ("****", fmt::string_view(buffer, 4)); } -#ifdef __cpp_lib_byte -TEST(base_test, format_byte) { - auto s = std::string(); - fmt::format_to(std::back_inserter(s), "{}", std::byte(42)); - EXPECT_EQ(s, "42"); -} -#endif - // Test that check is not found by ADL. template void check(T); TEST(base_test, adl_check) { diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 5befcab6..ac7cb70a 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -753,7 +753,7 @@ TEST(chrono_test, weekday) { std::locale::global(loc); auto sat = fmt::weekday(6); - + auto tm = std::tm(); tm.tm_wday = static_cast(sat.c_encoding()); @@ -795,20 +795,14 @@ TEST(chrono_test, cpp20_duration_subsecond_support) { "01.234000"); EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}), "-01.234000"); - EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}), - "12.34"); - EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}), - "12.37"); + EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}), "12.34"); + EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}), "12.37"); EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{-12375}), "-12.37"); - EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}), - "12"); - EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}), - "39.99"); - EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}), - "01.00"); - EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}), - "00.001"); + EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}), "12"); + EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}), "39.99"); + EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}), "01.00"); + EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}), "00.001"); EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000"); EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000"); EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123"); @@ -1032,7 +1026,7 @@ TEST(chrono_test, out_of_range) { TEST(chrono_test, year_month_day) { auto loc = get_locale("es_ES.UTF-8"); std::locale::global(loc); - + auto year = fmt::year(2024); auto month = fmt::month(1); auto day = fmt::day(1); diff --git a/test/format-test.cc b/test/format-test.cc index 8efdddfd..e6be9935 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -810,7 +810,7 @@ TEST(format_test, hash_flag) { EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50"); EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0."); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error, - "missing '}' in format string"); + "invalid format specifier for char"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error, "invalid format specifier for char"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error, @@ -831,7 +831,7 @@ TEST(format_test, zero_flag) { EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042"); EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error, - "missing '}' in format string"); + "invalid format specifier for char"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error, "invalid format specifier for char"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error, @@ -878,6 +878,10 @@ TEST(format_test, width) { EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0"); } +auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES + ? "width/precision is out of range" + : "width/precision is not integer"; + TEST(format_test, runtime_width) { auto int_maxer = std::to_string(INT_MAX + 1u); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0), @@ -902,16 +906,16 @@ TEST(format_test, runtime_width) { EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error, "width/precision is out of range"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error, - "width/precision is out of range"); + bad_dynamic_spec_msg); if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { long value = INT_MAX; EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); } EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error, "width/precision is not integer"); @@ -1127,16 +1131,16 @@ TEST(format_test, runtime_precision) { EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1), format_error, "width/precision is out of range"); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); if (fmt::detail::const_check(sizeof(long) > sizeof(int))) { long value = INT_MAX; EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); } EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)), - format_error, "width/precision is out of range"); + format_error, bad_dynamic_spec_msg); EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'), format_error, "width/precision is not integer"); @@ -2408,6 +2412,7 @@ namespace adl_test { template void make_format_args(const T&...) = delete; struct string : std::string {}; +auto format_as(const string& s) -> std::string { return s; } } // namespace adl_test // Test that formatting functions compile when make_format_args is found by ADL. @@ -2568,3 +2573,11 @@ TEST(format_test, bitint) { # endif } #endif + +#ifdef __cpp_lib_byte +TEST(base_test, format_byte) { + auto s = std::string(); + fmt::format_to(std::back_inserter(s), "{}", std::byte(42)); + EXPECT_EQ(s, "42"); +} +#endif diff --git a/test/os-test.cc b/test/os-test.cc index 57fe7a58..8f73935c 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -429,7 +429,7 @@ TEST(file_test, read) { } TEST(file_test, read_error) { - auto test_file = uniq_file_name(__LINE__); + auto test_file = uniq_file_name(__LINE__); file f(test_file, file::WRONLY | file::CREATE); char buf; // We intentionally read from a file opened in the write-only mode to diff --git a/test/scan-test.cc b/test/scan-test.cc index db27a5b2..03b26e5d 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -111,9 +111,9 @@ TEST(scan_test, invalid_format) { } namespace std { - using fmt::scan; - using fmt::scan_error; -} +using fmt::scan; +using fmt::scan_error; +} // namespace std TEST(scan_test, example) { // Example from https://wg21.link/p1729r3. diff --git a/test/std-test.cc b/test/std-test.cc index bcc7bd5f..42444181 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -36,8 +36,10 @@ TEST(std_test, path) { "Шчучыншчына"); EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "�"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD � TAIL"); - EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")), "HEAD \xF0\x9F\x98\x80 TAIL"); - EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")), "HEAD �\xF0\x9F\x98\x80 TAIL"); + EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")), + "HEAD \xF0\x9F\x98\x80 TAIL"); + EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")), + "HEAD �\xF0\x9F\x98\x80 TAIL"); EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\""); # endif } diff --git a/test/util.cc b/test/util.cc index a1b992b8..8ea26610 100644 --- a/test/util.cc +++ b/test/util.cc @@ -36,12 +36,11 @@ std::locale do_get_locale(const char* name) { std::locale get_locale(const char* name, const char* alt_name) { auto loc = do_get_locale(name); - if (loc == std::locale::classic() && alt_name) - loc = do_get_locale(alt_name); + if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name); #ifdef __OpenBSD__ - // Locales are not working in OpenBSD: - // https://github.com/fmtlib/fmt/issues/3670. - loc = std::locale::classic(); + // Locales are not working in OpenBSD: + // https://github.com/fmtlib/fmt/issues/3670. + loc = std::locale::classic(); #endif if (loc == std::locale::classic()) fmt::print(stderr, "{} locale is missing.\n", name); diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 312e632b..b31ea6be 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -78,7 +78,6 @@ TEST(xchar_test, format) { EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error); EXPECT_EQ(L"true", fmt::format(L"{}", true)); - EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); EXPECT_EQ(L"Cyrillic letter \x42e", fmt::format(L"Cyrillic letter {}", L'\x42e')); @@ -585,7 +584,8 @@ TEST(locale_test, sign) { TEST(std_test_xchar, complex) { auto s = fmt::format(L"{}", std::complex(1, 2)); EXPECT_EQ(s, L"(1+2i)"); - EXPECT_EQ(fmt::format(L"{:.2f}", std::complex(1, 2)), L"(1.00+2.00i)"); + EXPECT_EQ(fmt::format(L"{:.2f}", std::complex(1, 2)), + L"(1.00+2.00i)"); EXPECT_EQ(fmt::format(L"{:8}", std::complex(1, 2)), L"(1+2i) "); }