diff --git a/include/fmt/core.h b/include/fmt/core.h index 9dfcd2d6..42835c0e 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -69,9 +69,7 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ - FMT_MSC_VERSION > 1900) && \ - !defined(__INTELLISENSE__) +#if defined(__has_include) || FMT_ICC_VERSION >= 1600 || FMT_MSC_VERSION > 1900 # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -332,7 +330,7 @@ struct monostate { #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else -# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif FMT_BEGIN_DETAIL_NAMESPACE @@ -486,6 +484,18 @@ template class basic_string_view { size_ -= n; } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( + basic_string_view sv) const noexcept { + return size_ >= sv.size_ && + std::char_traits::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { + return size_ >= 1 && std::char_traits::eq(*data_, c); + } + FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { + return starts_with(basic_string_view(s)); + } + // Lexicographically compare this string reference to other. FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 6fbdb52a..8721888d 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -29,8 +29,6 @@ #include "format.h" FMT_BEGIN_NAMESPACE -template typename Locale::id num_format_facet::id; - namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { @@ -118,22 +116,40 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { } #endif -FMT_FUNC auto write_int(appender out, loc_value value, +FMT_FUNC auto write_loc(appender out, basic_format_arg value, const format_specs& specs, locale_ref loc) -> bool { #ifndef FMT_STATIC_THOUSANDS_SEPARATOR auto locale = loc.get(); // We cannot use the num_put facet because it may produce output in // a wrong encoding. - if (!std::has_facet>(locale)) return {}; - std::use_facet>(locale).put(out, value, specs, - locale); - return true; + using facet = format_facet; + if (std::has_facet(locale)) + return std::use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); #endif return false; } - } // namespace detail +template typename Locale::id format_facet::id; + +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template format_facet::format_facet(Locale& loc) { + auto& numpunct = std::use_facet>(loc); + grouping_ = numpunct.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); +} + +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, basic_format_arg val, + const format_specs& specs) const -> bool { + return visit_format_arg( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}, + val); +} +#endif + #if !FMT_MSC_VERSION FMT_API FMT_FUNC format_error::~format_error() noexcept = default; #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index 4aff0e93..e9861365 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -33,13 +33,14 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include // std::signbit -#include // uint32_t -#include // std::memcpy -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error +#include // std::signbit +#include // uint32_t +#include // std::memcpy +#include // std::initializer_list +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error #ifdef __cpp_lib_bit_cast # include // std::bitcast @@ -738,6 +739,21 @@ inline auto code_point_index(basic_string_view s, size_t n) string_view(reinterpret_cast(s.data()), s.size()), n); } +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + #ifndef FMT_USE_FLOAT128 # ifdef __SIZEOF_FLOAT128__ # define FMT_USE_FLOAT128 1 @@ -985,40 +1001,37 @@ constexpr auto compile_string_to_view(detail::std_string_view s) } } // namespace detail_exported -// A value to localize. -struct loc_value { - union { - unsigned long long ulong_long_value; - }; -}; +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, basic_format_arg val, + const format_specs& specs) const -> bool; -// A locale facet that formats numeric values in UTF-8. -// It is parameterized on the locale to avoid heavy include. -template class num_format_facet : public Locale::facet { public: static FMT_API typename Locale::id id; - void put(appender out, loc_value val, const format_specs& specs, - Locale& loc) const { - do_put(out, val, specs, loc); - } + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", + std::initializer_list g = {3}, + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(g.begin(), g.end()), + decimal_point_(decimal_point) {} - protected: - virtual void do_put(appender out, loc_value val, const format_specs& specs, - Locale& loc) const = 0; + auto put(appender out, basic_format_arg val, + const format_specs& specs) const -> bool { + return do_put(out, val, specs); + } }; FMT_BEGIN_DETAIL_NAMESPACE -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; - // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> @@ -1956,19 +1969,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template class digit_grouping { private: - thousands_sep_result sep_; + std::string grouping_; + std::basic_string thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + next_state initial_state() const { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. int next(next_state& state) const { - if (!sep_.thousands_sep) return max_value(); - if (state.group == sep_.grouping.end()) - return state.pos += sep_.grouping.back(); + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; @@ -1977,14 +1990,15 @@ template class digit_grouping { public: explicit digit_grouping(locale_ref loc, bool localized = true) { - if (localized) - sep_ = thousands_sep(loc); - else - sep_.thousands_sep = Char(); + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - Char separator() const { return sep_.thousands_sep; } + bool has_separator() const { return !thousands_sep_.empty(); } int count_separators(int num_digits) const { int count = 0; @@ -2007,7 +2021,9 @@ template class digit_grouping { for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { - *out++ = separator(); + out = + copy_str(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); @@ -2037,37 +2053,14 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, }); } -FMT_API auto write_int(appender out, loc_value value, const format_specs& specs, - locale_ref loc) -> bool; -template -inline auto write_int(OutputIt, loc_value, const basic_format_specs&, - locale_ref) -> bool { - return false; -} +// Writes localized value. +FMT_API auto write_loc(appender out, basic_format_arg value, + const format_specs& specs, locale_ref loc) -> bool; -template -auto write_int(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs& specs, locale_ref loc) -> bool { - auto result = false; - auto buf = memory_buffer(); - if (sizeof(value) <= sizeof(unsigned long long)) - result = write_int(appender(buf), {static_cast(value)}, - specs, loc); - if (!result) { - auto grouping = digit_grouping(loc); - out = write_int(out, value, prefix, specs, grouping); - return true; - } - size_t size = to_unsigned((prefix != 0 ? 1 : 0) + buf.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(buf.data(), buf.data() + buf.size(), it); - }); - return true; +template +inline auto write_loc(OutputIt, basic_format_arg>, + const basic_format_specs&, locale_ref) -> bool { + return false; } FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { @@ -2096,20 +2089,39 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) return {abs_value, prefix}; } +template struct loc_writer { + buffer_appender out; + const basic_format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } + + auto operator()(...) -> bool { return false; } +}; + template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const basic_format_specs& specs, - locale_ref loc) -> OutputIt { + locale_ref) -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type) { case presentation_type::none: case presentation_type::dec: { - if (specs.localized && - 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) { @@ -2169,6 +2181,10 @@ template & specs, locale_ref loc) -> OutputIt { + if (specs.localized && + write_loc(out, make_arg>(value), specs, loc)) { + return out; + } return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2180,6 +2196,10 @@ template & specs, locale_ref loc) -> OutputIt { + if (specs.localized && + write_loc(out, make_arg>(value), specs, loc)) { + return out; + } return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2331,7 +2351,7 @@ template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } @@ -2393,7 +2413,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } @@ -2515,7 +2535,7 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr Char separator() const { return Char(); } + constexpr bool has_separator() const { return false; } constexpr int count_separators(int) const { return 0; } @@ -3460,12 +3480,6 @@ template struct custom_formatter { template void operator()(T) const {} }; -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - template class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} @@ -3963,7 +3977,7 @@ template struct formatter> : formatter { specs_.precision, specs_.precision_ref, ctx); return detail::write_int( ctx.out(), static_cast>(t.value), 0, specs_, - detail::digit_grouping({"\3", ','})); + detail::digit_grouping("\3", ",")); } }; diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index dea7d60d..2105a668 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -211,16 +211,16 @@ class is_tuple_formattable_ { static constexpr const bool value = false; }; template class is_tuple_formattable_ { - template - static std::true_type check2(index_sequence, - integer_sequence); + template + static std::true_type check2(index_sequence, + integer_sequence); static std::false_type check2(...); - template + template static decltype(check2( - index_sequence{}, + index_sequence{}, integer_sequence< - bool, (is_formattable::type, - C>::value)...>{})) check(index_sequence); + bool, (is_formattable::type, + C>::value)...>{})) check(index_sequence); public: static constexpr const bool value = diff --git a/include/fmt/std.h b/include/fmt/std.h index c8b042a4..63dbebe3 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -101,19 +101,16 @@ template using variant_index_sequence = std::make_index_sequence::value>; -// variant_size and variant_alternative check. -template -struct is_variant_like_ : std::false_type {}; -template -struct is_variant_like_::value)>> - : std::true_type {}; +template struct is_variant_like_ : std::false_type {}; +template +struct is_variant_like_> : std::true_type {}; -// formattable element check +// formattable element check. template class is_variant_formattable_ { - template + template static std::conjunction< - is_formattable, C>...> - check(std::index_sequence); + is_formattable, C>...> + check(std::index_sequence); public: static constexpr const bool value = diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 3b5bc15c..5fb5c815 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -12,11 +12,32 @@ #include "format.h" +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +# include +#endif + FMT_BEGIN_NAMESPACE namespace detail { + template using is_exotic_char = bool_constant::value>; + +template +auto write_loc(OutputIt out, basic_format_arg> val, + const basic_format_specs& specs, locale_ref loc) + -> bool { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + auto& numpunct = + std::use_facet>(loc.get()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return visit_format_arg( + loc_writer{out, specs, separator, grouping, {}}, val); +#endif + return false; } +} // namespace detail FMT_MODULE_EXPORT_BEGIN diff --git a/test/core-test.cc b/test/core-test.cc index c76dc161..6d1309fe 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -9,9 +9,7 @@ #include "test-assert.h" // clang-format on -#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I #include "fmt/core.h" -#undef I #include // std::copy_n #include // INT_MAX @@ -72,6 +70,16 @@ TEST(string_view_test, compare) { EXPECT_LT(string_view("foo").compare(string_view("fop")), 0); EXPECT_GT(string_view("foo").compare(string_view("fo")), 0); EXPECT_LT(string_view("fo").compare(string_view("foo")), 0); + + EXPECT_TRUE(string_view("foo").starts_with('f')); + EXPECT_FALSE(string_view("foo").starts_with('o')); + EXPECT_FALSE(string_view().starts_with('o')); + + EXPECT_TRUE(string_view("foo").starts_with("fo")); + EXPECT_TRUE(string_view("foo").starts_with("foo")); + EXPECT_FALSE(string_view("foo").starts_with("fooo")); + EXPECT_FALSE(string_view().starts_with("fooo")); + check_op(); check_op(); check_op(); diff --git a/test/enforce-checks-test.cc b/test/enforce-checks-test.cc index c77cb142..960a7fcd 100644 --- a/test/enforce-checks-test.cc +++ b/test/enforce-checks-test.cc @@ -8,12 +8,14 @@ #include #include +#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I #include "fmt/chrono.h" #include "fmt/color.h" #include "fmt/format.h" #include "fmt/ostream.h" #include "fmt/ranges.h" #include "fmt/xchar.h" +#undef I // Exercise the API to verify that everything we expect to can compile. void test_format_api() { diff --git a/test/format-test.cc b/test/format-test.cc index b028aefd..ee722ab3 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2312,25 +2312,57 @@ TEST(format_int_test, format_int) { EXPECT_EQ(os.str(), fmt::format_int(max_value()).str()); } -#ifdef FMT_STATIC_THOUSANDS_SEPARATOR +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR # include -class num_format : public fmt::num_format_facet { +class format_facet : public fmt::format_facet { protected: - void do_put(fmt::appender out, fmt::loc_value, const fmt::format_specs&, - std::locale&) const override; + struct int_formatter { + fmt::appender out; + + template ::value)> + auto operator()(T value) -> bool { + fmt::format_to(out, "[{}]", value); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } + }; + + auto do_put(fmt::appender out, fmt::basic_format_arg arg, + const fmt::format_specs&) const -> bool override; }; -void num_format::do_put(fmt::appender out, fmt::loc_value value, - const fmt::format_specs&, std::locale&) const { - fmt::format_to(out, "[{}]", value.ulong_long_value); +auto format_facet::do_put(fmt::appender out, + fmt::basic_format_arg arg, + const fmt::format_specs&) const -> bool { + return visit_format_arg(int_formatter{out}, arg); } -TEST(format_test, num_format) { - auto loc = std::locale(std::locale(), new num_format()); +TEST(format_test, format_facet) { + auto loc = std::locale(std::locale(), new format_facet()); EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]"); EXPECT_EQ(fmt::format(loc, "{:L}", -42), "[-42]"); } +TEST(format_test, format_facet_separator) { + // U+2019 RIGHT SINGLE QUOTATION MARK is a digit separator in the de_CH + // locale. + auto loc = + std::locale({}, new fmt::format_facet("\xe2\x80\x99")); + EXPECT_EQ(fmt::format(loc, "{:L}", 1000), + "1\xe2\x80\x99" + "000"); +} + +TEST(format_test, format_facet_grouping) { + auto loc = + std::locale({}, new fmt::format_facet(",", {1, 2, 3})); + EXPECT_EQ(fmt::format(loc, "{:L}", 1234567890), "1,234,567,89,0"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR diff --git a/test/std-test.cc b/test/std-test.cc index b9c15e02..77cf7934 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -75,6 +75,9 @@ TEST(std_test, variant) { EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)"); EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")"); + + volatile int i = 42; // Test compile error before GCC 11 described in #3068. + EXPECT_EQ(fmt::format("{}", i), "42"); #endif } diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 3eec7384..dd45826d 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -448,11 +448,11 @@ TEST(locale_test, int_formatter) { auto f = fmt::formatter(); auto parse_ctx = fmt::format_parse_context("L"); f.parse(parse_ctx); - char buf[10] = {}; - fmt::basic_format_context format_ctx( - buf, {}, fmt::detail::locale_ref(loc)); - *f.format(12345, format_ctx) = 0; - EXPECT_STREQ("12,345", buf); + auto buf = fmt::memory_buffer(); + fmt::basic_format_context format_ctx( + fmt::appender(buf), {}, fmt::detail::locale_ref(loc)); + f.format(12345, format_ctx); + EXPECT_EQ(fmt::to_string(buf), "12,345"); } FMT_BEGIN_NAMESPACE