diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 0ce654c4..49774099 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -25,6 +25,85 @@ FMT_BEGIN_NAMESPACE namespace detail { +template class truncating_iterator_base { + protected: + OutputIt out_; + size_t limit_; + size_t count_ = 0; + + truncating_iterator_base() : out_(), limit_(0) {} + + truncating_iterator_base(OutputIt out, size_t limit) + : out_(out), limit_(limit) {} + + public: + using iterator_category = std::output_iterator_tag; + using value_type = typename std::iterator_traits::value_type; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + using _Unchecked_type = + truncating_iterator_base; // Mark iterator as checked. + + OutputIt base() const { return out_; } + size_t count() const { return count_; } +}; + +// An output iterator that truncates the output and counts the number of objects +// written to it. +template ::value_type>::type> +class truncating_iterator; + +template +class truncating_iterator + : public truncating_iterator_base { + mutable typename truncating_iterator_base::value_type blackhole_; + + public: + using value_type = typename truncating_iterator_base::value_type; + + truncating_iterator() = default; + + truncating_iterator(OutputIt out, size_t limit) + : truncating_iterator_base(out, limit) {} + + truncating_iterator& operator++() { + if (this->count_++ < this->limit_) ++this->out_; + return *this; + } + + truncating_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + value_type& operator*() const { + return this->count_ < this->limit_ ? *this->out_ : blackhole_; + } +}; + +template +class truncating_iterator + : public truncating_iterator_base { + public: + truncating_iterator() = default; + + truncating_iterator(OutputIt out, size_t limit) + : truncating_iterator_base(out, limit) {} + + template truncating_iterator& operator=(T val) { + if (this->count_++ < this->limit_) *this->out_++ = val; + return *this; + } + + truncating_iterator& operator++() { return *this; } + truncating_iterator& operator++(int) { return *this; } + truncating_iterator& operator*() { return *this; } +}; + // A compile-time string which is compiled into fast formatting code. class compiled_string {}; @@ -223,9 +302,13 @@ template void format_arg( basic_format_parse_context& parse_ctx, Context& ctx, Id arg_id) { - ctx.advance_to(visit_format_arg( - arg_formatter(ctx, &parse_ctx), - ctx.arg(arg_id))); + auto arg = ctx.arg(arg_id); + if (arg.type() == type::custom_type) { + visit_format_arg(custom_formatter(parse_ctx, ctx), arg); + } else { + ctx.advance_to(visit_format_arg( + arg_formatter(ctx), arg)); + } } // vformat_to is defined in a subnamespace to prevent ADL. @@ -287,7 +370,7 @@ auto vformat_to(OutputIt out, CompiledFormat& cf, advance_to(parse_ctx, part.arg_id_end); ctx.advance_to( visit_format_arg(arg_formatter( - ctx, nullptr, &specs), + ctx, &specs), arg)); break; } @@ -849,7 +932,10 @@ format_to_n_result format_to_n(OutputIt out, size_t n, const S&, return {it.base(), it.count()}; } -template +template ::value || + detail::is_compiled_string::value)> size_t formatted_size(const CompiledFormat& cf, const Args&... args) { return format_to(detail::counting_iterator(), cf, args...).count(); } diff --git a/include/fmt/core.h b/include/fmt/core.h index 7b2b2a43..68cc7964 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1865,11 +1865,11 @@ inline auto format_to_n(OutputIt out, size_t n, const S& format_str, Returns the number of characters in the output of ``format(format_str, args...)``. */ -template -inline size_t formatted_size(string_view format_str, Args&&... args) { +template > +inline size_t formatted_size(const S& format_str, Args&&... args) { const auto& vargs = fmt::make_args_checked(format_str, args...); detail::counting_buffer<> buf; - detail::vformat_to(buf, format_str, vargs); + detail::vformat_to(buf, to_string_view(format_str), vargs); return buf.count(); } diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index d4bd6afd..6722fe8c 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1066,6 +1066,7 @@ template const char basic_data::signs[] = {0, '-', '+', ' '}; #if __cplusplus < 201703L template constexpr const char basic_data::hex_digits[]; +template constexpr const unsigned basic_data::prefixes[]; template constexpr const char basic_data::left_padding_shifts[]; template constexpr const char basic_data::right_padding_shifts[]; diff --git a/include/fmt/format.h b/include/fmt/format.h index d7bb9b0a..9874d5ca 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -121,8 +121,8 @@ FMT_END_NAMESPACE # define FMT_THROW(x) throw x # endif # else -# define FMT_THROW(x) \ - do { \ +# define FMT_THROW(x) \ + do { \ FMT_ASSERT(false, (x).what()); \ } while (false) # endif @@ -174,9 +174,9 @@ FMT_END_NAMESPACE #endif // Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of -// int_writer template instances to just one by only using the largest integer -// type. This results in a reduction in binary size but will cause a decrease in -// integer formatting performance. +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. #if !defined(FMT_REDUCE_INT_INSTANTIATIONS) # define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif @@ -402,6 +402,10 @@ template constexpr Iterator& reserve(Iterator& it, size_t) { return it; } +template +using reserve_iterator = + remove_reference_t(), 0))>; + template constexpr T* to_pointer(OutputIt, size_t) { return nullptr; @@ -465,85 +469,6 @@ class counting_iterator { value_type operator*() const { return {}; } }; -template class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_ = 0; - - truncating_iterator_base() : out_(), limit_(0) {} - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - using _Unchecked_type = - truncating_iterator_base; // Mark iterator as checked. - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - -template -class truncating_iterator - : public truncating_iterator_base { - mutable typename truncating_iterator_base::value_type blackhole_; - - public: - using value_type = typename truncating_iterator_base::value_type; - - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template -class truncating_iterator - : public truncating_iterator_base { - public: - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - template truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; - // is spectacularly slow to compile in C++20 so use a simple fill_n // instead (#1998). template @@ -977,6 +902,8 @@ using uint32_or_64_or_128_t = conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, uint32_t, conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; // 128-bit integer type used internally struct FMT_EXTERN_TEMPLATE_API uint128_wrapper { @@ -1055,8 +982,10 @@ template struct FMT_EXTERN_TEMPLATE_API basic_data { static const char reset_color[5]; static const wchar_t wreset_color[5]; static const char signs[]; - static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1, 0}; - static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1, 0}; + static constexpr const unsigned prefixes[] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0}; + static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0}; // DEPRECATED! These are for ABI compatibility. static const uint32_t zero_or_powers_of_10_32[]; @@ -1095,44 +1024,33 @@ template FMT_CONSTEXPR int count_digits_fallback(T n) { count += 4; } } +#if FMT_USE_INT128 +FMT_CONSTEXPR inline int count_digits(uint128_t n) { + return count_digits_fallback(n); +} +#endif -#ifdef FMT_BUILTIN_CLZLL // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline int count_digits(uint64_t n) { if (is_constant_evaluated()) { return count_digits_fallback(n); } +#ifdef FMT_BUILTIN_CLZLL // https://github.com/fmtlib/format-benchmark/blob/master/digits10 auto t = bsr2log10(FMT_BUILTIN_CLZLL(n | 1) ^ 63); return t - (n < data::zero_or_powers_of_10_64_new[t]); -} #else -// Fallback version of count_digits used when __builtin_clz is not available. -FMT_CONSTEXPR inline int count_digits(uint64_t n) { return count_digits_fallback(n); -} #endif - -#if FMT_USE_INT128 -FMT_CONSTEXPR inline int count_digits(uint128_t n) { - int count = 1; - for (;;) { - // Integer division is slow so do it for a group of four digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; - n /= 10000U; - count += 4; - } } -#endif // Counts the number of digits in n. BITS = log2(radix). -template FMT_CONSTEXPR int count_digits(UInt n) { +template FMT_CONSTEXPR int count_digits(UInt n) { +#ifdef FMT_BUILTIN_CLZ + if (num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; +#endif int num_digits = 0; do { ++num_digits; @@ -1702,14 +1620,14 @@ FMT_CONSTEXPR OutputIt write_padded(OutputIt out, static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; - size_t left_padding = 0; auto* shifts = align == align::left ? data::left_padding_shifts : data::right_padding_shifts; - left_padding = padding >> shifts[specs.align]; + size_t left_padding = padding >> shifts[specs.align]; + size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); - it = fill(it, left_padding, specs.fill); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); - it = fill(it, padding - left_padding, specs.fill); + if (right_padding != 0) it = fill(it, padding - left_padding, specs.fill); return base_iterator(out, it); } @@ -1724,18 +1642,17 @@ constexpr OutputIt write_padded(OutputIt out, template OutputIt write_bytes(OutputIt out, string_view bytes, const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, bytes.size(), [bytes](iterator it) { - const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); - }); + return write_padded(out, specs, bytes.size(), + [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); } template constexpr OutputIt write_char(OutputIt out, Char value, const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, 1, [=](iterator it) { + return write_padded(out, specs, 1, [=](reserve_iterator it) { *it++ = value; return it; }); @@ -1747,9 +1664,9 @@ template struct write_int_data { size_t size; size_t padding; - FMT_CONSTEXPR write_int_data(int num_digits, string_view prefix, + FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, const basic_format_specs& specs) - : size(prefix.size() + to_unsigned(num_digits)), padding(0) { + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { @@ -1757,7 +1674,7 @@ template struct write_int_data { size = width; } } else if (specs.precision > num_digits) { - size = prefix.size() + to_unsigned(specs.precision); + size = (prefix >> 24) + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); } } @@ -1765,22 +1682,164 @@ template struct write_int_data { // Writes an integer in the format // -// where are written by f(it). -template -FMT_CONSTEXPR OutputIt write_int(OutputIt out, int num_digits, - string_view prefix, - const basic_format_specs& specs, F f) { +// where are written by write_digits(it). +// prefix contains chars in three lower bytes and the size in the fourth byte. +template +FMT_CONSTEXPR FMT_INLINE OutputIt +write_int(OutputIt out, int num_digits, unsigned prefix, + const basic_format_specs& specs, W write_digits) { + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + if (prefix != 0) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + } + return base_iterator(out, write_digits(it)); + } auto data = write_int_data(num_digits, prefix, specs); - using iterator = remove_reference_t; - return write_padded(out, specs, data.size, [=](iterator it) { - if (prefix.size() != 0) - it = copy_str(prefix.begin(), prefix.end(), it); - it = detail::fill_n(it, data.padding, static_cast('0')); - return f(it); - }); + return write_padded( + out, specs, data.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, data.padding, static_cast('0')); + return write_digits(it); + }); } -template +template +FMT_CONSTEXPR OutputIt write_dec(OutputIt out, UInt value, unsigned prefix, + const basic_format_specs& specs) { + auto num_digits = count_digits(value); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_decimal(it, value, num_digits).end; + }); +} + +template +OutputIt write_int_localized(OutputIt out, UInt value, unsigned prefix, + const basic_format_specs& specs, + locale_ref loc) { + static_assert(std::is_same, UInt>::value, ""); + const auto sep_size = 1; + std::string groups = grouping(loc); + if (groups.empty()) return write_dec(out, value, prefix, specs); + auto sep = thousands_sep(loc); + if (!sep) return write_dec(out, value, prefix, specs); + int num_digits = count_digits(value); + int size = num_digits, n = num_digits; + std::string::const_iterator group = groups.cbegin(); + while (group != groups.cend() && n > *group && *group > 0 && + *group != max_value()) { + size += sep_size; + n -= *group; + ++group; + } + if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); + char digits[40]; + format_decimal(digits, value, num_digits); + basic_memory_buffer buffer; + if (prefix != 0) ++size; + const auto usize = to_unsigned(size); + buffer.resize(usize); + basic_string_view s(&sep, sep_size); + // Index of a decimal digit with the least significant digit having index 0. + int digit_index = 0; + group = groups.cbegin(); + auto p = buffer.data() + size - 1; + for (int i = num_digits - 1; i > 0; --i) { + *p-- = static_cast(digits[i]); + if (*group <= 0 || ++digit_index % *group != 0 || + *group == max_value()) + continue; + if (group + 1 != groups.cend()) { + digit_index = 0; + ++group; + } + std::uninitialized_copy(s.data(), s.data() + s.size(), + make_checked(p, s.size())); + p -= s.size(); + } + *p-- = static_cast(*digits); + if (prefix != 0) *p = static_cast(prefix); + auto data = buffer.data(); + return write_padded( + out, specs, usize, usize, [=](reserve_iterator it) { + return copy_str(data, data + size, it); + }); +} + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +template +FMT_CONSTEXPR OutputIt write_int(OutputIt out, T value, + const basic_format_specs& specs, + locale_ref loc) { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + prefix = data::prefixes[specs.sign]; + } + auto utype = static_cast(specs.type); + switch (specs.type) { + case 0: + case 'd': + return specs.localized + ? write_int_localized(out, + static_cast>(abs_value), + prefix, specs, loc) + : write_dec(out, abs_value, prefix, specs); + case 'x': + case 'X': { + if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); + bool upper = specs.type != 'x'; + int num_digits = count_digits<4>(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<4, Char>(it, abs_value, num_digits, upper); + }); + } + case 'b': + case 'B': { + if (specs.alt) prefix_append(prefix, (utype << 8) | '0'); + int num_digits = count_digits<1>(abs_value); + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); + } + case 'o': { + int num_digits = count_digits<3>(abs_value); + if (specs.alt && specs.precision <= num_digits && abs_value != 0) { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix_append(prefix, '0'); + } + return write_int(out, num_digits, prefix, specs, + [=](reserve_iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); + } +#ifdef FMT_DEPRECATED_N_SPECIFIER + case 'n': + return write_int_localized(out, abs_value, prefix, specs, loc); +#endif + case 'c': + return write_char(out, static_cast(abs_value), specs); + default: + FMT_THROW(format_error("invalid type specifier")); + } + return out; +} + +template FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view s, const basic_format_specs& specs) { auto data = s.data(); @@ -1790,154 +1849,12 @@ FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view s, auto width = specs.width != 0 ? compute_width(basic_string_view(data, size)) : 0; - using iterator = remove_reference_t; - return write_padded(out, specs, size, width, [=](iterator it) { - return copy_str(data, data + size, it); - }); + return write_padded(out, specs, size, width, + [=](reserve_iterator it) { + return copy_str(data, data + size, it); + }); } -// The handle_int_type_spec handler that writes an integer. -template struct int_writer { - OutputIt out; - locale_ref locale; - const basic_format_specs& specs; - UInt abs_value; - char prefix[4]; - unsigned prefix_size; - - using iterator = - remove_reference_t(), 0))>; - - constexpr string_view get_prefix() const { - return string_view(prefix, prefix_size); - } - - FMT_CONSTEXPR void write_dec() { - auto num_digits = count_digits(abs_value); - out = write_int( - out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) { - return format_decimal(it, abs_value, num_digits).end; - }); - } - - template - FMT_CONSTEXPR int_writer(OutputIt output, locale_ref loc, Int value, - const basic_format_specs& s) - : out(output), - locale(loc), - specs(s), - abs_value(static_cast(value)), - prefix_size(0) { - static_assert(std::is_same, UInt>::value, ""); - if (is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (specs.sign != sign::none && specs.sign != sign::minus) { - prefix[0] = specs.sign == sign::plus ? '+' : ' '; - ++prefix_size; - } - } - - FMT_CONSTEXPR void on_dec() { - if (specs.localized) return on_num(); - write_dec(); - } - - FMT_CONSTEXPR void on_hex() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = specs.type; - } - int num_digits = count_digits<4>(abs_value); - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<4, Char>(it, abs_value, num_digits, - specs.type != 'x'); - }); - } - - FMT_CONSTEXPR void on_bin() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast(specs.type); - } - int num_digits = count_digits<1>(abs_value); - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<1, Char>(it, abs_value, num_digits); - }); - } - - FMT_CONSTEXPR void on_oct() { - int num_digits = count_digits<3>(abs_value); - if (specs.alt && specs.precision <= num_digits && abs_value != 0) { - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - prefix[prefix_size++] = '0'; - } - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<3, Char>(it, abs_value, num_digits); - }); - } - - enum { sep_size = 1 }; - - void on_num() { - std::string groups = grouping(locale); - if (groups.empty()) return write_dec(); - auto sep = thousands_sep(locale); - if (!sep) return write_dec(); - int num_digits = count_digits(abs_value); - int size = num_digits, n = num_digits; - std::string::const_iterator group = groups.cbegin(); - while (group != groups.cend() && n > *group && *group > 0 && - *group != max_value()) { - size += sep_size; - n -= *group; - ++group; - } - if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); - char digits[40]; - format_decimal(digits, abs_value, num_digits); - basic_memory_buffer buffer; - size += static_cast(prefix_size); - const auto usize = to_unsigned(size); - buffer.resize(usize); - basic_string_view s(&sep, sep_size); - // Index of a decimal digit with the least significant digit having index 0. - int digit_index = 0; - group = groups.cbegin(); - auto p = buffer.data() + size - 1; - for (int i = num_digits - 1; i > 0; --i) { - *p-- = static_cast(digits[i]); - if (*group <= 0 || ++digit_index % *group != 0 || - *group == max_value()) - continue; - if (group + 1 != groups.cend()) { - digit_index = 0; - ++group; - } - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(p, s.size())); - p -= s.size(); - } - *p-- = static_cast(*digits); - if (prefix_size != 0) *p = static_cast(prefix[0]); - auto data = buffer.data(); - out = write_padded( - out, specs, usize, usize, - [=](iterator it) { return copy_str(data, data + size, it); }); - } - - void on_chr() { out = write_char(out, static_cast(abs_value), specs); } - - FMT_NORETURN void on_error() { - FMT_THROW(format_error("invalid type specifier")); - } -}; - template OutputIt write_nonfinite(OutputIt out, bool isinf, const basic_format_specs& specs, @@ -1947,8 +1864,7 @@ OutputIt write_nonfinite(OutputIt out, bool isinf, constexpr size_t str_size = 3; auto sign = fspecs.sign; auto size = str_size + (sign ? 1 : 0); - using iterator = remove_reference_t; - return write_padded(out, specs, size, [=](iterator it) { + return write_padded(out, specs, size, [=](reserve_iterator it) { if (sign) *it++ = static_cast(data::signs[sign]); return copy_str(str, str + str_size, it); }); @@ -2028,7 +1944,7 @@ OutputIt write_float(OutputIt out, const DecimalFP& fp, static const Char zero = static_cast('0'); auto sign = fspecs.sign; size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); - using iterator = remove_reference_t; + using iterator = reserve_iterator; int output_exp = fp.exponent + significand_size - 1; auto use_exp_format = [=]() { @@ -2203,8 +2119,7 @@ OutputIt write_ptr(OutputIt out, UIntPtr value, const basic_format_specs* specs) { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); - using iterator = remove_reference_t; - auto write = [=](iterator it) { + auto write = [=](reserve_iterator it) { *it++ = static_cast('0'); *it++ = static_cast('x'); return format_uint<4, Char>(it, value, num_digits); @@ -2355,17 +2270,6 @@ class arg_formatter_base { return detail::reserve(out_, n); } - using reserve_iterator = remove_reference_t(), 0))>; - - template - FMT_CONSTEXPR void write_int(T value, const format_specs& spec) { - using uint_type = uint32_or_64_or_128_t; - int_writer w(out_, locale_, value, spec); - handle_int_type_spec(spec.type, w); - out_ = w.out; - } - void write(char value) { auto&& it = reserve(1); *it++ = value; @@ -2390,9 +2294,10 @@ class arg_formatter_base { void write(const Ch* s, size_t size, const format_specs& specs) { auto width = specs.width != 0 ? compute_width(basic_string_view(s, size)) : 0; - out_ = write_padded(out_, specs, size, width, [=](reserve_iterator it) { - return copy_str(s, s + size, it); - }); + out_ = write_padded(out_, specs, size, width, + [=](reserve_iterator it) { + return copy_str(s, s + size, it); + }); } template @@ -2414,7 +2319,9 @@ class arg_formatter_base { FMT_CONSTEXPR void on_int() { // char is only formatted as int if there are specs. - formatter.write_int(static_cast(value), *formatter.specs_); + formatter.out_ = + detail::write_int(formatter.out_, static_cast(value), + *formatter.specs_, formatter.locale_); } FMT_CONSTEXPR void on_char() { if (formatter.specs_) @@ -2467,11 +2374,8 @@ class arg_formatter_base { template ::value)> FMT_CONSTEXPR FMT_INLINE iterator operator()(T value) { - if (specs_) - write_int(value, *specs_); - else - out_ = detail::write(out_, value); - return out_; + return out_ = specs_ ? detail::write_int(out_, value, *specs_, locale_) + : detail::write(out_, value); } FMT_CONSTEXPR iterator operator()(Char value) { @@ -2529,8 +2433,6 @@ class arg_formatter : public arg_formatter_base { using context_type = basic_format_context; context_type& ctx_; - basic_format_parse_context* parse_ctx_; - const Char* ptr_; public: using iterator = typename base::iterator; @@ -2543,21 +2445,15 @@ class arg_formatter : public arg_formatter_base { *specs* contains format specifier information for standard argument types. \endrst */ - constexpr explicit arg_formatter( - context_type& ctx, - basic_format_parse_context* parse_ctx = nullptr, - format_specs* specs = nullptr, const Char* ptr = nullptr) - : base(ctx.out(), specs, ctx.locale()), - ctx_(ctx), - parse_ctx_(parse_ctx), - ptr_(ptr) {} + constexpr explicit arg_formatter(context_type& ctx, + format_specs* specs = nullptr) + : base(ctx.out(), specs, ctx.locale()), ctx_(ctx) {} using base::operator(); - /** Formats an argument of a user-defined type. */ - iterator operator()(typename basic_format_arg::handle handle) { - if (ptr_) advance_to(*parse_ctx_, ptr_); - handle.format(*parse_ctx_, ctx_); + iterator operator()(typename basic_format_arg::handle) { + // User-defined types are handled separately because they require access to + // the parse context. return ctx_.out(); } }; @@ -2926,14 +2822,10 @@ class dynamic_specs_handler }; template -FMT_CONSTEXPR const Char* parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) { +FMT_CONSTEXPR const Char* do_parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) { FMT_ASSERT(begin != end, ""); Char c = *begin; - if (c == '}' || c == ':') { - handler(); - return begin; - } if (c >= '0' && c <= '9') { int index = 0; if (c != '0') @@ -2958,6 +2850,16 @@ FMT_CONSTEXPR const Char* parse_arg_id(const Char* begin, const Char* end, return it; } +template +FMT_CONSTEXPR_DECL FMT_INLINE const Char* parse_arg_id(const Char* begin, + const Char* end, + IDHandler&& handler) { + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler(); + return begin; +} + // Adapts SpecHandler to IDHandler API for dynamic width. template struct width_adapter { explicit FMT_CONSTEXPR width_adapter(SpecHandler& h) : handler(h) {} @@ -3092,8 +2994,14 @@ FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end, // Parses standard format specifiers and sends notifications about parsed // components to handler. template -FMT_CONSTEXPR const Char* parse_format_specs(const Char* begin, const Char* end, - SpecHandler&& handler) { +FMT_CONSTEXPR_DECL FMT_INLINE const Char* parse_format_specs( + const Char* begin, const Char* end, SpecHandler&& handler) { + if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && + *begin != 'L') { + handler.on_type(*begin++); + return begin; + } + if (begin == end) return begin; begin = parse_align(begin, end, handler); @@ -3306,21 +3214,14 @@ struct format_handler : detail::error_handler { return parse_context.begin(); } auto specs = basic_format_specs(); - if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && - *begin != 'L') { - specs.type = static_cast(*begin++); - } else { - using parse_context_t = basic_format_parse_context; - specs_checker> handler( - specs_handler(specs, parse_context, - context), - arg.type()); - begin = parse_format_specs(begin, end, handler); - if (begin == end || *begin != '}') - on_error("missing '}' in format string"); - } - context.advance_to(visit_format_arg( - arg_formatter(context, &parse_context, &specs), arg)); + using parse_context_t = basic_format_parse_context; + specs_checker> handler( + specs_handler(specs, parse_context, context), + arg.type()); + begin = parse_format_specs(begin, end, handler); + if (begin == end || *begin != '}') on_error("missing '}' in format string"); + context.advance_to( + visit_format_arg(arg_formatter(context, &specs), arg)); return begin; } }; @@ -3697,7 +3598,7 @@ struct formatter; - return visit_format_arg(af(ctx, nullptr, &specs), + return visit_format_arg(af(ctx, &specs), detail::make_arg(val)); } @@ -3800,8 +3701,7 @@ template class dynamic_formatter { if (specs_.precision >= 0) checker.end_precision(); using af = detail::arg_formatter; - visit_format_arg(af(ctx, nullptr, &specs_), - detail::make_arg(val)); + visit_format_arg(af(ctx, &specs_), detail::make_arg(val)); return ctx.out(); } diff --git a/test/compile-test.cc b/test/compile-test.cc index 6ccef2d0..fbcbfbaf 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -19,6 +19,42 @@ #include "gtest-extra.h" #include "util.h" +TEST(IteratorTest, TruncatingIterator) { + char* p = nullptr; + fmt::detail::truncating_iterator it(p, 3); + auto prev = it++; + EXPECT_EQ(prev.base(), p); + EXPECT_EQ(it.base(), p + 1); +} + +TEST(IteratorTest, TruncatingIteratorDefaultConstruct) { + static_assert(std::is_default_constructible< + fmt::detail::truncating_iterator>::value, + ""); + + fmt::detail::truncating_iterator it; + EXPECT_EQ(nullptr, it.base()); + EXPECT_EQ(std::size_t{0}, it.count()); +} + +#ifdef __cpp_lib_ranges +TEST(IteratorTest, TruncatingIteratorOutputIterator) { + static_assert( + std::output_iterator, char>); +} +#endif + +TEST(IteratorTest, TruncatingBackInserter) { + std::string buffer; + auto bi = std::back_inserter(buffer); + fmt::detail::truncating_iterator it(bi, 2); + *it++ = '4'; + *it++ = '2'; + *it++ = '1'; + EXPECT_EQ(buffer.size(), 2); + EXPECT_EQ(buffer, "42"); +} + // compiletime_prepared_parts_type_provider is useful only with relaxed // constexpr. #if FMT_USE_CONSTEXPR @@ -224,6 +260,11 @@ TEST(CompileTest, FormatToNWithCompileMacro) { EXPECT_STREQ("2a", buffer); } +TEST(CompileTest, FormattedSizeWithCompileMacro) { + EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42)); + EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0)); +} + TEST(CompileTest, TextAndArg) { EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42)); diff --git a/test/format-test.cc b/test/format-test.cc index a71aeaf1..cdd42db5 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -151,43 +151,6 @@ TEST(IteratorTest, CountingIterator) { EXPECT_EQ((it + 41).count(), 42); } -TEST(IteratorTest, TruncatingIterator) { - char* p = nullptr; - fmt::detail::truncating_iterator it(p, 3); - auto prev = it++; - EXPECT_EQ(prev.base(), p); - EXPECT_EQ(it.base(), p + 1); -} - - -TEST(IteratorTest, TruncatingIteratorDefaultConstruct) { - static_assert( - std::is_default_constructible>::value, - ""); - - fmt::detail::truncating_iterator it; - EXPECT_EQ(nullptr, it.base()); - EXPECT_EQ(std::size_t{0}, it.count()); -} - -#ifdef __cpp_lib_ranges -TEST(IteratorTest, TruncatingIteratorOutputIterator) { - static_assert(std::output_iterator, - char>); -} -#endif - -TEST(IteratorTest, TruncatingBackInserter) { - std::string buffer; - auto bi = std::back_inserter(buffer); - fmt::detail::truncating_iterator it(bi, 2); - *it++ = '4'; - *it++ = '2'; - *it++ = '1'; - EXPECT_EQ(buffer.size(), 2); - EXPECT_EQ(buffer, "42"); -} - TEST(IteratorTest, IsOutputIterator) { EXPECT_TRUE((fmt::detail::is_output_iterator::value)); EXPECT_FALSE((fmt::detail::is_output_iterator::value)); diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 03e64392..b24fbf60 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -64,26 +64,6 @@ TEST(OStreamTest, Enum) { EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum())); } -struct test_arg_formatter - : fmt::detail::arg_formatter { - fmt::format_parse_context parse_ctx; - test_arg_formatter(fmt::format_context& ctx, fmt::format_specs& s) - : fmt::detail::arg_formatter( - ctx, &parse_ctx, &s), - parse_ctx("") {} -}; - -TEST(OStreamTest, CustomArg) { - fmt::memory_buffer buffer; - fmt::format_context ctx(fmt::detail::buffer_appender{buffer}, - fmt::format_args()); - fmt::format_specs spec; - test_arg_formatter af(ctx, spec); - fmt::visit_format_arg( - af, fmt::detail::make_arg(streamable_enum())); - EXPECT_EQ("streamable_enum", std::string(buffer.data(), buffer.size())); -} - TEST(OStreamTest, Format) { EXPECT_EQ("a string", format("{0}", TestString("a string"))); std::string s = format("The date is {0}", Date(2012, 12, 9)); diff --git a/test/printf-test.cc b/test/printf-test.cc index ccd72dcd..1c1173c4 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -606,23 +606,3 @@ TEST(PrintfTest, VSPrintfMakeWArgsExample) { {fmt::make_wprintf_args(42, L"something")})); #endif } - -TEST(PrintfTest, PrintfDetermineOutputSize) { - using backit = std::back_insert_iterator>; - using truncated_printf_context = - fmt::basic_printf_context, char>; - - auto v = std::vector{}; - auto it = std::back_inserter(v); - - const auto format_string = "%s"; - const auto format_arg = "Hello"; - const auto expected_size = fmt::sprintf(format_string, format_arg).size(); - - EXPECT_EQ((truncated_printf_context( - fmt::detail::truncating_iterator(it, 0), format_string, - fmt::make_format_args(format_arg)) - .format() - .count()), - expected_size); -}