diff --git a/CMakeLists.txt b/CMakeLists.txt index e4a48b0d..3bf80f80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_MODULE "Build a module instead of a traditional library." OFF) +option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) set(FMT_CAN_MODULE OFF) if (CMAKE_CXX_STANDARD GREATER 17 AND @@ -96,6 +97,10 @@ if (FMT_TEST AND FMT_MODULE) # The tests require {fmt} to be compiled as traditional library message(STATUS "Testing is incompatible with build mode 'module'.") endif () +set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") +if (FMT_SYSTEM_HEADERS) + set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) +endif () # Get version from core.h file(READ include/fmt/core.h core_h) @@ -262,7 +267,7 @@ endif () target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) -target_include_directories(fmt PUBLIC +target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC $ $) @@ -298,7 +303,7 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) -target_include_directories(fmt-header-only INTERFACE +target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE $ $) diff --git a/ChangeLog.rst b/ChangeLog.rst index d0a1aa1d..4b88f4a6 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -281,6 +281,9 @@ This doesn't introduce a dependency on ```` so there is virtually no compile time effect. +* Deprecated an undocumented ``format_to`` overload that takes + ``basic_memory_buffer``. + * Made parameter order in ``vformat_to`` consistent with ``format_to`` (`#2327 `_). diff --git a/include/fmt/args.h b/include/fmt/args.h index 36f62220..99060040 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -146,14 +146,7 @@ class dynamic_format_arg_store constexpr dynamic_format_arg_store() = default; constexpr dynamic_format_arg_store( - const dynamic_format_arg_store& store) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(), -#endif - data_(store.data_), - named_info_(store.named_info_) { - } + const dynamic_format_arg_store& store) = delete; /** \rst diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index d54c8ae7..5285d420 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -11,14 +11,29 @@ #include #include #include +#include #include -#include +#include #include #include "format.h" FMT_BEGIN_NAMESPACE +// Enable tzset. +#ifndef FMT_USE_TZSET +// UWP doesn't provide _tzset. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# define FMT_USE_TZSET 1 +# else +# define FMT_USE_TZSET 0 +# endif +#endif + // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 @@ -290,36 +305,42 @@ inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } -template -inline auto do_write(const std::tm& time, const std::locale& loc, char format, - char modifier) -> std::basic_string { - auto&& os = std::basic_ostringstream(); - os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, Char(' '), &time, format, modifier); - if (end.failed()) FMT_THROW(format_error("failed to format time")); - return std::move(os).str(); -} - -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); - return std::copy(str.begin(), str.end(), out); -} - inline const std::locale& get_classic_locale() { static const auto& locale = std::locale::classic(); return locale; } -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; +template +constexpr const size_t codecvt_result::max_size; + +template +void write_codecvt(codecvt_result& out, string_view in_buf, + const std::locale& loc) { + using codecvt = std::codecvt; +#if FMT_CLANG_VERSION +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" + auto& f = std::use_facet(loc); +# pragma clang diagnostic pop +#else + auto& f = std::use_facet(loc); +#endif + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { if (detail::is_utf8() && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. @@ -332,56 +353,89 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, using code_unit = char32_t; #endif - using codecvt = std::codecvt; -#if FMT_CLANG_VERSION -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated" - auto& f = std::use_facet(loc); -# pragma clang diagnostic pop -#else - auto& f = std::use_facet(loc); -#endif - - auto mb = std::mbstate_t(); - const char* from_next = nullptr; - code_unit* to_next = nullptr; - constexpr size_t buf_size = 32; - code_unit buf[buf_size] = {}; - auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf, - buf + buf_size, to_next); - if (result != std::codecvt_base::ok) - FMT_THROW(format_error("failed to format time")); - str.clear(); - for (code_unit* p = buf; p != to_next; ++p) { + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto&& buf = basic_memory_buffer(); + for (code_unit* p = unit.buf; p != unit.end; ++p) { uint32_t c = static_cast(*p); if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { // surrogate pair ++p; - if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (p == unit.end || (c & 0xfc00) != 0xd800 || + (*p & 0xfc00) != 0xdc00) { FMT_THROW(format_error("failed to format time")); } c = (c << 10) + static_cast(*p) - 0x35fdc00; } if (c < 0x80) { - str.push_back(static_cast(c)); + buf.push_back(static_cast(c)); } else if (c < 0x800) { - str.push_back(static_cast(0xc0 | (c >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - str.push_back(static_cast(0xe0 | (c >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if (c >= 0x10000 && c <= 0x10ffff) { - str.push_back(static_cast(0xf0 | (c >> 18))); - str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else { FMT_THROW(format_error("failed to format time")); } } + return copy_str(buf.data(), buf.data() + buf.size(), out); } - return std::copy(str.begin(), str.end(), out); + return copy_str(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy_str(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + using iterator = std::ostreambuf_iterator; + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return buf.out(); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } } // namespace detail @@ -878,7 +932,13 @@ template struct has_member_data_tm_gmtoff> : std::true_type {}; -#if defined(_WIN32) +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +#if FMT_USE_TZSET inline void tzset_once() { static bool init = []() -> bool { _tzset(); @@ -1021,7 +1081,9 @@ template class tm_writer { } void format_utc_offset_impl(std::false_type) { #if defined(_WIN32) +# if FMT_USE_TZSET tzset_once(); +# endif long offset = 0; _get_timezone(&offset); if (tm_.tm_isdst) { @@ -1035,6 +1097,14 @@ template class tm_writer { #endif } + void format_tz_name_impl(std::true_type) { + if (is_classic_) + out_ = write_tm_str(out_, tm_.tm_zone, loc_); + else + format_localized('Z'); + } + void format_tz_name_impl(std::false_type) { format_localized('Z'); } + void format_localized(char format, char modifier = 0) { out_ = write(out_, tm_, loc_, format, modifier); } @@ -1144,7 +1214,7 @@ template class tm_writer { void on_utc_offset() { format_utc_offset_impl(has_member_data_tm_gmtoff{}); } - void on_tz_name() { format_localized('Z'); } + void on_tz_name() { format_tz_name_impl(has_member_data_tm_zone{}); } void on_year(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) @@ -1318,21 +1388,21 @@ inline bool isfinite(T) { return true; } -// Converts value to int and checks that it's in the range [0, upper). -template ::value)> -inline int to_nonnegative_int(T value, int upper) { +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper), "invalid value"); (void)upper; - return static_cast(value); + return static_cast(value); } -template ::value)> -inline int to_nonnegative_int(T value, int upper) { +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { FMT_ASSERT( std::isnan(value) || (value >= 0 && value <= static_cast(upper)), "invalid value"); (void)upper; - return static_cast(value); + return static_cast(value); } template ::value)> @@ -1389,15 +1459,37 @@ inline std::chrono::duration get_milliseconds( #endif } -template ::value)> -inline std::chrono::duration get_milliseconds( +// Returns the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +constexpr int count_fractional_digits(long long num, long long den, int n = 0) { + return num % den == 0 + ? n + : (n > 18 ? 6 : count_fractional_digits(num * 10, den, n + 1)); +} + +constexpr long long pow10(std::uint32_t n) { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +template ::is_signed)> +constexpr std::chrono::duration abs( std::chrono::duration d) { - using common_type = typename std::common_type::type; - auto ms = mod(d.count() * static_cast(Period::num) / - static_cast(Period::den) * 1000, - 1000); - return std::chrono::duration(static_cast(ms)); + // We need to compare the duration using the count() method directly + // due to a compiler bug in clang-11 regarding the spaceship operator, + // when -Wzero-as-null-pointer-constant is enabled. + // In clang-12 the bug has been fixed. See + // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: + // https://www.godbolt.org/z/Knbb5joYx. + return d.count() >= d.zero().count() ? d : -d; +} + +template ::is_signed)> +constexpr std::chrono::duration abs( + std::chrono::duration d) { + return d; } template (out, n, num_digits).end; } + template void write_fractional_seconds(Duration d) { + constexpr auto num_fractional_digits = + count_fractional_digits(Duration::period::num, Duration::period::den); + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + if (std::ratio_less::value) { + *out++ = '.'; + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? (detail::abs(d) - + std::chrono::duration_cast(d)) + .count() + : std::chrono::duration_cast( + detail::abs(d) - + std::chrono::duration_cast(d)) + .count(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(subseconds, max_value())); + int num_digits = detail::count_digits(n); + if (num_fractional_digits > num_digits) + out = std::fill_n(out, num_fractional_digits - num_digits, '0'); + out = format_decimal(out, n, num_digits).end; + } + } + void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } @@ -1637,19 +1759,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) { write(second(), 2); -#if FMT_SAFE_DURATION_CAST - // convert rep->Rep - using duration_rep = std::chrono::duration; - using duration_Rep = std::chrono::duration; - auto tmpval = fmt_safe_duration_cast(duration_rep{val}); -#else - auto tmpval = std::chrono::duration(val); -#endif - auto ms = get_milliseconds(tmpval); - if (ms != std::chrono::milliseconds(0)) { - *out++ = '.'; - write(ms.count(), 3); - } + write_fractional_seconds(std::chrono::duration{val}); return; } auto time = tm(); @@ -1678,7 +1788,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - write(second(), 2); + on_second(numeric_system::standard); } void on_am_pm() { diff --git a/include/fmt/core.h b/include/fmt/core.h index e87b706c..6f75b353 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -8,7 +8,8 @@ #ifndef FMT_CORE_H_ #define FMT_CORE_H_ -#include // std::FILE +#include // std::byte +#include // std::FILE #include #include #include @@ -367,6 +368,12 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # endif #endif +#ifdef __cpp_lib_byte +using byte = std::byte; +#else +enum class byte : unsigned char {}; +#endif + #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) @@ -560,7 +567,7 @@ constexpr auto to_string_view(const S& s) FMT_BEGIN_DETAIL_NAMESPACE void to_string_view(...); -using fmt::v8::to_string_view; +using fmt::to_string_view; // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in @@ -1102,6 +1109,11 @@ template constexpr auto count_named_args() -> size_t { return count::value...>(); } +template +constexpr auto count_statically_named_args() -> size_t { + return count::value...>(); +} + enum class type { none_type, // Integer types should go first, @@ -1357,20 +1369,21 @@ template struct arg_mapper { -> basic_string_view { return std_string_view(val); } - FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) - -> decltype(this->map("")) { + + using cstring_result = conditional_t::value, + const char*, unformattable_pointer>; + + FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> cstring_result { return map(reinterpret_cast(val)); } FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) - -> decltype(this->map("")) { + -> cstring_result { return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) - -> decltype(this->map("")) { + FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> cstring_result { return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) - -> decltype(this->map("")) { + FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> cstring_result { return map(reinterpret_cast(val)); } @@ -1402,15 +1415,20 @@ template struct arg_mapper { } template ::value && - !has_formatter::value && - !has_fallback_formatter::value)> + FMT_ENABLE_IF( + std::is_enum::value&& std::is_convertible::value && + !has_formatter::value && + !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(std::declval().map( static_cast::type>(val))) { return map(static_cast::type>(val)); } + FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned { + return map(static_cast(val)); + } + template > struct formattable : bool_constant() || @@ -1482,14 +1500,11 @@ class appender : public std::back_insert_iterator> { using _Unchecked_type = appender; // Mark iterator as checked. auto operator++() -> appender& { - base::operator++(); return *this; } auto operator++(int) -> appender { - auto tmp = *this; - ++*this; - return tmp; + return *this; } }; @@ -2472,7 +2487,7 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, const Char* end, SpecHandler&& handler) -> const Char* { - if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && + if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') { presentation_type type = parse_presentation_type(*begin++); if (type == presentation_type::none) @@ -3033,7 +3048,8 @@ template class basic_format_string { std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); #ifdef FMT_HAS_CONSTEVAL - if constexpr (detail::count_named_args() == 0) { + if constexpr (detail::count_named_args() == + detail::count_statically_named_args()) { using checker = detail::format_string_checker...>; detail::parse_format_string(str_, checker(s, {})); diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index f4ffeb67..04ded482 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -704,7 +704,12 @@ FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits( if (handler.fixed) { // Adjust fixed precision by exponent because it is relative to decimal // point. - handler.precision += exp + handler.exp10; + int precision_offset = exp + handler.exp10; + if (precision_offset > 0 && + handler.precision > max_value() - precision_offset) { + FMT_THROW(format_error("number is too big")); + } + handler.precision += precision_offset; // Check if precision is satisfied just by leading zeros, e.g. // format("{:.2f}", 0.001) gives "0.00" without generating any digits. if (handler.precision <= 0) { diff --git a/include/fmt/format.h b/include/fmt/format.h index 3cf123ee..ea5b9ece 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -265,6 +265,38 @@ FMT_END_NAMESPACE FMT_BEGIN_NAMESPACE namespace detail { + +template class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). // Example: uint64_t d = bit_cast(2.718); @@ -2574,6 +2606,7 @@ FMT_FORMAT_AS(unsigned long, unsigned long long); FMT_FORMAT_AS(Char*, const Char*); FMT_FORMAT_AS(std::basic_string, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::byte, unsigned char); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); template @@ -3016,16 +3049,14 @@ constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg { # endif /** - \rst - User-defined literal equivalent of :func:`fmt::format`. + DEPRECATED! User-defined literal equivalent of fmt::format. **Example**:: using namespace fmt::literals; std::string message = "The answer is {}"_format(42); - \endrst */ -constexpr auto operator"" _format(const char* s, size_t n) +FMT_DEPRECATED constexpr auto operator"" _format(const char* s, size_t n) -> detail::udl_formatter { return {{s, n}}; } diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index dd005fa1..3d716ece 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -18,36 +18,6 @@ template class basic_printf_context; namespace detail { -template class formatbuf : public std::basic_streambuf { - private: - using int_type = typename std::basic_streambuf::int_type; - using traits_type = typename std::basic_streambuf::traits_type; - - buffer& buffer_; - - public: - explicit formatbuf(buffer& buf) : buffer_(buf) {} - - protected: - // The put area is always empty. This makes the implementation simpler and has - // the advantage that the streambuf and the buffer are always in sync and - // sputc never writes into uninitialized memory. A disadvantage is that each - // call to sputc always results in a (virtual) call to overflow. There is no - // disadvantage here for sputn since this always results in a call to xsputn. - - auto overflow(int_type ch = traits_type::eof()) -> int_type override { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast(ch)); - return ch; - } - - auto xsputn(const Char* s, std::streamsize count) - -> std::streamsize override { - buffer_.append(s, s + count); - return count; - } -}; - // Checks if T has a user-defined operator<<. template class is_streamable { @@ -75,6 +45,8 @@ struct is_streamable< enable_if_t< std::is_arithmetic::value || std::is_array::value || std::is_pointer::value || std::is_same::value || + std::is_same>::value || + std::is_same>::value || (std::is_convertible::value && !std::is_enum::value)>> : std::false_type {}; @@ -97,7 +69,7 @@ void write_buffer(std::basic_ostream& os, buffer& buf) { template void format_value(buffer& buf, const T& value, locale_ref loc = locale_ref()) { - auto&& format_buf = formatbuf(buf); + auto&& format_buf = formatbuf>(buf); auto&& output = std::basic_ostream(&format_buf); #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) if (loc) output.imbue(loc.get()); diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index ee12bd95..71474f32 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -19,21 +19,6 @@ FMT_BEGIN_NAMESPACE -template struct formatting_range { -#ifdef FMT_DEPRECATED_BRACED_RANGES - Char prefix = '{'; - Char postfix = '}'; -#else - Char prefix = '['; - Char postfix = ']'; -#endif - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } -}; - template struct formatting_tuple { Char prefix = '('; Char postfix = ')'; @@ -71,7 +56,7 @@ OutputIterator copy(wchar_t ch, OutputIterator out) { return out; } -/// Return true value if T has std::string interface, like std::string_view. +// Returns true if T has a std::string-like interface, like std::string_view. template class is_std_string_like { template static auto check(U* p) @@ -86,6 +71,32 @@ template class is_std_string_like { template struct is_std_string_like> : std::true_type {}; +template class is_map { + template static auto check(U*) -> typename U::mapped_type; + template static void check(...); + + public: +#ifdef FMT_FORMAT_MAP_AS_LIST + static FMT_CONSTEXPR_DECL const bool value = false; +#else + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value; +#endif +}; + +template class is_set { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: +#ifdef FMT_FORMAT_SET_AS_LIST + static FMT_CONSTEXPR_DECL const bool value = false; +#else + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value && !is_map::value; +#endif +}; + template struct conditional_helper {}; template struct is_range_ : std::false_type {}; @@ -573,6 +584,7 @@ struct formatter::value>> { template struct is_range { static FMT_CONSTEXPR_DECL const bool value = detail::is_range_::value && !detail::is_std_string_like::value && + !detail::is_map::value && !std::is_convertible>::value && !std::is_constructible, T>::value; }; @@ -588,11 +600,9 @@ struct formatter< detail::has_fallback_formatter, Char>::value) #endif >> { - formatting_range formatting; - template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return formatting.parse(ctx); + return ctx.begin(); } template < @@ -600,17 +610,64 @@ struct formatter< FMT_ENABLE_IF( std::is_same::value, const T, T>>::value)> - auto format(U& values, FormatContext& ctx) -> decltype(ctx.out()) { - auto out = detail::copy(formatting.prefix, ctx.out()); - size_t i = 0; - auto it = std::begin(values); - auto end = std::end(values); + auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) { +#ifdef FMT_DEPRECATED_BRACED_RANGES + Char prefix = '{'; + Char postfix = '}'; +#else + Char prefix = detail::is_set::value ? '{' : '['; + Char postfix = detail::is_set::value ? '}' : ']'; +#endif + auto out = ctx.out(); + *out++ = prefix; + int i = 0; + auto it = std::begin(range); + auto end = std::end(range); for (; it != end; ++it) { if (i > 0) out = detail::write_delimiter(out); out = detail::write_range_entry(out, *it); ++i; } - return detail::copy(formatting.postfix, out); + *out++ = postfix; + return out; + } +}; + +template +struct formatter< + T, Char, + enable_if_t< + detail::is_map::value +// Workaround a bug in MSVC 2019 and earlier. +#if !FMT_MSC_VER + && (is_formattable, Char>::value || + detail::has_fallback_formatter, Char>::value) +#endif + >> { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template < + typename FormatContext, typename U, + FMT_ENABLE_IF( + std::is_same::value, + const T, T>>::value)> + auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + *out++ = '{'; + int i = 0; + for (const auto& item : map) { + if (i > 0) out = detail::write_delimiter(out); + out = detail::write_range_entry(out, item.first); + *out++ = ':'; + *out++ = ' '; + out = detail::write_range_entry(out, item.second); + ++i; + } + *out++ = '}'; + return out; } }; diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index a0dd032f..55825077 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -5,8 +5,8 @@ // // For the license information refer to format.h. -#ifndef FMT_WCHAR_H_ -#define FMT_WCHAR_H_ +#ifndef FMT_XCHAR_H_ +#define FMT_XCHAR_H_ #include #include @@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) { template void print(std::FILE* f, wformat_string fmt, T&&... args) { - return vprint(f, wstring_view(fmt), make_wformat_args(args...)); + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); } template void print(wformat_string fmt, T&&... args) { - return vprint(wstring_view(fmt), make_wformat_args(args...)); + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); } /** @@ -233,4 +233,4 @@ template inline auto to_wstring(const T& value) -> std::wstring { FMT_MODULE_EXPORT_END FMT_END_NAMESPACE -#endif // FMT_WCHAR_H_ +#endif // FMT_XCHAR_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6a27e33f..a0fbd05f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,19 +8,6 @@ target_link_libraries(test-main gtest) include(CheckCXXCompilerFlag) -# Workaround GTest bug https://github.com/google/googletest/issues/705. -check_cxx_compiler_flag( - -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) -if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) - target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks) -endif () - -# Use less strict pedantic flags for the tests because GMock doesn't compile -# cleanly with -pedantic and -std=c++98. -if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) - #set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros) -endif () - function(add_fmt_executable name) add_executable(${name} ${ARGN}) if (MINGW) diff --git a/test/args-test.cc b/test/args-test.cc index 2b1db8c8..e793cb5c 100644 --- a/test/args-test.cc +++ b/test/args-test.cc @@ -10,7 +10,7 @@ #include "gtest/gtest.h" TEST(args_test, basic) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(42); store.push_back("abc1"); store.push_back(1.5f); @@ -19,7 +19,7 @@ TEST(args_test, basic) { TEST(args_test, strings_and_refs) { // Unfortunately the tests are compiled with old ABI so strings use COW. - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char str[] = "1234567890"; store.push_back(str); store.push_back(std::cref(str)); @@ -48,7 +48,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, custom_format) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto c = custom_type(); store.push_back(c); ++c.i; @@ -77,7 +77,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, to_string_and_formatter) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto s = to_stringable(); store.push_back(s); store.push_back(std::cref(s)); @@ -85,13 +85,13 @@ TEST(args_test, to_string_and_formatter) { } TEST(args_test, named_int) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("a1", 42)); EXPECT_EQ("42", fmt::vformat("{a1}", store)); } TEST(args_test, named_strings) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char str[] = "1234567890"; store.push_back(fmt::arg("a1", str)); store.push_back(fmt::arg("a2", std::cref(str))); @@ -100,7 +100,7 @@ TEST(args_test, named_strings) { } TEST(args_test, named_arg_by_ref) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char band[] = "Rolling Stones"; store.push_back(fmt::arg("band", std::cref(band))); band[9] = 'c'; // Changing band affects the output. @@ -108,7 +108,7 @@ TEST(args_test, named_arg_by_ref) { } TEST(args_test, named_custom_format) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto c = custom_type(); store.push_back(fmt::arg("c1", c)); ++c.i; @@ -121,7 +121,7 @@ TEST(args_test, named_custom_format) { } TEST(args_test, clear) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(42); auto result = fmt::vformat("{}", store); @@ -138,7 +138,7 @@ TEST(args_test, clear) { } TEST(args_test, reserve) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.reserve(2, 1); store.push_back(1.5f); store.push_back(fmt::arg("a1", 42)); @@ -163,7 +163,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, throw_on_copy) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(std::string("foo")); try { store.push_back(copy_throwable()); @@ -171,16 +171,3 @@ TEST(args_test, throw_on_copy) { } EXPECT_EQ(fmt::vformat("{}", store), "foo"); } - -TEST(args_test, copy_constructor) { - auto store = fmt::dynamic_format_arg_store(); - store.push_back(fmt::arg("test1", "value1")); - store.push_back(fmt::arg("test2", "value2")); - store.push_back(fmt::arg("test3", "value3")); - - auto store2 = store; - store2.push_back(fmt::arg("test4", "value4")); - - auto result = fmt::vformat("{test1} {test2} {test3} {test4}", store2); - EXPECT_EQ(result, "value1 value2 value3 value4"); -} diff --git a/test/chrono-test.cc b/test/chrono-test.cc index a2bc20c2..42360e5f 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -543,15 +543,12 @@ TEST(chrono_test, negative_durations) { } TEST(chrono_test, special_durations) { - EXPECT_EQ( - "40.", - fmt::format("{:%S}", std::chrono::duration(1e20)).substr(0, 3)); + auto value = fmt::format("{:%S}", std::chrono::duration(1e20)); + EXPECT_EQ(value, "40"); auto nan = std::numeric_limits::quiet_NaN(); EXPECT_EQ( "nan nan nan nan nan:nan nan", fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration(nan))); - (void)fmt::format("{:%S}", - std::chrono::duration(1.79400457e+31f)); EXPECT_EQ(fmt::format("{}", std::chrono::duration(1)), "1Es"); EXPECT_EQ(fmt::format("{}", std::chrono::duration(1)), @@ -585,4 +582,44 @@ TEST(chrono_test, weekday) { } } +TEST(chrono_test, cpp20_duration_subsecond_support) { + using attoseconds = std::chrono::duration; + // Check that 18 digits of subsecond precision are supported. + EXPECT_EQ(fmt::format("{:%S}", attoseconds{999999999999999999}), + "00.999999999999999999"); + EXPECT_EQ(fmt::format("{:%S}", attoseconds{673231113420148734}), + "00.673231113420148734"); + EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673231113420148734}), + "-00.673231113420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13420148734}), + "13.420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), + "-13.420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); + { + // Check that {:%H:%M:%S} is equivalent to {:%T}. + auto dur = std::chrono::milliseconds{3601234}; + auto formatted_dur = fmt::format("{:%T}", dur); + EXPECT_EQ(formatted_dur, "01:00:01.234"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + } + using nanoseconds_dbl = std::chrono::duration; + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789"); + // Verify that only the seconds part is extracted and printed. + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789"); + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000"); + { + // Now the hour is printed, and we also test if negative doubles work. + auto dur = nanoseconds_dbl{-99123456789}; + auto formatted_dur = fmt::format("{:%T}", dur); + EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + } + // Check that durations with precision greater than std::chrono::seconds have + // fixed precision, and print zeros even if there is no fractional part. + EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), + "07.000000"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR diff --git a/test/core-test.cc b/test/core-test.cc index decf31a7..78ec3de2 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -737,6 +737,8 @@ struct convertible_to_pointer { operator const int*() const { return nullptr; } }; +enum class test_scoped_enum {}; + TEST(core_test, is_formattable) { static_assert(fmt::is_formattable::value, ""); static_assert(fmt::is_formattable::value, ""); @@ -777,6 +779,7 @@ TEST(core_test, is_formattable) { static_assert(!fmt::is_formattable::value, ""); static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } diff --git a/test/format-test.cc b/test/format-test.cc index 9233a87b..952dbe20 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -934,6 +934,9 @@ TEST(format_test, precision) { EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0, fmt::detail::max_value()), format_error, "number is too big"); + EXPECT_THROW_MSG( + (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304), + format_error, "number is too big"); EXPECT_EQ("st", fmt::format("{0:.2}", "str")); } @@ -1758,6 +1761,21 @@ TEST(format_test, custom_format_compile_time_string) { using namespace fmt::literals; +# if FMT_GCC_VERSION +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1 +# elif FMT_CLANG_VERSION && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1 +# endif +# endif +# ifndef FMT_CHECK_DEPRECATED_UDL_FORMAT +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 0 +# endif + +# if FMT_CHECK_DEPRECATED_UDL_FORMAT +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" + TEST(format_test, format_udl) { EXPECT_EQ("{}c{}"_format("ab", 1), fmt::format("{}c{}", "ab", 1)); EXPECT_EQ("foo"_format(), "foo"); @@ -1765,6 +1783,9 @@ TEST(format_test, format_udl) { EXPECT_EQ("{}"_format(date(2015, 10, 21)), "2015-10-21"); } +# pragma GCC diagnostic pop +# endif + TEST(format_test, named_arg_udl) { auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra", "second"_a = "cad", "third"_a = 99); diff --git a/test/fuzzing/float.cc b/test/fuzzing/float.cc index b3780e1d..d4c9e4f5 100644 --- a/test/fuzzing/float.cc +++ b/test/fuzzing/float.cc @@ -12,7 +12,7 @@ void check_round_trip(fmt::string_view format_str, double value) { auto buffer = fmt::memory_buffer(); - fmt::format_to(buffer, format_str, value); + fmt::format_to(std::back_inserter(buffer), format_str, value); if (std::isnan(value)) { auto nan = std::signbit(value) ? "-nan" : "nan"; diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 1282d62a..0cc4e1aa 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,13 @@ else () target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) endif () +# Workaround GTest bug https://github.com/google/googletest/issues/705. +check_cxx_compiler_flag( + -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) +if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) + target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks) +endif () + if (MSVC) # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) diff --git a/test/ostream-test.cc b/test/ostream-test.cc index a01182d1..f81039e5 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -294,3 +294,8 @@ struct abstract { void format_abstract_compiles(const abstract& a) { fmt::format(FMT_COMPILE("{}"), a); } + +TEST(ostream_test, is_formattable) { + EXPECT_TRUE(fmt::is_formattable()); + EXPECT_TRUE(fmt::is_formattable>()); +} diff --git a/test/ranges-test.cc b/test/ranges-test.cc index f8e67390..86dd5520 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -55,7 +55,12 @@ TEST(ranges_test, format_vector2) { TEST(ranges_test, format_map) { auto m = std::map{{"one", 1}, {"two", 2}}; - EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]"); + EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); +} + +TEST(ranges_test, format_set) { + EXPECT_EQ(fmt::format("{}", std::set{"one", "two"}), + "{\"one\", \"two\"}"); } TEST(ranges_test, format_pair) { @@ -190,7 +195,7 @@ TEST(ranges_test, range) { EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]"); } -enum class test_enum { foo }; +enum test_enum { foo }; TEST(ranges_test, enum_range) { auto v = std::vector{test_enum::foo};