diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 29f7a250..6faf3e06 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1100,8 +1100,6 @@ struct formatter, Char> { // is not specified. basic_memory_buffer buf; auto out = std::back_inserter(buf); - using range = internal::output_range; - internal::basic_writer w(range(ctx.out())); internal::handle_dynamic_spec(specs.width, width_ref, ctx); internal::handle_dynamic_spec( @@ -1115,8 +1113,8 @@ struct formatter, Char> { f.precision = precision; parse_chrono_format(begin, end, f); } - w.write(buf.data(), buf.size(), specs); - return w.out(); + return internal::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } }; diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 215f50bc..ed4795d8 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -383,9 +383,7 @@ OutputIt format_default(OutputIt out, T value) { template OutputIt format_default(OutputIt out, double value) { - basic_writer> w(out); - w.write(value); - return w.out(); + return internal::write(out, value); } template diff --git a/include/fmt/format.h b/include/fmt/format.h index 8395d32e..832ad2eb 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1561,6 +1561,16 @@ template struct int_writer { } }; +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); + }); +} + template OutputIt write_nonfinite(OutputIt out, bool isinf, const basic_format_specs& specs, @@ -1577,6 +1587,71 @@ OutputIt write_nonfinite(OutputIt out, bool isinf, }); } +template ::value)> +OutputIt write(OutputIt out, T value, basic_format_specs specs = {}, + locale_ref loc = {}) { + if (const_check(!is_supported_floating_point(value))) return out; + float_specs fspecs = parse_float_type_spec(specs); + fspecs.sign = specs.sign; + if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + fspecs.sign = sign::minus; + value = -value; + } else if (fspecs.sign == sign::minus) { + fspecs.sign = sign::none; + } + + if (!std::isfinite(value)) + return write_nonfinite(out, std::isinf(value), specs, fspecs); + + if (specs.align == align::numeric && fspecs.sign) { + auto it = reserve(out, 1); + *it++ = static_cast(data::signs[fspecs.sign]); + out = base_iterator(out, it); + fspecs.sign = sign::none; + if (specs.width != 0) --specs.width; + } + + memory_buffer buffer; + if (fspecs.format == float_format::hex) { + if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); + snprintf_float(promote_float(value), specs.precision, fspecs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, specs); + } + int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; + if (fspecs.format == float_format::exp) { + if (precision == max_value()) + FMT_THROW(format_error("number is too big")); + else + ++precision; + } + if (const_check(std::is_same())) fspecs.binary32 = true; + fspecs.use_grisu = use_grisu(); + int exp = format_float(promote_float(value), precision, fspecs, buffer); + fspecs.precision = precision; + Char point = + fspecs.locale ? decimal_point(loc) : static_cast('.'); + float_writer w(buffer.data(), static_cast(buffer.size()), exp, + fspecs, point); + return write_padded(out, specs, w.size(), w); +} + +template +OutputIt write(OutputIt out, basic_string_view s, + const basic_format_specs& specs = {}) { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + auto width = specs.width != 0 + ? count_code_points(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); + }); +} + template OutputIt write_ptr(OutputIt out, UIntPtr value, const basic_format_specs* specs) { @@ -1592,27 +1667,21 @@ OutputIt write_ptr(OutputIt out, UIntPtr value, : base_iterator(out, write(reserve(out, size))); } -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); - }); -} +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; -// This template provides operations for formatting and writing data into a -// character range. -template class basic_writer { +template +class arg_formatter_base { public: using char_type = typename Range::value_type; using iterator = typename Range::iterator; using format_specs = basic_format_specs; - protected: + private: iterator out_; // Output iterator. locale_ref locale_; + format_specs* specs_; // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to out_. @@ -1620,6 +1689,27 @@ template class basic_writer { return internal::reserve(out_, n); } + using reserve_iterator = remove_reference_t(), 0))>; + + struct char_writer { + char_type value; + + size_t size() const { return 1; } + + template It operator()(It it) const { + *it++ = value; + return it; + } + }; + + void write_char(char_type value) { + if (specs_) + out_ = write_padded(out_, *specs_, 1, char_writer{value}); + else + write(value); + } + // Writes a decimal integer. template void write_decimal(Int value) { auto abs_value = static_cast>(value); @@ -1632,15 +1722,6 @@ template class basic_writer { it = format_decimal(it, abs_value, num_digits); } - using reserve_iterator = remove_reference_t(), 0))>; - - public: - explicit basic_writer(Range out, locale_ref loc = locale_ref()) - : out_(out.begin()), locale_(loc) {} - - iterator& out() { return out_; } - void write(int value) { write_decimal(value); } void write(long value) { write_decimal(value); } void write(long long value) { write_decimal(value); } @@ -1661,55 +1742,6 @@ template class basic_writer { out_ = w.out; } - template ::value)> - void write(T value, format_specs specs = {}) { - if (const_check(!is_supported_floating_point(value))) return; - float_specs fspecs = parse_float_type_spec(specs); - fspecs.sign = specs.sign; - if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; - value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; - } - - if (!std::isfinite(value)) { - out_ = write_nonfinite(out_, std::isinf(value), specs, fspecs); - return; - } - - if (specs.align == align::numeric && fspecs.sign) { - auto&& it = reserve(1); - *it++ = static_cast(data::signs[fspecs.sign]); - fspecs.sign = sign::none; - if (specs.width != 0) --specs.width; - } - - memory_buffer buffer; - if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); - snprintf_float(promote_float(value), specs.precision, fspecs, buffer); - out_ = write_bytes(out_, {buffer.data(), buffer.size()}, specs); - return; - } - int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; - if (fspecs.format == float_format::exp) { - if (precision == max_value()) - FMT_THROW(format_error("number is too big")); - else - ++precision; - } - if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = use_grisu(); - int exp = format_float(promote_float(value), precision, fspecs, buffer); - fspecs.precision = precision; - char_type point = fspecs.locale ? decimal_point(locale_) - : static_cast('.'); - float_writer w(buffer.data(), static_cast(buffer.size()), - exp, fspecs, point); - out_ = write_padded(out_, specs, w.size(), w); - } - void write(char value) { auto&& it = reserve(1); *it++ = value; @@ -1743,49 +1775,7 @@ template class basic_writer { template void write(basic_string_view s, const format_specs& specs = {}) { - const Char* data = s.data(); - std::size_t size = s.size(); - if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); - write(data, size, specs); - } -}; - -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -class arg_formatter_base : private basic_writer { - public: - using char_type = typename Range::value_type; - using iterator = typename Range::iterator; - using format_specs = basic_format_specs; - - private: - using writer_type = basic_writer; - using writer_type::out_; - format_specs* specs_; - - using writer_type::write; - using writer_type::write_int; - - struct char_writer { - char_type value; - - size_t size() const { return 1; } - - template It operator()(It it) const { - *it++ = value; - return it; - } - }; - - void write_char(char_type value) { - if (specs_) - out() = write_padded(out(), *specs_, 1, char_writer{value}); - else - write(value); + out_ = internal::write(out_, s, specs); } void write_pointer(const void* p) { @@ -1793,8 +1783,7 @@ class arg_formatter_base : private basic_writer { } protected: - using writer_type::out; - writer_type& writer() { return *this; } + iterator out() { return out_; } format_specs* specs() { return specs_; } void write(bool value) { @@ -1814,7 +1803,7 @@ class arg_formatter_base : private basic_writer { public: arg_formatter_base(Range r, format_specs* s, locale_ref loc) - : basic_writer(r, loc), specs_(s) {} + : out_(r.begin()), locale_(loc), specs_(s) {} iterator operator()(monostate) { FMT_ASSERT(false, "invalid argument type"); @@ -1844,8 +1833,9 @@ class arg_formatter_base : private basic_writer { template ::value)> iterator operator()(T value) { + auto specs = specs_ ? *specs_ : format_specs(); if (const_check(is_supported_floating_point(value))) - write(value, specs_ ? *specs_ : format_specs()); + out_ = internal::write(out_, value, specs, locale_); else FMT_ASSERT(false, "unsupported float argument type"); return out(); @@ -2503,7 +2493,7 @@ template struct id_adapter { template FMT_CONSTEXPR void parse_format_string(basic_string_view format_str, Handler&& handler) { - struct pfs_writer { + struct writer { FMT_CONSTEXPR void operator()(const Char* begin, const Char* end) { if (begin == end) return; for (;;) { @@ -3336,21 +3326,21 @@ extern template FMT_API char thousands_sep_impl(locale_ref loc); extern template FMT_API wchar_t thousands_sep_impl(locale_ref loc); extern template FMT_API char decimal_point_impl(locale_ref loc); extern template FMT_API wchar_t decimal_point_impl(locale_ref loc); -extern template -int format_float(double value, int precision, float_specs specs, - buffer& buf); -extern template -int format_float(long double value, int precision, - float_specs specs, buffer& buf); +extern template int format_float(double value, int precision, + float_specs specs, buffer& buf); +extern template int format_float(long double value, int precision, + float_specs specs, + buffer& buf); int snprintf_float(float value, int precision, float_specs specs, buffer& buf) = delete; -extern template -int snprintf_float(double value, int precision, float_specs specs, - buffer& buf); -extern template -int snprintf_float(long double value, int precision, - float_specs specs, buffer& buf); -} +extern template int snprintf_float(double value, int precision, + float_specs specs, + buffer& buf); +extern template int snprintf_float(long double value, + int precision, + float_specs specs, + buffer& buf); +} // namespace internal #endif template , diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 6f2c2ada..a3f087d4 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -328,7 +328,9 @@ template struct printf_formatter { } }; -/** This template formats data and writes the output to a writer. */ +/** + This template formats data and writes the output through an output iterator. + */ template class basic_printf_context { public: /** The character type for the output. */ @@ -358,9 +360,8 @@ template class basic_printf_context { public: /** \rst - Constructs a ``printf_context`` object. References to the arguments and - the writer are stored in the context object so make sure they have - appropriate lifetimes. + Constructs a ``printf_context`` object. References to the arguments are + stored in the context object so make sure they have appropriate lifetimes. \endrst */ basic_printf_context(OutputIt out, basic_string_view format_str, diff --git a/test/format-test.cc b/test/format-test.cc index 46629d1d..e7daf1d7 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -35,8 +35,6 @@ #include "util.h" #undef ERROR -#undef min -#undef max using std::size_t; @@ -47,7 +45,6 @@ using fmt::memory_buffer; using fmt::string_view; using fmt::wmemory_buffer; using fmt::wstring_view; -using fmt::internal::basic_writer; using fmt::internal::max_value; using testing::Return; @@ -102,47 +99,6 @@ void std_format(long double value, std::wstring& result) { result = buffer; } #endif - -// Checks if writing value to BasicWriter produces the same result -// as writing it to std::basic_ostringstream. -template -::testing::AssertionResult check_write(const T& value, const char* type) { - fmt::basic_memory_buffer buffer; - using range = fmt::buffer_range; - basic_writer writer(buffer); - writer.write(value); - std::basic_string actual = to_string(buffer); - std::basic_string expected; - std_format(value, expected); - if (expected == actual) return ::testing::AssertionSuccess(); - return ::testing::AssertionFailure() - << "Value of: (Writer<" << type << ">() << value).str()\n" - << " Actual: " << actual << "\n" - << "Expected: " << expected << "\n"; -} - -struct AnyWriteChecker { - template - ::testing::AssertionResult operator()(const char*, const T& value) const { - ::testing::AssertionResult result = check_write(value, "char"); - return result ? check_write(value, "wchar_t") : result; - } -}; - -template struct WriteChecker { - template - ::testing::AssertionResult operator()(const char*, const T& value) const { - return check_write(value, "char"); - } -}; - -// Checks if writing value to BasicWriter produces the same result -// as writing it to std::ostringstream both for char and wchar_t. -#define CHECK_WRITE(value) EXPECT_PRED_FORMAT1(AnyWriteChecker(), value) - -#define CHECK_WRITE_CHAR(value) EXPECT_PRED_FORMAT1(WriteChecker(), value) -#define CHECK_WRITE_WCHAR(value) \ - EXPECT_PRED_FORMAT1(WriteChecker(), value) } // namespace struct uint32_pair { @@ -479,92 +435,6 @@ TEST(StringViewTest, Ctor) { EXPECT_EQ(4u, string_view(std::string("defg")).size()); } -TEST(WriterTest, WriteInt) { - CHECK_WRITE(42); - CHECK_WRITE(-42); - CHECK_WRITE(static_cast(12)); - CHECK_WRITE(34u); - CHECK_WRITE(std::numeric_limits::min()); - CHECK_WRITE(max_value()); - CHECK_WRITE(max_value()); -} - -TEST(WriterTest, WriteLong) { - CHECK_WRITE(56l); - CHECK_WRITE(78ul); - CHECK_WRITE(std::numeric_limits::min()); - CHECK_WRITE(max_value()); - CHECK_WRITE(max_value()); -} - -TEST(WriterTest, WriteLongLong) { - CHECK_WRITE(56ll); - CHECK_WRITE(78ull); - CHECK_WRITE(std::numeric_limits::min()); - CHECK_WRITE(max_value()); - CHECK_WRITE(max_value()); -} - -TEST(WriterTest, WriteDouble) { - CHECK_WRITE(4.2); - CHECK_WRITE(-4.2); - auto min = std::numeric_limits::min(); - auto max = max_value(); - if (fmt::internal::use_grisu()) { - EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min)); - EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max)); - } else { - CHECK_WRITE(min); - CHECK_WRITE(max); - } -} - -TEST(WriterTest, WriteLongDouble) { - CHECK_WRITE(4.2l); - CHECK_WRITE_CHAR(-4.2l); - std::wstring str; - std_format(4.2l, str); - if (str[0] != '-') - CHECK_WRITE_WCHAR(-4.2l); - else - fmt::print("warning: long double formatting with std::swprintf is broken"); - auto min = std::numeric_limits::min(); - auto max = max_value(); - if (fmt::internal::use_grisu()) { - EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min)); - EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max)); - } else { - CHECK_WRITE(min); - CHECK_WRITE(max); - } -} - -TEST(WriterTest, WriteDoubleAtBufferBoundary) { - memory_buffer buf; - for (int i = 0; i < 100; ++i) fmt::format_to(buf, "{}", 1.23456789); -} - -TEST(WriterTest, WriteDoubleWithFilledBuffer) { - memory_buffer buf; - // Fill the buffer. - for (int i = 0; i < fmt::inline_buffer_size; ++i) fmt::format_to(buf, " "); - fmt::format_to(buf, "{}", 1.2); - fmt::string_view sv(buf.data(), buf.size()); - sv.remove_prefix(fmt::inline_buffer_size); - EXPECT_EQ("1.2", sv); -} - -TEST(WriterTest, WriteChar) { CHECK_WRITE('a'); } - -TEST(WriterTest, WriteWideChar) { CHECK_WRITE_WCHAR(L'a'); } - -TEST(WriterTest, WriteString) { - CHECK_WRITE_CHAR("abc"); - CHECK_WRITE_WCHAR("abc"); -} - -TEST(WriterTest, WriteWideString) { CHECK_WRITE_WCHAR(L"abc"); } - TEST(FormatToTest, FormatWithoutArgs) { std::string s; fmt::format_to(std::back_inserter(s), "test");