diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 22b1ec8d..c20a323a 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -17,6 +17,7 @@ #include // std::memmove #include #include +#include #ifndef FMT_STATIC_THOUSANDS_SEPARATOR # include @@ -27,8 +28,11 @@ #endif #include "format.h" +#include "locale.h" FMT_BEGIN_NAMESPACE +template std::locale::id num_format_facet::id; + namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { @@ -115,6 +119,28 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif + +template +FMT_FUNC auto write_int(unsigned long long value, locale_ref loc) + -> std::basic_string { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto&& ios = std::basic_ios(nullptr); + auto locale = loc.get(); + ios.imbue(locale); + auto&& buf = std::basic_stringbuf(); + auto out = std::ostreambuf_iterator(&buf); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet_t = conditional_t::value, + num_format_facet<>, std::num_put>; + if (std::has_facet(locale)) { + std::use_facet(locale).put(out, ios, ' ', value); + return buf.str(); + } +#endif + return {}; +} + } // namespace detail #if !FMT_MSC_VERSION diff --git a/include/fmt/format.h b/include/fmt/format.h index 7b200c4d..d96c9012 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1992,10 +1992,11 @@ template class digit_grouping { } }; +// Writes a decimal integer with digit grouping. template -auto write_int_localized(OutputIt out, UInt value, unsigned prefix, - const basic_format_specs& specs, - const digit_grouping& grouping) -> OutputIt { +auto write_int(OutputIt out, UInt value, unsigned prefix, + const basic_format_specs& specs, + const digit_grouping& grouping) -> OutputIt { static_assert(std::is_same, UInt>::value, ""); int num_digits = count_digits(value); char digits[40]; @@ -2012,12 +2013,32 @@ auto write_int_localized(OutputIt out, UInt value, unsigned prefix, }); } +template +FMT_API auto write_int(unsigned long long value, locale_ref loc) + -> std::basic_string; + template -auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs& specs, locale_ref loc) - -> bool { - auto grouping = digit_grouping(loc); - out = write_int_localized(out, value, prefix, specs, grouping); +auto write_int(OutputIt& out, UInt value, unsigned prefix, + const basic_format_specs& specs, locale_ref loc) -> bool { + using char_t = + conditional_t::value, wchar_t, char>; + auto str = std::basic_string(); + if (sizeof(value) <= sizeof(unsigned long long)) + str = write_int(static_cast(value), loc); + if (str.empty()) { + auto grouping = digit_grouping(loc); + out = write_int(out, value, prefix, specs, grouping); + return true; + } + size_t size = to_unsigned((prefix != 0 ? 1 : 0) + str.size()); + out = write_padded( + out, specs, size, size, [&](reserve_iterator it) { + if (prefix != 0) { + char sign = static_cast(prefix); + *it++ = static_cast(sign); + } + return copy_str(str.data(), str.data() + str.size(), it); + }); return true; } @@ -2058,10 +2079,9 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, case presentation_type::none: case presentation_type::dec: { if (specs.localized && - write_int_localized(out, static_cast>(abs_value), - prefix, specs, loc)) { + write_int(out, static_cast>(abs_value), prefix, + specs, loc)) return out; - } auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { @@ -3914,7 +3934,7 @@ template struct formatter> : formatter { specs_.width_ref, ctx); detail::handle_dynamic_spec( specs_.precision, specs_.precision_ref, ctx); - return detail::write_int_localized( + return detail::write_int( ctx.out(), static_cast>(t.value), 0, specs_, detail::digit_grouping({"\3", ','})); } diff --git a/include/fmt/locale.h b/include/fmt/locale.h new file mode 100644 index 00000000..1b22fb47 --- /dev/null +++ b/include/fmt/locale.h @@ -0,0 +1,26 @@ +// Formatting library for C++ - optional locale support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_LOCALE_H_ +#define FMT_LOCALE_H_ + +#include // std::num_put + +#include "core.h" + +FMT_BEGIN_NAMESPACE + +// A locale facet that formats numeric values in UTF-8. +template +class num_format_facet : public std::num_put { + public: + static FMT_API std::locale::id id; +}; + +FMT_END_NAMESPACE + +#endif // FMT_LOCALE_H_ \ No newline at end of file diff --git a/src/format.cc b/src/format.cc index 99b7e9dd..5d8d97bd 100644 --- a/src/format.cc +++ b/src/format.cc @@ -22,6 +22,9 @@ template FMT_API auto locale_ref::get() const -> std::locale; // Explicit instantiations for char. +template FMT_API auto write_int(unsigned long long, locale_ref) + -> std::basic_string; + template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; template FMT_API auto decimal_point_impl(locale_ref) -> char; @@ -37,6 +40,9 @@ template FMT_API void vformat_to(buffer&, string_view, // Explicit instantiations for wchar_t. +template FMT_API auto write_int(unsigned long long, locale_ref) + -> std::basic_string; + template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result; template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 290042a6..a47a86be 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -7,12 +7,14 @@ #include "fmt/xchar.h" +#include #include #include #include #include "fmt/chrono.h" #include "fmt/color.h" +#include "fmt/locale.h" #include "fmt/ostream.h" #include "fmt/ranges.h" #include "gtest-extra.h" // Contains @@ -344,6 +346,7 @@ TEST(xchar_test, escape_string) { TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); } #ifndef FMT_STATIC_THOUSANDS_SEPARATOR + template struct numpunct : std::numpunct { protected: Char do_decimal_point() const override { return '?'; } @@ -441,7 +444,7 @@ TEST(locale_test, wformat) { fmt::format(small_grouping_loc, L"{:L}", max_value())); } -TEST(locale_test, double_formatter) { +TEST(locale_test, int_formatter) { auto loc = std::locale(std::locale(), new special_grouping()); auto f = fmt::formatter(); auto parse_ctx = fmt::format_parse_context("L"); @@ -518,4 +521,22 @@ TEST(locale_test, sign) { EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50"); } +class num_format : public fmt::num_format_facet<> { + protected: + using fmt::num_format_facet<>::do_put; + iter_type do_put(iter_type out, std::ios_base&, char, + unsigned long long) const override; +}; + +num_format::iter_type num_format::do_put(iter_type out, std::ios_base&, char, + unsigned long long) const { + const char s[] = "foo"; + return std::copy_n(s, sizeof(s) - 1, out); +} + +TEST(locale_test, num_format) { + auto loc = std::locale(std::locale(), new num_format()); + EXPECT_EQ(fmt::format(loc, "{:L}", 42), "foo"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR