diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 0a917d41e8ea2..815ff9eddfa3b 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -664,6 +664,30 @@ enum class numeric_system { alternative }; +// Glibc extensions for formatting numeric values. +enum class pad_type { + unspecified, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with zeros even if the conversion specifier + // character uses space-padding by default. + zero, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + // Parses a put_time-like format string and invokes handler actions. template FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, @@ -672,6 +696,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin == end || *begin == '}') return begin; if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; + pad_type pad = pad_type::unspecified; while (ptr != end) { auto c = *ptr; if (c == '}') break; @@ -682,6 +707,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + case '0': + pad = pad_type::zero; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': @@ -758,16 +799,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Hour, minute, second: case 'H': - handler.on_24_hour(numeric_system::standard); + handler.on_24_hour(numeric_system::standard, pad); break; case 'I': - handler.on_12_hour(numeric_system::standard); + handler.on_12_hour(numeric_system::standard, pad); break; case 'M': - handler.on_minute(numeric_system::standard); + handler.on_minute(numeric_system::standard, pad); break; case 'S': - handler.on_second(numeric_system::standard); + handler.on_second(numeric_system::standard, pad); break; // Other: case 'c': @@ -872,16 +913,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': - handler.on_24_hour(numeric_system::alternative); + handler.on_24_hour(numeric_system::alternative, pad); break; case 'I': - handler.on_12_hour(numeric_system::alternative); + handler.on_12_hour(numeric_system::alternative, pad); break; case 'M': - handler.on_minute(numeric_system::alternative); + handler.on_minute(numeric_system::alternative, pad); break; case 'S': - handler.on_second(numeric_system::alternative); + handler.on_second(numeric_system::alternative, pad); break; case 'z': handler.on_utc_offset(numeric_system::alternative); @@ -965,10 +1006,10 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_day_of_year() {} FMT_CONSTEXPR void on_day_of_month(numeric_system) {} FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_datetime(numeric_system) {} FMT_CONSTEXPR void on_loc_date(numeric_system) {} FMT_CONSTEXPR void on_loc_time(numeric_system) {} @@ -1238,6 +1279,17 @@ class tm_writer { *out_++ = *d++; *out_++ = *d; } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } void write_year_extended(long long year) { // At least 4 characters. @@ -1514,23 +1566,25 @@ class tm_writer { } } - void on_24_hour(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); format_localized('H', 'O'); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write2(tm_hour12()); + return write2(tm_hour12(), pad); format_localized('I', 'O'); } - void on_minute(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); format_localized('M', 'O'); } - void on_second(numeric_system ns) { + void on_second(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) { - write2(tm_sec()); + write2(tm_sec(), pad); if (subsecs_) { if (std::is_floating_point::value) { auto buf = memory_buffer(); @@ -1594,10 +1648,10 @@ struct chrono_format_checker : null_chrono_spec_handler { template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} @@ -1819,13 +1873,15 @@ struct chrono_formatter { } } - void write(Rep value, int width) { + void write(Rep value, int width, pad_type pad = pad_type::unspecified) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); - if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } out = format_decimal(out, n, num_digits).end; } @@ -1874,34 +1930,34 @@ struct chrono_formatter { void on_day_of_month(numeric_system) {} void on_day_of_month_space(numeric_system) {} - void on_24_hour(numeric_system ns) { + void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour(), 2); + if (ns == numeric_system::standard) return write(hour(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_tm(time, &tm_writer_type::on_24_hour, ns); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour12(), 2); + if (ns == numeric_system::standard) return write(hour12(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_tm(time, &tm_writer_type::on_12_hour, ns); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); } - void on_minute(numeric_system ns) { + void on_minute(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(minute(), 2); + if (ns == numeric_system::standard) return write(minute(), 2, pad); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_tm(time, &tm_writer_type::on_minute, ns); + format_tm(time, &tm_writer_type::on_minute, ns, pad); } - void on_second(numeric_system ns) { + void on_second(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { @@ -1910,10 +1966,12 @@ struct chrono_formatter { write_floating_seconds(buf, std::chrono::duration(val), precision); if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } out = std::copy(buf.begin(), buf.end(), out); } else { - write(second(), 2); + write(second(), 2, pad); write_fractional_seconds( out, std::chrono::duration(val), precision); } @@ -1921,7 +1979,7 @@ struct chrono_formatter { } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_tm(time, &tm_writer_type::on_second, ns); + format_tm(time, &tm_writer_type::on_second, ns, pad); } void on_12_hour_time() { @@ -1945,7 +2003,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - on_second(numeric_system::standard); + on_second(numeric_system::standard, pad_type::unspecified); } void on_am_pm() { diff --git a/include/fmt/core.h b/include/fmt/core.h index 6d0bead89ee09..6c9d968bbc961 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -281,6 +281,12 @@ # endif #endif +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline +#else +# define FMT_INLINE_VARIABLE +#endif + // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") #if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) @@ -2662,7 +2668,7 @@ FMT_CONSTEXPR auto check_char_specs(const format_specs& specs) -> bool { return true; } -constexpr int invalid_arg_index = -1; +constexpr FMT_INLINE_VARIABLE int invalid_arg_index = -1; #if FMT_USE_NONTYPE_TEMPLATE_ARGS template diff --git a/include/fmt/format.h b/include/fmt/format.h index 4a1663bb3e276..2c4a8b7d309f2 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -680,7 +680,7 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) return next; } -constexpr uint32_t invalid_code_point = ~uint32_t(); +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. @@ -798,12 +798,22 @@ using is_integer = !std::is_same::value>; #ifndef FMT_USE_FLOAT128 -# ifdef __SIZEOF_FLOAT128__ -# define FMT_USE_FLOAT128 1 -# else +# ifdef __clang__ +// Clang emulates GCC, so it has to appear early. +# if FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +# endif +# elif defined(__GNUC__) +// GNU C++: +# if defined(_GLIBCXX_USE_FLOAT128) && !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +# endif +# endif +# ifndef FMT_USE_FLOAT128 # define FMT_USE_FLOAT128 0 # endif #endif + #if FMT_USE_FLOAT128 using float128 = __float128; #else @@ -1216,7 +1226,7 @@ FMT_CONSTEXPR auto count_digits(UInt n) -> int { FMT_INLINE auto do_count_digits(uint32_t n) -> int { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. -# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) +# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 @@ -4298,9 +4308,7 @@ template <> struct formatter { }; // group_digits_view is not derived from view because it copies the argument. -template struct group_digits_view { - T value; -}; +template struct group_digits_view { T value; }; /** \rst diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 3ea41c8c294e8..8912fe93a5c71 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -157,8 +157,7 @@ struct has_mutable_begin_end< 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 {}; + int>> : std::true_type {}; template struct is_range_ @@ -662,6 +661,41 @@ struct formatter, Char> { } }; +namespace detail { +// Check if T has an interface like a container adaptor (e.g. std::stack, +// std::queue, std::priority_queue). +template class is_container_adaptor_like { + template static auto check(U* p) -> typename U::container_type; + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +template struct all { + const Container& c; + auto begin() const -> typename Container::const_iterator { return c.begin(); } + auto end() const -> typename Container::const_iterator { return c.end(); }; +}; +} // namespace detail + +template +struct formatter::value>> + : formatter, Char> { + using all = detail::all; + template + auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { + struct getter : T { + static auto get(const T& t) -> all { + return {t.*(&getter::c)}; // Access c through the derived class. + } + }; + return formatter::format(getter::get(t), ctx); + } +}; + FMT_MODULE_EXPORT_BEGIN /** diff --git a/src/os.cc b/src/os.cc index 7873fe6f0cc6b..a6b1d6e17f8cc 100644 --- a/src/os.cc +++ b/src/os.cc @@ -206,7 +206,11 @@ void buffered_file::close() { } int buffered_file::descriptor() const { +#ifdef fileno // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL. + int fd = fileno(file_); +#else int fd = FMT_POSIX_CALL(fileno(file_)); +#endif if (fd == -1) FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor"))); return fd; diff --git a/test/chrono-test.cc b/test/chrono-test.cc index b3bb22a2ddb68..cdd21319be2cb 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -921,3 +921,56 @@ TEST(chrono_test, timestamps_sub_seconds) { EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d)); } } + +TEST(chrono_test, glibc_extensions) { + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()), + fmt::format_error, "invalid format"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()), + fmt::format_error, "invalid format"); + EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()), + fmt::format_error, "invalid format"); + + { + const auto d = std::chrono::hours(1) + std::chrono::minutes(2) + + std::chrono::seconds(3); + + EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3"); + + EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3"); + } + + { + const auto tm = make_tm(1970, 1, 1, 1, 2, 3); + EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3"); + + EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03"); + EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3"); + EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3"); + } + + { + const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140); + EXPECT_EQ(fmt::format("{:%S}", d), "03.140"); + EXPECT_EQ(fmt::format("{:%0S}", d), "03.140"); + EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140"); + EXPECT_EQ(fmt::format("{:%-S}", d), "3.140"); + } + + { + const auto d = std::chrono::duration(3.14); + EXPECT_EQ(fmt::format("{:%S}", d), "03.140000"); + EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000"); + EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000"); + EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000"); + } +} diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 8cafeec443095..26e3362a98b9d 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -12,6 +12,8 @@ #include "fmt/ranges.h" #include +#include +#include #include #include @@ -406,3 +408,62 @@ TEST(ranges_test, range_of_range_of_mixed_const) { TEST(ranges_test, vector_char) { EXPECT_EQ(fmt::format("{}", std::vector{'a', 'b'}), "['a', 'b']"); } + +TEST(ranges_test, container_adaptor) { + { + using fmt::detail::is_container_adaptor_like; + using T = std::nullptr_t; + static_assert(is_container_adaptor_like>::value, ""); + static_assert(is_container_adaptor_like>::value, ""); + static_assert(is_container_adaptor_like>::value, ""); + static_assert(!is_container_adaptor_like>::value, ""); + } + + { + std::stack s; + s.push(1); + s.push(2); + EXPECT_EQ(fmt::format("{}", s), "[1, 2]"); + EXPECT_EQ(fmt::format("{}", const_cast(s)), "[1, 2]"); + } + + { + std::queue q; + q.push(1); + q.push(2); + EXPECT_EQ(fmt::format("{}", q), "[1, 2]"); + } + + { + std::priority_queue q; + q.push(3); + q.push(1); + q.push(2); + q.push(4); + EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]"); + } + + { + std::stack s; + s.push('a'); + s.push('b'); + // See https://cplusplus.github.io/LWG/issue3881. + EXPECT_EQ(fmt::format("{}", s), "['a', 'b']"); + } + + { + struct my_container_adaptor { + using value_type = int; + using container_type = std::vector; + void push(const value_type& v) { c.push_back(v); } + + protected: + container_type c; + }; + + my_container_adaptor m; + m.push(1); + m.push(2); + EXPECT_EQ(fmt::format("{}", m), "[1, 2]"); + } +}