diff --git a/include/fmt/base.h b/include/fmt/base.h index 5c63948b..fb905183 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -301,6 +301,8 @@ template struct type_identity { }; template using type_identity_t = typename type_identity::type; template +using make_unsigned_t = typename std::make_unsigned::type; +template using underlying_t = typename std::underlying_type::type; #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 @@ -399,10 +401,9 @@ template auto convert_for_visit(T) -> monostate { return {}; } // Casts a nonnegative integer to unsigned. template -FMT_CONSTEXPR auto to_unsigned(Int value) -> - typename std::make_unsigned::type { +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); - return static_cast::type>(value); + return static_cast>(value); } // A heuristic to detect std::string and std::[experimental::]string_view. @@ -2029,27 +2030,52 @@ using sign_t = sign::type; namespace detail { -// Workaround an array initialization issue in gcc 4.8. -template struct fill_t { +template +using unsigned_char = typename conditional_t::value, + std::make_unsigned, + type_identity>::type; + +struct fill_t { private: enum { max_size = 4 }; - Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; + char data_[max_size] = {' '}; unsigned char size_ = 1; public: + template FMT_CONSTEXPR void operator=(basic_string_view s) { auto size = s.size(); - FMT_ASSERT(size <= max_size, "invalid fill"); - for (size_t i = 0; i < size; ++i) data_[i] = s[i]; size_ = static_cast(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + data_[0] = static_cast(uchar); + data_[1] = static_cast(uchar >> 8); + return; + } + FMT_ASSERT(size <= max_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) data_[i] = static_cast(s[i]); + } + + FMT_CONSTEXPR void operator=(char c) { + data_[0] = c; + size_ = 1; } constexpr auto size() const -> size_t { return size_; } - constexpr auto data() const -> const Char* { return data_; } - FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } - FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { - return data_[index]; + template constexpr auto get() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(data_[0]) | + (static_cast(data_[1]) << 8)); + } + + template ::value)> + constexpr auto data() const -> const Char* { + return data_; + } + template ::value)> + constexpr auto data() const -> const Char* { + return nullptr; } }; } // namespace detail @@ -2087,7 +2113,7 @@ template struct format_specs { bool upper : 1; // An uppercase version e.g. 'X' for 'x'. bool alt : 1; // Alternate form ('#'). bool localized : 1; - detail::fill_t fill; + detail::fill_t fill; constexpr format_specs() : width(0), @@ -2389,7 +2415,7 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, if (specs.align == align::none) { // Ignore 0 if align is specified for compatibility with std::format. specs.align = align::numeric; - specs.fill[0] = Char('0'); + specs.fill = '0'; } ++begin; break; @@ -2480,7 +2506,8 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, } auto align = parse_align(to_ascii(*fill_end)); enter_state(state::align, align != align::none); - specs.fill = {begin, to_unsigned(fill_end - begin)}; + specs.fill = + basic_string_view(begin, to_unsigned(fill_end - begin)); specs.align = align; begin = fill_end + 1; } diff --git a/include/fmt/format.h b/include/fmt/format.h index 12f5ba09..e17a3b93 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1711,13 +1711,14 @@ constexpr auto convert_float(T value) -> convert_float_result { return static_cast>(value); } -template -FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, - const fill_t& fill) -> OutputIt { +template +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t& fill) + -> OutputIt { auto fill_size = fill.size(); - if (fill_size == 1) return detail::fill_n(it, n, fill[0]); - auto data = fill.data(); - for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + if (fill_size == 1) return detail::fill_n(it, n, fill.template get()); + if (const Char* data = fill.template data()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } return it; } @@ -1737,9 +1738,9 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, size_t left_padding = padding >> shifts[specs.align]; size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); - if (left_padding != 0) it = fill(it, left_padding, specs.fill); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); - if (right_padding != 0) it = fill(it, right_padding, specs.fill); + if (right_padding != 0) it = fill(it, right_padding, specs.fill); return base_iterator(out, it); } @@ -1788,17 +1789,11 @@ template struct find_escape_result { uint32_t cp; }; -template -using make_unsigned_char = - typename conditional_t::value, - std::make_unsigned, - type_identity>::type; - template auto find_escape(const Char* begin, const Char* end) -> find_escape_result { for (; begin != end; ++begin) { - uint32_t cp = static_cast>(*begin); + uint32_t cp = static_cast>(*begin); if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; if (needs_escape(cp)) return {begin, begin + 1, cp}; } @@ -2385,7 +2380,7 @@ FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, report_error("invalid fill character '{'"); return begin; } - specs.fill = {begin, to_unsigned(p - begin)}; + specs.fill = basic_string_view(begin, to_unsigned(p - begin)); begin = p + 1; } else { ++begin; @@ -2464,8 +2459,8 @@ FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, auto size = str_size + (sign ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = - specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); - if (is_zero_fill) specs.fill[0] = static_cast(' '); + specs.fill.size() == 1 && specs.fill.template get() == '0'; + if (is_zero_fill) specs.fill = ' '; return write_padded(out, specs, size, [=](reserve_iterator it) { if (sign) *it++ = detail::sign(sign); return copy(str, str + str_size, it); @@ -4163,7 +4158,7 @@ template struct formatter> { template struct nested_formatter { private: int width_; - detail::fill_t fill_; + detail::fill_t fill_; align_t align_ : 4; formatter formatter_; diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 30f41404..aca56ebb 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -262,7 +262,7 @@ class printf_arg_formatter : public arg_formatter { } fmt_specs.sign = sign::none; fmt_specs.alt = false; - fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. + fmt_specs.fill = ' '; // Ignore '0' flag for char types. // align::numeric needs to be overwritten here since the '0' flag is // ignored for non-numeric types if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) @@ -319,7 +319,7 @@ void parse_flags(format_specs& specs, const Char*& it, const Char* end) { specs.sign = sign::plus; break; case '0': - specs.fill[0] = '0'; + specs.fill = '0'; break; case ' ': if (specs.sign != sign::plus) specs.sign = sign::space; @@ -346,7 +346,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs, ++it; arg_index = value != -1 ? value : max_value(); } else { - if (c == '0') specs.fill[0] = '0'; + if (c == '0') specs.fill = '0'; if (value != 0) { // Nonzero value means that we parsed width and don't need to // parse it or flags again, so return now. @@ -477,7 +477,7 @@ void vprintf(buffer& buf, basic_string_view format, // specified, the '0' flag is ignored if (specs.precision >= 0 && arg.is_integral()) { // Ignore '0' for non-numeric types or if '-' present. - specs.fill[0] = ' '; + specs.fill = ' '; } if (specs.precision >= 0 && arg.type() == type::cstring_type) { auto str = arg.visit(get_cstring()); @@ -488,12 +488,12 @@ void vprintf(buffer& buf, basic_string_view format, arg = make_arg>(sv); } if (specs.alt && arg.visit(is_zero_int())) specs.alt = false; - if (specs.fill[0] == '0') { + if (specs.fill.template get() == '0') { if (arg.is_arithmetic() && specs.align != align::left) specs.align = align::numeric; else - specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-' - // flag is also present. + specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-' + // flag is also present. } // Parse length and convert the argument to the required type. diff --git a/test/base-test.cc b/test/base-test.cc index 98d5b6e0..f7365637 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -504,7 +504,7 @@ template constexpr auto parse_test_specs(const char (&s)[N]) { TEST(core_test, constexpr_parse_format_specs) { static_assert(parse_test_specs("<").align == fmt::align::left, ""); - static_assert(parse_test_specs("*^").fill[0] == '*', ""); + static_assert(parse_test_specs("*^").fill.get() == '*', ""); static_assert(parse_test_specs("+").sign == fmt::sign::plus, ""); static_assert(parse_test_specs("-").sign == fmt::sign::minus, ""); static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");