diff --git a/include/fmt/color.h b/include/fmt/color.h index 34de634c4046..b203e9ac8b3c 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -538,6 +538,86 @@ inline std::basic_string format(const text_style& ts, const S& format_str, fmt::make_format_args>(args...)); } +#if FMT_OUTPUT_RANGES +/** + Formats a string with the given text_style and writes the output to ``out``. + */ +template || + std::output_iterator, Char>)> +auto vformat_to(Output&& out, const text_style& ts, + basic_string_view format_str, + basic_format_args>> args) { + auto&& buf = detail::get_appendable_buffer(std::forward(out)); + detail::vformat_to(buf, ts, format_str, args); + return detail::get_iterator(buf, out); +} + +/** + \rst + Formats arguments with the given text_style, writes the result to the output + iterator or range ``out`` and returns the iterator past the end of the output + range. + + **Example**:: + + std::vector out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + \endrst +*/ +template > || + std::output_iterator, + char_t>)&&detail::is_string::value, + FMT_ENABLE_IF(enable)> +inline auto format_to(Output&& out, const text_style& ts, const S& format_str, + Args&&... args) { + return vformat_to(std::forward(out), ts, + detail::to_string_view(format_str), + fmt::make_format_args>>(args...)); +} + +/** + Formats a string with the given text_style and writes the output to ``out``. + */ +template || + std::output_iterator, Char>)> +auto vformat_into( + Output&& out, const text_style& ts, basic_string_view format_str, + basic_format_args>> args) { + auto&& buf = detail::get_buffer(std::forward(out)); + detail::vformat_to(buf, ts, format_str, args); + return detail::get_iterator(buf, out); +} + +/** + \rst + Formats arguments with the given text_style, writes the result to the output + iterator ``out`` and returns the iterator past the end of the output range. + + **Example**:: + + std::vector out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + \endrst +*/ +template > || + std::output_iterator, + char_t>)&&detail::is_string::value, + FMT_ENABLE_IF(enable)> +inline auto format_into(Output&& out, const text_style& ts, const S& format_str, + Args&&... args) { + return vformat_into( + std::forward(out), ts, detail::to_string_view(format_str), + fmt::make_format_args>>(args...)); +} +#else /** Formats a string with the given text_style and writes the output to ``out``. */ @@ -573,6 +653,7 @@ inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, return vformat_to(out, ts, detail::to_string_view(format_str), fmt::make_format_args>>(args...)); } +#endif template struct formatter, Char> : formatter { diff --git a/include/fmt/compile.h b/include/fmt/compile.h index a4c7e49563dc..44b8fe43e249 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -471,6 +471,39 @@ FMT_INLINE std::basic_string format(const S&, } } +# if FMT_OUTPUT_RANGES +template ::value)> +FMT_CONSTEXPR auto format_to(Output&& out, const S&, Args&&... args) { + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format_to( + std::forward(out), + static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format_to(std::forward(out), compiled, + std::forward(args)...); + } +} + +template ::value)> +FMT_CONSTEXPR auto format_into(Output&& out, const S&, Args&&... args) { + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format_into( + std::forward(out), + static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format_into(std::forward(out), compiled, + std::forward(args)...); + } +} +# else template ::value)> FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { @@ -484,6 +517,7 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { return fmt::format_to(out, compiled, std::forward(args)...); } } +# endif #endif template // std::addressof #include #include +#if defined(__cpp_lib_ranges) && defined(__cpp_if_constexpr) +# include +#endif // The fmt library version in the form major * 10000 + minor * 100 + patch. #define FMT_VERSION 100102 @@ -129,6 +132,29 @@ # define FMT_CONSTEXPR_CHAR_TRAITS #endif +// MSVC goofs up the range checking under a specific version +#ifndef FMT_RANGE_CHECKS +# if (!FMT_MSC_VERSION || FMT_MSC_VERSION > 1800) +# define FMT_RANGE_CHECKS 1 +# else +# define FMT_RANGE_CHECKS 0 +# endif +#endif + +// Only on if we know we can do the begin/end range checking properly, +// that we have `if constexpr`, and that we have a std::ranges::subrange type +#ifndef FMT_OUTPUT_RANGES +# if FMT_RANGE_CHECKS && defined(__cpp_if_constexpr) && \ + (__cpp_if_constexpr >= 201606L) && defined(__cpp_lib_ranges) && \ + (__cpp_lib_ranges >= 201911L) && defined(__cpp_concepts) && \ + (__cpp_concepts >= 202002L) && defined(__cpp_lib_concepts) && \ + (__cpp_lib_concepts >= 201907L) +# define FMT_OUTPUT_RANGES 1 +# else +# define FMT_OUTPUT_RANGES 0 +# endif +#endif + // Check if exceptions are disabled. #ifndef FMT_EXCEPTIONS # if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ @@ -215,7 +241,14 @@ #endif #ifndef FMT_UNICODE -# define FMT_UNICODE !FMT_MSC_VERSION +# if FMT_CLANG_VERSION +# define FMT_UNICODE 1 +# elif defined(_MSVC_EXECUTION_CHARACTER_SET) && \ + (_MSVC_EXECUTION_CHARACTER_SET == 65001) +# define FMT_UNICODE 1 +# else +# define FMT_UNICODE !FMT_MSC_VERSION +# endif #endif #ifndef FMT_CONSTEVAL @@ -785,8 +818,8 @@ inline auto get_container(std::back_insert_iterator it) return *accessor(it).container; } -template -FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) +template +FMT_CONSTEXPR auto copy_str(InputIt begin, InputSen end, OutputIt out) -> OutputIt { while (begin != end) *out++ = static_cast(*begin++); return out; @@ -796,12 +829,47 @@ template , U>::value&& is_char::value)> FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { - if (is_constant_evaluated()) return copy_str(begin, end, out); + if (is_constant_evaluated()) + return copy_str(begin, end, out); auto size = to_unsigned(end - begin); if (size > 0) memcpy(out, begin, size * sizeof(U)); return out + size; } +#if FMT_OUTPUT_RANGES + +template +FMT_CONSTEXPR auto copy_str(InputIt begin, InputSen end, OutputIt out, + OutputSen out_end) + -> std::ranges::subrange { + if constexpr (std::is_same::value) { + // minor speed optimization + auto out_current = + copy_str(std::move(begin), std::move(end), std::move(out)); + return {std::move(out_current), std::move(out_end)}; + } else { + while (begin != end && out != out_end) *out++ = static_cast(*begin++); + return {std::move(out), std::move(out_end)}; + } +} + +template , U>::value&& is_char::value)> +FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out, U* out_end) + -> std::ranges::subrange { + if (is_constant_evaluated()) + return copy_str(begin, end, out, out_end); + auto size = to_unsigned(end - begin); + auto out_size = to_unsigned(out_end - out); + if (size > out_size) size = out_size; + if (size > 0) memcpy(out, begin, size * sizeof(U)); + return {out + size, out_end}; +} + +#endif + /** \rst A contiguous memory buffer with an optional growing ability. It is an internal @@ -946,6 +1014,129 @@ class iterator_buffer final : public Traits, public buffer { auto count() const -> size_t { return Traits::count() + this->size(); } }; +#if FMT_OUTPUT_RANGES +// A buffer that writes to an output range when flushed. +template +class range_buffer final : public Traits, public buffer { + private: + using Subrange_ = std::ranges::subrange, + std::ranges::sentinel_t>; + Subrange_ out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == buffer_size) flush(); + } + + void flush() { + if (std::ranges::empty(out_)) return; + auto size = this->size(); + this->clear(); + auto it_end = copy_str(data_, data_ + this->limit(size), + std::ranges::begin(out_), std::ranges::end(out_)); + out_ = Subrange_(std::move(it_end).begin(), std::move(it_end).end()); + } + + public: + template + explicit range_buffer(Out&& out, size_t n = buffer_size) + : Traits(n), + buffer(data_, 0, buffer_size), + out_(std::forward(out)) {} + range_buffer(range_buffer&& other) + : Traits(other), + buffer(data_, 0, buffer_size), + out_(std::move(other.out_)) {} + ~range_buffer() { flush(); } + + auto out() -> Subrange_ { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class range_buffer, T, Traits> final + : public Traits, + public buffer { + private: + using Subrange_ = std::ranges::subrange, + std::ranges::sentinel_t>; + Output& out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == buffer_size) flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + copy_str(data_, data_ + this->limit(size), std::back_inserter(out_)); + } + + public: + explicit range_buffer(Output& out, size_t n = buffer_size) + : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} + range_buffer(range_buffer&& other) + : Traits(other), + buffer(data_, 0, buffer_size), + out_(std::move(other.out_)) {} + ~range_buffer() { flush(); } + + auto out() -> Subrange_ { + flush(); + return Subrange_(out_.end(), out_.end()); + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class range_buffer, T, Traits> final + : public Traits, + public buffer { + private: + using Subrange_ = std::ranges::subrange, + std::ranges::sentinel_t>; + Output& out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == buffer_size) flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + copy_str(data_, data_ + this->limit(size), + std::inserter(out_, out_.end())); + } + + public: + explicit range_buffer(Output& out, size_t n = buffer_size) + : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} + range_buffer(range_buffer&& other) + : Traits(other), + buffer(data_, 0, buffer_size), + out_(std::move(other.out_)) {} + ~range_buffer() { flush(); } + + auto out() -> Subrange_ { + flush(); + return Subrange_(out_.end(), out_.end()); + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; +#endif + +// A buffer that writes to an output iterator when flushed. template class iterator_buffer final : public fixed_buffer_traits, @@ -1125,26 +1316,6 @@ template using buffer_appender = conditional_t::value, appender, std::back_insert_iterator>>; -// Maps an output iterator to a buffer. -template -auto get_buffer(OutputIt out) -> iterator_buffer { - return iterator_buffer(out); -} -template , Buf>::value)> -auto get_buffer(std::back_insert_iterator out) -> buffer& { - return get_container(out); -} - -template -FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { - return buf.out(); -} -template -auto get_iterator(buffer&, OutputIt out) -> OutputIt { - return out; -} - struct view {}; template struct named_arg : view { @@ -1528,6 +1699,82 @@ template using void_t = typename void_t_impl::type; template using void_t = void; #endif +template struct is_range_ : std::false_type {}; + +#if FMT_RANGE_CHECKS + +# define FMT_DECLTYPE_RETURN(val) \ + ->decltype(val) { return val; } \ + static_assert( \ + true, "") // This makes it so that a semicolon is required after the + // macro, which helps clang-format handle the formatting. + +// C array overload +template +auto range_begin(const T (&arr)[N]) -> const T* { + return arr; +} +template +auto range_end(const T (&arr)[N]) -> const T* { + return arr + N; +} + +template +struct has_member_fn_begin_end_t : std::false_type {}; + +template +struct has_member_fn_begin_end_t().begin()), + decltype(std::declval().end())>> + : std::true_type {}; + +// Member function overload +template +auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).begin()); +template +auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).end()); + +// ADL overload. Only participates in overload resolution if member functions +// are not found. +template +auto range_begin(T&& rng) + -> enable_if_t::value, + decltype(begin(static_cast(rng)))> { + return begin(static_cast(rng)); +} +template +auto range_end(T&& rng) -> enable_if_t::value, + decltype(end(static_cast(rng)))> { + return end(static_cast(rng)); +} + +template +struct has_const_begin_end : std::false_type {}; +template +struct has_mutable_begin_end : std::false_type {}; + +template +struct has_const_begin_end< + T, + void_t< + decltype(detail::range_begin(std::declval&>())), + decltype(detail::range_end(std::declval&>()))>> + : std::true_type {}; + +template +struct has_mutable_begin_end< + T, void_t())), + decltype(detail::range_end(std::declval())), + // the extra int here is because older versions of MSVC don't + // SFINAE properly unless there are distinct types + int>> : std::true_type {}; + +template +struct is_range_ + : std::integral_constant::value || + has_mutable_begin_end::value)> {}; +# undef FMT_DECLTYPE_RETURN +#endif + template struct is_output_iterator : std::false_type {}; @@ -1543,6 +1790,97 @@ template struct is_back_insert_iterator> : std::true_type {}; +#if FMT_OUTPUT_RANGES +template +struct is_container_push_backable : std::false_type {}; + +template +struct is_container_push_backable< + Container, T, + void_t().push_back(std::declval()))>> + : std::true_type {}; + +template +struct is_container_insertable : std::false_type {}; + +template +struct is_container_insertable< + Container, T, + void_t().insert( + std::declval>(), + std::declval()))>> + : std::integral_constant::value> { +}; +#endif + +// Maps an output iterator to a buffer. +#if FMT_OUTPUT_RANGES +template decltype(auto) get_buffer(Output&& out) { + if constexpr (std::ranges::output_range) { + return range_buffer, T>(std::forward(out)); + } else if constexpr (is_back_insert_iterator>::value) { + using Container_ = typename remove_cvref_t::container_type; + if constexpr (std::is_base_of, Container_>::value) { + return get_container(out); + } else { + return iterator_buffer, T>( + std::forward(out)); + } + } else { + return iterator_buffer, T>( + std::forward(out)); + } +} + +template +decltype(auto) get_appendable_buffer(Output&& out) { + if constexpr (std::ranges::output_range) { + if constexpr (is_container_insertable::value) { + // appendable container: give a back_inserter-based buffer + if constexpr (is_container_push_backable::value) { + using Iterator_ = std::back_insert_iterator>; + return range_buffer(std::forward(out)); + } else { + using Iterator_ = std::insert_iterator>; + return range_buffer(std::forward(out)); + } + } else { + return range_buffer, T>(std::forward(out)); + } + } else { + return get_buffer(std::forward(out)); + } +} + +template +FMT_INLINE auto get_iterator(Buf& buf, Output&&) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, Output&& out) { + return out; +} +#else +template +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template , Buf>::value)> +auto get_buffer(std::back_insert_iterator out) -> buffer& { + return get_container(out); +} + +template +FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} +#endif + // A type-erased reference to an std::locale to avoid a heavy include. class locale_ref { private: @@ -1613,6 +1951,40 @@ template FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg { return make_arg(val); } + +template ::value> +struct out_storage { + using iterator = remove_cvref_t; + using range = void; + Out out_; + + template , + out_storage>::value)> + FMT_CONSTEXPR out_storage(Value&& value) : out_(std::forward(value)) {} + + out_storage(out_storage&&) = default; + out_storage(const out_storage&) = default; + out_storage& operator=(out_storage&&) = default; + out_storage& operator=(const out_storage&) = default; +}; + +#if FMT_OUTPUT_RANGES +template struct out_storage { + using iterator = std::ranges::iterator_t; + using sentinel = std::ranges::sentinel_t; + using range = std::ranges::subrange; + range out_; + + template , + out_storage>::value)> + FMT_CONSTEXPR out_storage(Value&& value) : out_(std::forward(value)) {} + + out_storage(out_storage&&) = default; + out_storage(const out_storage&) = default; + out_storage& operator=(out_storage&&) = default; + out_storage& operator=(const out_storage&) = default; +}; +#endif } // namespace detail FMT_BEGIN_EXPORT @@ -1723,12 +2095,17 @@ FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( // Formatting context. template class basic_format_context { private: - OutputIt out_; + using Storage_ = detail::out_storage; + Storage_ out_storage_; basic_format_args args_; detail::locale_ref loc_; + using ItOrRange_ = + conditional_t::value, + typename Storage_::iterator, typename Storage_::range>; public: - using iterator = OutputIt; + using iterator = typename Storage_::iterator; + using range = typename Storage_::range; using format_arg = basic_format_arg; using format_args = basic_format_args; using parse_context_type = basic_format_parse_context; @@ -1746,7 +2123,7 @@ template class basic_format_context { */ constexpr basic_format_context(OutputIt out, format_args ctx_args, detail::locale_ref loc = {}) - : out_(out), args_(ctx_args), loc_(loc) {} + : out_storage_(out), args_(ctx_args), loc_(loc) {} constexpr auto arg(int id) const -> format_arg { return args_.get(id); } FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { @@ -1760,12 +2137,19 @@ template class basic_format_context { FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } - // Returns an iterator to the beginning of the output range. - FMT_CONSTEXPR auto out() -> iterator { return out_; } + // Returns an iterator to the beginning of the + // output range, or the whole output range. + FMT_CONSTEXPR auto out() -> ItOrRange_ { return out_storage_.out_; } // Advances the begin iterator to ``it``. - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; + void advance_to(ItOrRange_ it_or_range) { +#if FMT_OUTPUT_RANGES + if (!detail::is_back_insert_iterator()) + out_storage_.out_ = std::move(it_or_range); +#else + if (!detail::is_back_insert_iterator()) + out_storage_.out_ = it_or_range; +#endif } FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } @@ -2819,6 +3203,30 @@ FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) return vformat(fmt, fmt::make_format_args(args...)); } +#if FMT_OUTPUT_RANGES +/** Formats a string and writes the output to ``out``, appending + rather than directly writing if it is a typical insertable + contianer type. */ +template || + std::output_iterator, char>)> +auto vformat_to(Output&& out, string_view fmt, format_args args) { + auto&& buf = detail::get_appendable_buffer(std::forward(out)); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** Formats a string and writes the output to ``out``, writing + directly into the range [begin, end). */ +template || + std::output_iterator, char>)> +auto vformat_into(Output&& out, string_view fmt, format_args args) { + auto&& buf = detail::get_buffer(std::forward(out)); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} +#else /** Formats a string and writes the output to ``out``. */ template ::value)> @@ -2827,7 +3235,59 @@ auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { detail::vformat_to(buf, fmt, args, {}); return detail::get_iterator(buf, out); } +#endif + +#if FMT_OUTPUT_RANGES +/** + \rst + Formats ``args`` according to specifications in ``fmt``, writes the result to + the output range or iterator ``out`` and returns the subrange past the end of + the output range. `format_to` will write to the end of a container (if it is an + insertable container). `format_to` does not append a terminating null + character. + + **Example**:: + + auto out = std::vector(); + fmt::format_to(out, "{}", 42); + + **Example**:: + + auto out = std::vector(); + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template || + std::output_iterator, char>)> +FMT_INLINE auto format_to(Output&& out, format_string fmt, T&&... args) { + return vformat_to(std::forward(out), fmt, + fmt::make_format_args(args...)); +} + +/** + \rst + Formats ``args`` according to specifications in ``fmt``, writes the result into + the output range or iterator ``out`` and returns the subrange past the end of + the output range. `format_into` does not append into a contianer and only + writes into it. `format_into` does not append or write a terminating null + character. + **Example**:: + + auto out = std::vector(2, '\0'); + fmt::format_into(out, "{}", 42); + \endrst + */ +template || + std::output_iterator, char>)> +FMT_INLINE auto format_into(Output&& out, format_string fmt, + T&&... args) { + return vformat_into(std::forward(out), fmt, + fmt::make_format_args(args...)); +} +#else /** \rst Formats ``args`` according to specifications in ``fmt``, writes the result to @@ -2846,6 +3306,7 @@ FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) -> OutputIt { return vformat_to(out, fmt, fmt::make_format_args(args...)); } +#endif template struct format_to_n_result { /** Iterator past the end of the output range. */ diff --git a/include/fmt/format.h b/include/fmt/format.h index fa76aa112e53..4400ae57587b 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1063,8 +1063,8 @@ namespace detail_exported { #if FMT_USE_NONTYPE_TEMPLATE_ARGS template struct fixed_string { constexpr fixed_string(const Char (&str)[N]) { - detail::copy_str(static_cast(str), - str + N, data); + detail::copy_str( + static_cast(str), static_cast(str + N), data); } Char data[N] = {}; }; @@ -2123,7 +2123,7 @@ template auto write_int(OutputIt out, UInt value, unsigned prefix, const format_specs& specs, const digit_grouping& grouping) -> OutputIt { - static_assert(std::is_same, UInt>::value, ""); + static_assert(std::is_same, UInt>::value, ""); int num_digits = 0; auto buffer = memory_buffer(); switch (specs.type) { @@ -2139,7 +2139,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, if (specs.alt) prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); num_digits = count_digits<4>(value); - format_uint<4,Char>(appender(buffer), value, num_digits, upper); + format_uint<4, Char>(appender(buffer), value, num_digits, upper); break; } case presentation_type::bin_lower: @@ -2148,7 +2148,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, if (specs.alt) prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); num_digits = count_digits<1>(value); - format_uint<1,Char>(appender(buffer), value, num_digits); + format_uint<1, Char>(appender(buffer), value, num_digits); break; } case presentation_type::oct: { @@ -2157,7 +2157,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, // is not greater than the number of digits. if (specs.alt && specs.precision <= num_digits && value != 0) prefix_append(prefix, '0'); - format_uint<3,Char>(appender(buffer), value, num_digits); + format_uint<3, Char>(appender(buffer), value, num_digits); break; } case presentation_type::chr: @@ -2167,11 +2167,11 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, } unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + - to_unsigned(grouping.count_separators(num_digits)); + to_unsigned(grouping.count_separators(num_digits)); return write_padded( out, specs, size, size, [&](reserve_iterator it) { - for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) - *it++ = static_cast(p & 0xff); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); return grouping.apply(it, string_view(buffer.data(), buffer.size())); }); } @@ -2326,7 +2326,7 @@ class counting_iterator { FMT_UNCHECKED_ITERATOR(counting_iterator); struct value_type { - template FMT_CONSTEXPR void operator=(const T&) {} + template FMT_CONSTEXPR void operator=(const T&) const {} }; FMT_CONSTEXPR counting_iterator() : count_(0) {} @@ -4541,6 +4541,51 @@ inline auto format(const Locale& loc, format_string fmt, T&&... args) return fmt::vformat(loc, string_view(fmt), fmt::make_format_args(args...)); } +#if FMT_OUTPUT_RANGES +template || + std::output_iterator< + Output, char>)&&detail::is_locale::value)> +auto vformat_to(Output&& out, const Locale& loc, string_view fmt, + format_args args) { + auto&& buf = detail::get_appendable_buffer(std::forward(out)); + detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template || + std::output_iterator< + Output, char>)&&detail::is_locale::value)> +auto vformat_into(Output&& out, const Locale& loc, string_view fmt, + format_args args) { + auto&& buf = detail::get_buffer(std::forward(out)); + detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template || + std::output_iterator, + char>)&&detail::is_locale::value)> +FMT_INLINE auto format_to(Output&& out, const Locale& loc, + format_string fmt, T&&... args) { + return vformat_to(std::forward(out), loc, fmt, + fmt::make_format_args(args...)); +} + +template || + std::output_iterator, + char>)&&detail::is_locale::value)> +FMT_INLINE auto format_into(Output&& out, const Locale& loc, + format_string fmt, T&&... args) { + return vformat_into(std::forward(out), loc, fmt, + fmt::make_format_args(args...)); +} +#else template ::value&& detail::is_locale::value)> @@ -4559,6 +4604,7 @@ FMT_INLINE auto format_to(OutputIt out, const Locale& loc, format_string fmt, T&&... args) -> OutputIt { return vformat_to(out, loc, fmt, fmt::make_format_args(args...)); } +#endif template ::value)> diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index c0b51aeef68a..2e6ef6d33d4d 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -86,82 +86,6 @@ template class is_set { template struct conditional_helper {}; -template struct is_range_ : std::false_type {}; - -#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800 - -# define FMT_DECLTYPE_RETURN(val) \ - ->decltype(val) { return val; } \ - static_assert( \ - true, "") // This makes it so that a semicolon is required after the - // macro, which helps clang-format handle the formatting. - -// C array overload -template -auto range_begin(const T (&arr)[N]) -> const T* { - return arr; -} -template -auto range_end(const T (&arr)[N]) -> const T* { - return arr + N; -} - -template -struct has_member_fn_begin_end_t : std::false_type {}; - -template -struct has_member_fn_begin_end_t().begin()), - decltype(std::declval().end())>> - : std::true_type {}; - -// Member function overload -template -auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).begin()); -template -auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast(rng).end()); - -// ADL overload. Only participates in overload resolution if member functions -// are not found. -template -auto range_begin(T&& rng) - -> enable_if_t::value, - decltype(begin(static_cast(rng)))> { - return begin(static_cast(rng)); -} -template -auto range_end(T&& rng) -> enable_if_t::value, - decltype(end(static_cast(rng)))> { - return end(static_cast(rng)); -} - -template -struct has_const_begin_end : std::false_type {}; -template -struct has_mutable_begin_end : std::false_type {}; - -template -struct has_const_begin_end< - T, - void_t< - decltype(detail::range_begin(std::declval&>())), - decltype(detail::range_end(std::declval&>()))>> - : std::true_type {}; - -template -struct has_mutable_begin_end< - T, void_t())), - decltype(detail::range_end(std::declval())), - // the extra int here is because older versions of MSVC don't - // SFINAE properly unless there are distinct types - int>> : std::true_type {}; - -template -struct is_range_ - : std::integral_constant::value || - has_mutable_begin_end::value)> {}; -# undef FMT_DECLTYPE_RETURN -#endif - // tuple_size and tuple_element check. template class is_tuple_like_ { template diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 9a7aa4d770d2..740cdb738092 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -138,6 +138,114 @@ inline auto format(const Locale& loc, const S& format_str, T&&... args) fmt::make_format_args>(args...)); } +#if FMT_OUTPUT_RANGES +template , + FMT_ENABLE_IF(( + std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_exotic_char::value)> +auto vformat_to(Output&& out, const S& format_str, + basic_format_args>> args) { + auto&& buf = detail::get_appendable_buffer(std::forward(out)); + detail::vformat_to(buf, detail::to_string_view(format_str), args); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF(( + std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_exotic_char::value)> +auto vformat_into( + Output&& out, const S& format_str, + basic_format_args>> args) { + auto&& buf = detail::get_buffer(std::forward(out)); + detail::vformat_to(buf, detail::to_string_view(format_str), args); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF(( + std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_exotic_char::value)> +inline auto format_to(Output&& out, const S& fmt, T&&... args) { + return vformat_to(std::forward(out), detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(( + std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_exotic_char::value)> +inline auto format_into(Output&& out, const S& fmt, T&&... args) { + return vformat_into(std::forward(out), detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF( + (std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto vformat_to( + Output&& out, const Locale& loc, const S& format_str, + basic_format_args>> args) { + auto&& buf = detail::get_appendable_buffer(std::forward(out)); + vformat_to(buf, detail::to_string_view(format_str), args, + detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF( + (std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto vformat_into( + Output&& out, const Locale& loc, const S& format_str, + basic_format_args>> args) { + auto&& buf = detail::get_buffer(std::forward(out)); + vformat_to(buf, detail::to_string_view(format_str), args, + detail::locale_ref(loc)); + return detail::get_iterator(buf, out); +} + +template , + bool enable = + (std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_locale::value && + detail::is_exotic_char::value, + FMT_ENABLE_IF(enable)> +inline auto format_to(Output&& out, const Locale& loc, const S& format_str, + T&&... args) { + return vformat_to(std::forward(out), loc, + detail::to_string_view(format_str), + fmt::make_format_args>(args...)); +} + +template , + bool enable = + (std::ranges::output_range || + std::output_iterator, + Char>)&&detail::is_locale::value && + detail::is_exotic_char::value, + FMT_ENABLE_IF(enable)> +inline auto format_into(Output&& out, const Locale& loc, const S& format_str, + T&&... args) { + return vformat_into(std::forward(out), loc, + detail::to_string_view(format_str), + fmt::make_format_args>(args...)); +} +#else template , FMT_ENABLE_IF(detail::is_output_iterator::value&& detail::is_exotic_char::value)> @@ -183,6 +291,7 @@ inline auto format_to(OutputIt out, const Locale& loc, const S& format_str, return vformat_to(out, loc, detail::to_string_view(format_str), fmt::make_format_args>(args...)); } +#endif template ::value&& diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ec97ac29a724..0bf2fe35cd3a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -73,6 +73,14 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS)) add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc) endif () add_fmt_test(ostream-test) +if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) + add_fmt_test(output-range-test) + target_compile_features(output-range-test PRIVATE cxx_std_20) + if (MSVC) + # Without this option, MSVC returns 199711L for the __cplusplus macro. + target_compile_options(output-range-test PRIVATE /Zc:__cplusplus) + endif() +endif() add_fmt_test(compile-test) add_fmt_test(compile-fp-test HEADER_ONLY) if (MSVC) diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 56fbd38216a8..e2c4a1c8635f 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -5,6 +5,11 @@ // // For the license information refer to format.h. +// Disable bogus MSVC warnings. +#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) +# define _CRT_SECURE_NO_WARNINGS +#endif + #include "fmt/chrono.h" #include @@ -1027,4 +1032,4 @@ TEST(chrono_test, glibc_extensions) { TEST(chrono_test, out_of_range) { auto d = std::chrono::duration(538976288); EXPECT_THROW((void)fmt::format("{:%j}", d), fmt::format_error); -} \ No newline at end of file +} diff --git a/test/format-test.cc b/test/format-test.cc index e967ed323d4c..7bcef5e2f1ed 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1696,8 +1696,13 @@ TEST(format_test, format_custom) { TEST(format_test, format_to_custom) { char buf[10] = {}; +#if FMT_OUTPUT_RANGES + auto result_range = fmt::format_to(buf, "{}", Answer()); + EXPECT_EQ(result_range.begin(), buf + 2); +#else auto end = fmt::format_to(buf, "{}", Answer()); EXPECT_EQ(end, buf + 2); +#endif EXPECT_STREQ(buf, "42"); } diff --git a/test/output-range-test.cc b/test/output-range-test.cc new file mode 100644 index 000000000000..5d79bdc4b424 --- /dev/null +++ b/test/output-range-test.cc @@ -0,0 +1,196 @@ +// Formatting library for C++ - std::ostream support tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/format.h" + +#if FMT_OUTPUT_RANGES + +# include +# include +# include +# include +# include + +# include "gmock/gmock.h" +# include "gtest-extra.h" +# include "util.h" + +TEST(output_range_c_array_char_test, format_into) { + char buffer[4]; + buffer[3] = 'x'; + auto result = fmt::format_into(buffer, "{}", "abc"); + EXPECT_EQ(buffer + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer, 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer, 4)); + result = fmt::format_into(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer, 4)); +} + +TEST(output_range_array_char_test, format_into) { + std::array buffer; + buffer[3] = 'x'; + auto result = fmt::format_into(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4)); + result = fmt::format_into(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4)); +} + +TEST(output_range_span_array_char_test, format_into) { + std::array storage; + std::span buffer(storage); + buffer[3] = 'x'; + auto result = fmt::format_into(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4)); + result = fmt::format_into(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4)); +} + +TEST(output_range_vector_char_test, format_into) { + std::vector buffer(4, '\0'); + buffer[3] = 'x'; + auto result = fmt::format_into(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4)); + result = fmt::format_into(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4)); +} + +TEST(output_range_list_char_test, format_into) { + std::list buffer(4, '\0'); + using subrng = std::ranges::subrange; + auto fourth_it = std::ranges::next(buffer.begin(), 3); + auto end = buffer.end(); + subrng first3_subrange(buffer.begin(), fourth_it); + *fourth_it = 'x'; + auto result = fmt::format_into(buffer, "{}", "abc"); + EXPECT_EQ(fourth_it, result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), first3_subrange)); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcx"), buffer)); + result = fmt::format_into(buffer, "x{}y", "abc"); + EXPECT_EQ(end, result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("xabc"), buffer)); +} + +TEST(output_range_deque_char_test, format_into) { + std::deque buffer(4, '\0'); + using subrng = std::ranges::subrange; + auto fourth_it = std::ranges::next(buffer.begin(), 3); + auto end = buffer.end(); + subrng first3_subrange(buffer.begin(), fourth_it); + *fourth_it = 'x'; + auto result = fmt::format_into(buffer, "{}", "abc"); + EXPECT_EQ(fourth_it, result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), first3_subrange)); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcx"), buffer)); + result = fmt::format_into(buffer, "x{}y", "abc"); + EXPECT_EQ(end, result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("xabc"), buffer)); +} + +TEST(output_range_c_array_char_test, format_to) { + char buffer[4]; + buffer[3] = 'x'; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer, 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer, 4)); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer, 4)); +} + +TEST(output_range_array_char_test, format_to) { + std::array buffer; + buffer[3] = 'x'; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4)); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4)); +} + +TEST(output_range_span_array_char_test, format_to) { + std::array storage; + std::span buffer(storage); + buffer[3] = 'x'; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3)); + EXPECT_EQ("abcx", fmt::string_view(buffer.data(), 4)); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 4, result.begin()); + EXPECT_EQ("xabc", fmt::string_view(buffer.data(), 4)); +} + +TEST(output_range_vector_char_test, format_to) { + std::vector buffer{}; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", fmt::string_view(buffer.data(), 3)); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 8, result.begin()); + EXPECT_EQ(buffer.begin() + (std::ptrdiff_t)buffer.size(), result.begin()); + EXPECT_EQ(buffer.end(), result.begin()); + EXPECT_EQ("abcxabcy", fmt::string_view(buffer.data(), 8)); +} + +TEST(output_range_basic_string_char_test, format_to) { + std::string buffer{}; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer.begin() + 3, result.begin()); + EXPECT_EQ("abc", buffer); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.begin() + 8, result.begin()); + EXPECT_EQ(buffer.begin() + (std::ptrdiff_t)buffer.size(), result.begin()); + EXPECT_EQ(buffer.end(), result.begin()); + EXPECT_EQ("abcxabcy", buffer); +} + +TEST(output_range_list_char_test, format_to) { + std::list buffer{}; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer.end(), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), 3), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), (std::ptrdiff_t)buffer.size()), + result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), buffer)); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.end(), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), 8), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), (std::ptrdiff_t)buffer.size()), + result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcxabcy"), buffer)); +} + +TEST(output_range_deque_char_test, format_to) { + std::deque buffer{}; + auto result = fmt::format_to(buffer, "{}", "abc"); + EXPECT_EQ(buffer.end(), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), 3), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), (std::ptrdiff_t)buffer.size()), + result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abc"), buffer)); + result = fmt::format_to(buffer, "x{}y", "abc"); + EXPECT_EQ(buffer.end(), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), 8), result.begin()); + EXPECT_EQ(std::ranges::next(buffer.begin(), (std::ptrdiff_t)buffer.size()), + result.begin()); + EXPECT_TRUE(std::ranges::equal(fmt::string_view("abcxabcy"), buffer)); +} + +#endif diff --git a/test/ranges-test.cc b/test/ranges-test.cc index ba5c464dee07..9cf83f1ae5f3 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -205,8 +205,13 @@ TEST(ranges_test, format_struct) { TEST(ranges_test, format_to) { char buf[10]; +#if FMT_OUTPUT_RANGES + auto result_range = fmt::format_to(buf, "{}", std::vector{1, 2, 3}); + *result_range.begin() = '\0'; +#else auto end = fmt::format_to(buf, "{}", std::vector{1, 2, 3}); *end = '\0'; +#endif EXPECT_STREQ(buf, "[1, 2, 3]"); } diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 90ada586ce78..1b62e6bf17e1 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -5,6 +5,11 @@ // // For the license information refer to format.h. +// Disable bogus MSVC warnings. +#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER) +# define _CRT_SECURE_NO_WARNINGS +#endif + #include "fmt/xchar.h" #include