diff --git a/doc/api.rst b/doc/api.rst index 0834a874d021..4e3702526ddd 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -85,6 +85,19 @@ Compatibility .. doxygentypedef:: fmt::string_view .. doxygentypedef:: fmt::wstring_view +Locale +------ + +All formatting is locale-independent by default. Use the ``'n'`` format +specifier to insert the appropriate number separator characters from the +locale:: + + #include + #include + + std::locale::global(std::locale("en_US.UTF-8")); + auto s = fmt::format("{:n}", 1000000); // s == "1,000,000" + .. _format-api: Format API diff --git a/doc/syntax.rst b/doc/syntax.rst index 56b70f072785..af65616a3925 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -76,7 +76,7 @@ The general form of a *standard format specifier* is: .. productionlist:: sf format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`] - fill: + fill: align: "<" | ">" | "^" sign: "+" | "-" | " " width: `integer` | "{" `arg_id` "}" @@ -84,11 +84,11 @@ The general form of a *standard format specifier* is: type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s" int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X" -The *fill* character can be any character other than '{', '}' or '\\0'. The -presence of a fill character is signaled by the character following it, which -must be one of the alignment options. If the second character of *format_spec* -is not a valid alignment option, then it is assumed that both the fill character -and the alignment option are absent. +The *fill* character can be any Unicode code point other than ``'{'`` or +``'}'``. The presence of a fill character is signaled by the character following +it, which must be one of the alignment options. If the second character of +*format_spec* is not a valid alignment option, then it is assumed that both the +fill character and the alignment option are absent. The meaning of the various alignment options is as follows: @@ -320,71 +320,93 @@ following examples. Accessing arguments by position:: - format("{0}, {1}, {2}", 'a', 'b', 'c'); + fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'); // Result: "a, b, c" - format("{}, {}, {}", 'a', 'b', 'c'); + fmt::format("{}, {}, {}", 'a', 'b', 'c'); // Result: "a, b, c" - format("{2}, {1}, {0}", 'a', 'b', 'c'); + fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'); // Result: "c, b, a" - format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated + fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated // Result: "abracadabra" Aligning the text and specifying a width:: - format("{:<30}", "left aligned"); + fmt::format("{:<30}", "left aligned"); // Result: "left aligned " - format("{:>30}", "right aligned"); + fmt::format("{:>30}", "right aligned"); // Result: " right aligned" - format("{:^30}", "centered"); + fmt::format("{:^30}", "centered"); // Result: " centered " - format("{:*^30}", "centered"); // use '*' as a fill char + fmt::format("{:*^30}", "centered"); // use '*' as a fill char // Result: "***********centered***********" Dynamic width:: - format("{:<{}}", "left aligned", 30); + fmt::format("{:<{}}", "left aligned", 30); // Result: "left aligned " Dynamic precision:: - format("{:.{}f}", 3.14, 1); + fmt::format("{:.{}f}", 3.14, 1); // Result: "3.1" Replacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign:: - format("{:+f}; {:+f}", 3.14, -3.14); // show it always + fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always // Result: "+3.140000; -3.140000" - format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers + fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers // Result: " 3.140000; -3.140000" - format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}' + fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}' // Result: "3.140000; -3.140000" Replacing ``%x`` and ``%o`` and converting the value to different bases:: - format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); // Result: "int: 42; hex: 2a; oct: 52; bin: 101010" // with 0x or 0 or 0b as prefix: - format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); + fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); // Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" Padded hex byte with prefix and always prints both hex characters:: - format("{:#04x}", 0); + fmt::format("{:#04x}", 0); // Result: "0x00" -.. ifconfig:: False +Box drawing using Unicode fill:: + + fmt::print( + "┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Hello, world!", 20); + +prints:: + + ┌────────────────────┐ + │ Hello, world! │ + └────────────────────┘ - Using the comma as a thousands separator:: +Using type-specific formatting:: - format("{:,}", 1234567890); - '1,234,567,890' + #include - Using type-specific formatting:: + auto t = tm(); + t.tm_year = 2010 - 1900; + t.tm_mon = 6; + t.tm_mday = 4; + t.tm_hour = 12; + t.tm_min = 15; + t.tm_sec = 58; + fmt::print("{:%Y-%m-%d %H:%M:%S}", t); + // Prints: 2010-08-04 12:15:58 - >>> import datetime - >>> d = datetime.datetime(2010, 7, 4, 12, 15, 58) - Format("{:%Y-%m-%d %H:%M:%S}") << d) - '2010-07-04 12:15:58' +Using the comma as a thousands separator:: + + #include + + auto s = fmt::format(std::locale("en_US.UTF-8"), "{:n}", 1234567890); + // s == "1,234,567,890" + +.. ifconfig:: False Nesting arguments and more complex examples:: diff --git a/doc/usage.rst b/doc/usage.rst index dacb4f03936f..d42719e0acf5 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -114,6 +114,22 @@ fmt can be installed on Linux, macOS and Windows with conda install -c conda-forge fmt +Vcpkg +===== + +You can download and install fmt using the `vcpkg +`__ dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install fmt + +The fmt port in vcpkg is kept up to date by Microsoft team members and community +contributors. If the version is out of date, please `create an issue or pull +request `__ on the vcpkg repository. + Android NDK =========== diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index ca4ed30ae69c..80cbe6976feb 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -8,14 +8,14 @@ #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ -#include "format.h" -#include "locale.h" - #include #include #include #include +#include "format.h" +#include "locale.h" + FMT_BEGIN_NAMESPACE // Enable safe chrono durations, unless explicitly disabled. @@ -495,12 +495,12 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_text(ptr - 1, ptr); break; case 'n': { - const char newline[] = "\n"; + const Char newline[]{'\n', 0}; handler.on_text(newline, newline + 1); break; } case 't': { - const char tab[] = "\t"; + const Char tab[]{'\t', 0}; handler.on_text(tab, tab + 1); break; } @@ -759,18 +759,30 @@ inline std::chrono::duration get_milliseconds( return std::chrono::duration(static_cast(ms)); } -template -OutputIt format_chrono_duration_value(OutputIt out, Rep val, int precision) { - if (precision >= 0) return format_to(out, "{:.{}f}", val, precision); - return format_to(out, std::is_floating_point::value ? "{:g}" : "{}", +template +OutputIt format_duration_value(OutputIt out, Rep val, int precision) { + const Char pr_f[]{'{', ':', '.', '{', '}', 'f', '}', 0}; + if (precision >= 0) return format_to(out, pr_f, val, precision); + const Char fp_f[]{'{', ':', 'g', '}', 0}; + const Char format[]{'{', '}', 0}; + return format_to(out, std::is_floating_point::value ? fp_f : format, val); } -template -static OutputIt format_chrono_duration_unit(OutputIt out) { - if (const char* unit = get_units()) return format_to(out, "{}", unit); - if (Period::den == 1) return format_to(out, "[{}]s", Period::num); - return format_to(out, "[{}/{}]s", Period::num, Period::den); +template +OutputIt format_duration_unit(OutputIt out) { + if (const char* unit = get_units()) { + string_view s(unit); + if (const_check(std::is_same())) { + utf8_to_utf16 u(s); + return std::copy(u.c_str(), u.c_str() + u.size(), out); + } + return std::copy(s.begin(), s.end(), out); + } + const Char num_f[]{'[', '{', '}', ']', 's', 0}; + if (Period::den == 1) return format_to(out, num_f, Period::num); + const Char num_def_f[]{'[', '{', '}', '/', '{', '}', ']', 's', 0}; + return format_to(out, num_def_f, Period::num, Period::den); } template (); auto& facet = std::use_facet>(locale); std::basic_ostringstream os; os.imbue(locale); - facet.put(os, os, ' ', &time, format, format + std::strlen(format)); + facet.put(os, os, ' ', &time, format, modifier); auto str = os.str(); std::copy(str.begin(), str.end(), out); } @@ -907,7 +919,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) return write(hour(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_localized(time, "%OH"); + format_localized(time, 'H', 'O'); } void on_12_hour(numeric_system ns) { @@ -916,7 +928,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) return write(hour12(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_localized(time, "%OI"); + format_localized(time, 'I', 'O'); } void on_minute(numeric_system ns) { @@ -925,7 +937,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) return write(minute(), 2); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_localized(time, "%OM"); + format_localized(time, 'M', 'O'); } void on_second(numeric_system ns) { @@ -950,13 +962,12 @@ struct chrono_formatter { } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_localized(time, "%OS"); + format_localized(time, 'S', 'O'); } void on_12_hour_time() { if (handle_nan_inf()) return; - - format_localized(time(), "%r"); + format_localized(time(), 'r'); } void on_24_hour_time() { @@ -980,16 +991,18 @@ struct chrono_formatter { void on_am_pm() { if (handle_nan_inf()) return; - format_localized(time(), "%p"); + format_localized(time(), 'p'); } void on_duration_value() { if (handle_nan_inf()) return; write_sign(); - out = format_chrono_duration_value(out, val, precision); + out = format_duration_value(out, val, precision); } - void on_duration_unit() { out = format_chrono_duration_unit(out); } + void on_duration_unit() { + out = format_duration_unit(out); + } }; } // namespace internal @@ -1024,7 +1037,7 @@ struct formatter, Char> { } void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - void on_fill(Char fill) { f.specs.fill[0] = fill; } + void on_fill(basic_string_view fill) { f.specs.fill = fill; } void on_align(align_t align) { f.specs.align = align; } void on_width(int width) { f.specs.width = width; } void on_precision(int _precision) { f.precision = _precision; } @@ -1088,8 +1101,8 @@ struct formatter, Char> { internal::handle_dynamic_spec( precision, precision_ref, ctx); if (begin == end || *begin == '}') { - out = internal::format_chrono_duration_value(out, d.count(), precision); - internal::format_chrono_duration_unit(out); + out = internal::format_duration_value(out, d.count(), precision); + internal::format_duration_unit(out); } else { internal::chrono_formatter f( ctx, out, d); diff --git a/include/fmt/color.h b/include/fmt/color.h index 362a95e142e7..3756ba3fb850 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -491,10 +491,8 @@ void vformat_to(basic_memory_buffer& buf, const text_style& ts, internal::make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } - vformat_to(buf, format_str, args); - if (has_style) { - internal::reset_color(buf); - } + internal::vformat_to(buf, format_str, args); + if (has_style) internal::reset_color(buf); } } // namespace internal @@ -540,7 +538,7 @@ void print(const text_style& ts, const S& format_str, const Args&... args) { template > inline std::basic_string vformat( const text_style& ts, const S& format_str, - basic_format_args> args) { + basic_format_args>> args) { basic_memory_buffer buf; internal::vformat_to(buf, ts, to_string_view(format_str), args); return fmt::to_string(buf); @@ -562,7 +560,7 @@ template > inline std::basic_string format(const text_style& ts, const S& format_str, const Args&... args) { return vformat(ts, to_string_view(format_str), - {internal::make_args_checked(format_str, args...)}); + internal::make_args_checked(format_str, args...)); } FMT_END_NAMESPACE diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 5829f623f10b..2e166b5cdfad 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -9,6 +9,7 @@ #define FMT_COMPILE_H_ #include + #include "format.h" FMT_BEGIN_NAMESPACE @@ -549,7 +550,7 @@ std::basic_string format(const CompiledFormat& cf, const Args&... args) { using range = buffer_range; using context = buffer_context; internal::cf::vformat_to(range(buffer), cf, - {make_format_args(args...)}); + make_format_args(args...)); return to_string(buffer); } @@ -561,8 +562,8 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf, using char_type = typename CompiledFormat::char_type; using range = internal::output_range; using context = format_context_t; - return internal::cf::vformat_to( - range(out), cf, {make_format_args(args...)}); + return internal::cf::vformat_to(range(out), cf, + make_format_args(args...)); } template // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 60102 +#define FMT_VERSION 60103 #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) @@ -217,7 +217,7 @@ FMT_BEGIN_NAMESPACE -// Implementations of enable_if_t and other metafunctions for pre-C++14 systems. +// Implementations of enable_if_t and other metafunctions for older systems. template using enable_if_t = typename std::enable_if::type; template @@ -229,6 +229,8 @@ template using remove_const_t = typename std::remove_const::type; template using remove_cvref_t = typename std::remove_cv>::type; +template struct type_identity { using type = T; }; +template using type_identity_t = typename type_identity::type; struct monostate {}; @@ -251,7 +253,7 @@ FMT_API void assert_fail(const char* file, int line, const char* message); # define FMT_ASSERT(condition, message) \ ((condition) \ ? void() \ - : fmt::internal::assert_fail(__FILE__, __LINE__, (message))) + : ::fmt::internal::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -266,7 +268,7 @@ template struct std_string_view {}; #ifdef FMT_USE_INT128 // Do nothing. -#elif defined(__SIZEOF_INT128__) +#elif defined(__SIZEOF_INT128__) && !FMT_NVCC # define FMT_USE_INT128 1 using int128_t = __int128_t; using uint128_t = __uint128_t; @@ -1220,7 +1222,13 @@ using wformat_context = buffer_context; such as `~fmt::vformat`. \endrst */ -template class format_arg_store { +template +class format_arg_store +#if FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ private: static const size_t num_args = sizeof...(Args); static const bool is_packed = num_args < internal::max_packed_args; @@ -1239,7 +1247,12 @@ template class format_arg_store { : internal::is_unpacked_bit | num_args; format_arg_store(const Args&... args) - : data_{internal::make_arg(args)...} {} + : +#if FMT_GCC_VERSION < 409 + basic_format_args(*this), +#endif + data_{internal::make_arg(args)...} { + } }; /** @@ -1426,21 +1439,23 @@ template > inline format_arg_store, remove_reference_t...> make_args_checked(const S& format_str, const remove_reference_t&... args) { - static_assert(all_true<(!std::is_base_of>() || - !std::is_reference())...>::value, - "passing views as lvalues is disallowed"); + static_assert( + all_true<(!std::is_base_of>::value || + !std::is_reference::value)...>::value, + "passing views as lvalues is disallowed"); check_format_string>...>(format_str); return {args...}; } template -std::basic_string vformat(basic_string_view format_str, - basic_format_args> args); +std::basic_string vformat( + basic_string_view format_str, + basic_format_args>> args); template typename buffer_context::iterator vformat_to( buffer& buf, basic_string_view format_str, - basic_format_args> args); + basic_format_args>> args); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); } // namespace internal @@ -1471,8 +1486,9 @@ void arg(S, internal::named_arg) = delete; template , FMT_ENABLE_IF( internal::is_contiguous_back_insert_iterator::value)> -OutputIt vformat_to(OutputIt out, const S& format_str, - basic_format_args> args) { +OutputIt vformat_to( + OutputIt out, const S& format_str, + basic_format_args>> args) { using container = remove_reference_t; internal::container_buffer buf((internal::get_container(out))); internal::vformat_to(buf, to_string_view(format_str), args); @@ -1485,14 +1501,14 @@ template format_to( std::back_insert_iterator out, const S& format_str, Args&&... args) { - return vformat_to( - out, to_string_view(format_str), - {internal::make_args_checked(format_str, args...)}); + return vformat_to(out, to_string_view(format_str), + internal::make_args_checked(format_str, args...)); } template > inline std::basic_string vformat( - const S& format_str, basic_format_args> args) { + const S& format_str, + basic_format_args>> args) { return internal::vformat(to_string_view(format_str), args); } @@ -1512,7 +1528,7 @@ template > inline std::basic_string format(const S& format_str, Args&&... args) { return internal::vformat( to_string_view(format_str), - {internal::make_args_checked(format_str, args...)}); + internal::make_args_checked(format_str, args...)); } FMT_API void vprint(string_view, format_args); diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 117212e9a70d..55bf1c44e992 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -8,8 +8,6 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ -#include "format.h" - #include #include #include @@ -17,6 +15,8 @@ #include #include // for std::memmove #include + +#include "format.h" #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) # include #endif @@ -386,7 +386,8 @@ class fp { const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; auto u = bit_cast(d); f = u & significand_mask; - auto biased_e = (u & exponent_mask) >> double_significand_size; + int biased_e = + static_cast((u & exponent_mask) >> double_significand_size); // Predecessor is closer if d is a normalized power of 2 (f == 0) other than // the smallest normalized number (biased_e > 1). bool is_predecessor_closer = f == 0 && biased_e > 1; @@ -394,7 +395,7 @@ class fp { f += implicit_bit; else biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = static_cast(biased_e - exponent_bias - double_significand_size); + e = biased_e - exponent_bias - double_significand_size; return is_predecessor_closer; } @@ -458,13 +459,11 @@ inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; } // Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its // (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. inline fp get_cached_power(int min_exponent, int& pow10_exponent) { - const uint64_t one_over_log2_10 = 0x4d104d42; // round(pow(2, 32) / log2(10)) + const int64_t one_over_log2_10 = 0x4d104d42; // round(pow(2, 32) / log2(10)) int index = static_cast( - static_cast( - (min_exponent + fp::significand_size - 1) * one_over_log2_10 + - ((uint64_t(1) << 32) - 1) // ceil - ) >> - 32 // arithmetic shift + ((min_exponent + fp::significand_size - 1) * one_over_log2_10 + + ((int64_t(1) << 32) - 1)) // ceil + >> 32 // arithmetic shift ); // Decimal exponent of the first (smallest) cached power of 10. const int first_dec_exp = -348; @@ -1038,7 +1037,7 @@ void fallback_format(Double d, buffer& buf, int& exp10) { // if T is a IEEE754 binary32 or binary64 and snprintf otherwise. template int format_float(T value, int precision, float_specs specs, buffer& buf) { - static_assert(!std::is_same(), ""); + static_assert(!std::is_same::value, ""); FMT_ASSERT(value >= 0, "value is negative"); const bool fixed = specs.format == float_format::fixed; @@ -1113,7 +1112,7 @@ int snprintf_float(T value, int precision, float_specs specs, buffer& buf) { // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - static_assert(!std::is_same(), ""); + static_assert(!std::is_same::value, ""); // Subtract 1 to account for the difference in precision since we use %e for // both general and exponent format. @@ -1148,7 +1147,8 @@ int snprintf_float(T value, int precision, float_specs specs, "fuzz mode - avoid large allocation inside snprintf"); #endif // Suppress the warning about a nonliteral format string. - auto snprintf_ptr = FMT_SNPRINTF; + // Cannot use auto becase of a bug in MinGW (#1532). + int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; int result = precision >= 0 ? snprintf_ptr(begin, capacity, format, precision, value) : snprintf_ptr(begin, capacity, format, value); @@ -1380,7 +1380,8 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { FMT_FUNC void internal::vprint_mojibake(std::FILE* f, string_view format_str, format_args args) { memory_buffer buffer; - vformat_to(buffer, format_str, basic_format_args>(args)); + internal::vformat_to(buffer, format_str, + basic_format_args>(args)); fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index ee8ab92f7ae5..59dc223b3465 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -33,8 +33,6 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include "core.h" - #include #include #include @@ -43,6 +41,8 @@ #include #include +#include "core.h" + #ifdef __clang__ # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else @@ -221,7 +221,7 @@ namespace internal { // A helper function to suppress bogus "conditional expression is constant" // warnings. -template inline T const_check(T value) { return value; } +template FMT_CONSTEXPR T const_check(T value) { return value; } // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). @@ -235,7 +235,7 @@ inline Dest bit_cast(const Source& source) { } inline bool is_big_endian() { - auto u = 1u; + const auto u = 1u; struct bytes { char data[sizeof(u)]; }; @@ -878,11 +878,11 @@ inline Char* format_decimal(Char* buffer, UInt value, int num_digits, return end; } -template constexpr int digits10() noexcept { +template constexpr int digits10() FMT_NOEXCEPT { return std::numeric_limits::digits10; } -template <> constexpr int digits10() noexcept { return 38; } -template <> constexpr int digits10() noexcept { return 38; } +template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } +template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } template inline Iterator format_decimal(Iterator out, UInt value, int num_digits, @@ -962,9 +962,24 @@ template struct null {}; // Workaround an array initialization issue in gcc 4.8. template struct fill_t { private: - Char data_[6]; + enum { max_size = 4 }; + Char data_[max_size]; + unsigned char size_; public: + FMT_CONSTEXPR void operator=(basic_string_view s) { + auto size = s.size(); + if (size > max_size) { + FMT_THROW(format_error("invalid fill")); + return; + } + for (size_t i = 0; i < size; ++i) data_[i] = s[i]; + size_ = static_cast(size); + } + + size_t size() const { return size_; } + const Char* data() const { return data_; } + FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; } FMT_CONSTEXPR const Char& operator[](size_t index) const { return data_[index]; @@ -973,6 +988,7 @@ template struct fill_t { static FMT_CONSTEXPR fill_t make() { auto fill = fill_t(); fill[0] = Char(' '); + fill.size_ = 1; return fill; } }; @@ -1352,6 +1368,14 @@ template struct nonfinite_writer { } }; +template +OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { + auto fill_size = fill.size(); + if (fill_size == 1) return std::fill_n(it, n, fill[0]); + for (size_t i = 0; i < n; ++i) it = std::copy_n(fill.data(), fill_size, it); + return it; +} + // This template provides operations for formatting and writing data into a // character range. template class basic_writer { @@ -1614,20 +1638,20 @@ template class basic_writer { size_t size = f.size(); // The number of code units. size_t num_code_points = width != 0 ? f.width() : size; if (width <= num_code_points) return f(reserve(size)); - auto&& it = reserve(width + (size - num_code_points)); - char_type fill = specs.fill[0]; - std::size_t padding = width - num_code_points; + size_t padding = width - num_code_points; + size_t fill_size = specs.fill.size(); + auto&& it = reserve(size + padding * fill_size); if (specs.align == align::right) { - it = std::fill_n(it, padding, fill); + it = fill(it, padding, specs.fill); f(it); } else if (specs.align == align::center) { std::size_t left_padding = padding / 2; - it = std::fill_n(it, left_padding, fill); + it = fill(it, left_padding, specs.fill); f(it); - it = std::fill_n(it, padding - left_padding, fill); + it = fill(it, padding - left_padding, specs.fill); } else { f(it); - it = std::fill_n(it, padding, fill); + it = fill(it, padding, specs.fill); } } @@ -2008,7 +2032,9 @@ template class specs_setter { : specs_(other.specs_) {} FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(Char fill) { specs_.fill[0] = fill; } + FMT_CONSTEXPR void on_fill(basic_string_view fill) { + specs_.fill = fill; + } FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; } FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; } FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; } @@ -2320,16 +2346,25 @@ template struct precision_adapter { SpecHandler& handler; }; +template +FMT_CONSTEXPR const Char* next_code_point(const Char* begin, const Char* end) { + if (const_check(sizeof(Char) != 1) || (*begin & 0x80) == 0) return begin + 1; + do { + ++begin; + } while (begin != end && (*begin & 0xc0) == 0x80); + return begin; +} + // Parses fill and alignment. template FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end, Handler&& handler) { FMT_ASSERT(begin != end, ""); auto align = align::none; - int i = 0; - if (begin + 1 != end) ++i; - do { - switch (static_cast(begin[i])) { + auto p = next_code_point(begin, end); + if (p == end) p = begin; + for (;;) { + switch (static_cast(*p)) { case '<': align = align::left; break; @@ -2346,18 +2381,21 @@ FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end, break; } if (align != align::none) { - if (i > 0) { + if (p != begin) { auto c = *begin; if (c == '{') return handler.on_error("invalid fill character '{'"), begin; - begin += 2; - handler.on_fill(c); + handler.on_fill(basic_string_view(begin, p - begin)); + begin = p + 1; } else ++begin; handler.on_align(align); break; + } else if (p == begin) { + break; } - } while (i-- > 0); + p = begin; + } return begin; } @@ -3178,6 +3216,11 @@ arg_join join(It begin, It end, wstring_view sep) { std::vector v = {1, 2, 3}; fmt::print("{}", fmt::join(v, ", ")); // Output: "1, 2, 3" + + ``fmt::join`` applies passed format specifiers to the range elements:: + + fmt::print("{:02}", fmt::join(v, ", ")); + // Output: "01, 02, 03" \endrst */ template @@ -3223,7 +3266,7 @@ std::basic_string to_string(const basic_memory_buffer& buf) { template typename buffer_context::iterator internal::vformat_to( internal::buffer& buf, basic_string_view format_str, - basic_format_args> args) { + basic_format_args>> args) { using range = buffer_range; return vformat_to>(buf, to_string_view(format_str), args); @@ -3233,7 +3276,7 @@ template , FMT_ENABLE_IF(internal::is_string::value)> inline typename buffer_context::iterator vformat_to( internal::buffer& buf, const S& format_str, - basic_format_args> args) { + basic_format_args>> args) { return internal::vformat_to(buf, to_string_view(format_str), args); } @@ -3244,7 +3287,7 @@ inline typename buffer_context::iterator format_to( internal::check_format_string(format_str); using context = buffer_context; return internal::vformat_to(buf, to_string_view(format_str), - {make_format_args(args...)}); + make_format_args(args...)); } template @@ -3257,8 +3300,9 @@ template ::value && !internal::is_contiguous_back_insert_iterator::value)> -inline OutputIt vformat_to(OutputIt out, const S& format_str, - format_args_t> args) { +inline OutputIt vformat_to( + OutputIt out, const S& format_str, + format_args_t, char_t> args) { using range = internal::output_range>; return vformat_to>(range(out), to_string_view(format_str), args); @@ -3284,7 +3328,7 @@ inline OutputIt format_to(OutputIt out, const S& format_str, Args&&... args) { internal::check_format_string(format_str); using context = format_context_t>; return vformat_to(out, to_string_view(format_str), - {make_format_args(args...)}); + make_format_args(args...)); } template struct format_to_n_result { @@ -3312,7 +3356,7 @@ template ::value)> inline format_to_n_result vformat_to_n( OutputIt out, std::size_t n, basic_string_view format_str, - format_to_n_args args) { + format_to_n_args, type_identity_t> args) { auto it = vformat_to(internal::truncating_iterator(out, n), format_str, args); return {it.base(), it.count()}; @@ -3334,13 +3378,13 @@ inline format_to_n_result format_to_n(OutputIt out, std::size_t n, internal::check_format_string(format_str); using context = format_to_n_context>; return vformat_to_n(out, n, to_string_view(format_str), - {make_format_args(args...)}); + make_format_args(args...)); } template inline std::basic_string internal::vformat( basic_string_view format_str, - basic_format_args> args) { + basic_format_args>> args) { basic_memory_buffer buffer; internal::vformat_to(buffer, format_str, args); return to_string(buffer); @@ -3405,6 +3449,10 @@ template struct udl_arg { } }; +template +FMT_CONSTEXPR basic_string_view literal_to_view(const Char (&s)[N]) { + return {s, N - 1}; +} } // namespace internal inline namespace literals { @@ -3468,7 +3516,8 @@ FMT_END_NAMESPACE using char_type = fmt::remove_cvref_t; \ __VA_ARGS__ FMT_CONSTEXPR \ operator fmt::basic_string_view() const { \ - return {s, sizeof(s) / sizeof(char_type) - 1}; \ + /* FMT_STRING only accepts string literals. */ \ + return fmt::internal::literal_to_view(s); \ } \ }; \ return FMT_STRING(); \ @@ -3476,7 +3525,7 @@ FMT_END_NAMESPACE /** \rst - Constructs a compile-time format string. + Constructs a compile-time format string from a string literal *s*. **Example**:: diff --git a/include/fmt/locale.h b/include/fmt/locale.h index 7c13656e4fa8..70bc1935efa5 100644 --- a/include/fmt/locale.h +++ b/include/fmt/locale.h @@ -9,6 +9,7 @@ #define FMT_LOCALE_H_ #include + #include "format.h" FMT_BEGIN_NAMESPACE @@ -18,16 +19,16 @@ template typename buffer_context::iterator vformat_to( const std::locale& loc, buffer& buf, basic_string_view format_str, - basic_format_args> args) { + basic_format_args>> args) { using range = buffer_range; return vformat_to>(buf, to_string_view(format_str), args, internal::locale_ref(loc)); } template -std::basic_string vformat(const std::locale& loc, - basic_string_view format_str, - basic_format_args> args) { +std::basic_string vformat( + const std::locale& loc, basic_string_view format_str, + basic_format_args>> args) { basic_memory_buffer buffer; internal::vformat_to(loc, buffer, format_str, args); return fmt::to_string(buffer); @@ -37,7 +38,7 @@ std::basic_string vformat(const std::locale& loc, template > inline std::basic_string vformat( const std::locale& loc, const S& format_str, - basic_format_args> args) { + basic_format_args>> args) { return internal::vformat(loc, to_string_view(format_str), args); } @@ -46,15 +47,15 @@ inline std::basic_string format(const std::locale& loc, const S& format_str, Args&&... args) { return internal::vformat( loc, to_string_view(format_str), - {internal::make_args_checked(format_str, args...)}); + internal::make_args_checked(format_str, args...)); } template ::value, char_t>> -inline OutputIt vformat_to(OutputIt out, const std::locale& loc, - const S& format_str, - format_args_t args) { +inline OutputIt vformat_to( + OutputIt out, const std::locale& loc, const S& format_str, + format_args_t, Char> args) { using range = internal::output_range; return vformat_to>( range(out), to_string_view(format_str), args, internal::locale_ref(loc)); diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 72d078b20c61..c4831533d05c 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -93,7 +93,9 @@ void format_value(buffer& buf, const T& value, locale_ref loc = locale_ref()) { formatbuf format_buf(buf); std::basic_ostream output(&format_buf); + #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) if (loc) output.imbue(loc.get()); + #endif output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output << value; buf.resize(buf.size()); @@ -115,7 +117,7 @@ struct fallback_formatter::value>> template void vprint(std::basic_ostream& os, basic_string_view format_str, - basic_format_args> args) { + basic_format_args>> args) { basic_memory_buffer buffer; internal::vformat_to(buffer, format_str, args); internal::write(os, buffer); @@ -134,7 +136,7 @@ template ::value, char_t>> void print(std::basic_ostream& os, const S& format_str, Args&&... args) { vprint(os, to_string_view(format_str), - {internal::make_args_checked(format_str, args...)}); + internal::make_args_checked(format_str, args...)); } FMT_END_NAMESPACE diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 047aa323e2f6..8a2a8c20b13a 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -597,7 +597,8 @@ inline format_arg_store make_wprintf_args( template > inline std::basic_string vsprintf( - const S& format, basic_format_args> args) { + const S& format, + basic_format_args>> args) { basic_memory_buffer buffer; printf(buffer, to_string_view(format), args); return to_string(buffer); @@ -616,12 +617,13 @@ template ::value, char_t>> inline std::basic_string sprintf(const S& format, const Args&... args) { using context = basic_printf_context_t; - return vsprintf(to_string_view(format), {make_format_args(args...)}); + return vsprintf(to_string_view(format), make_format_args(args...)); } template > -inline int vfprintf(std::FILE* f, const S& format, - basic_format_args> args) { +inline int vfprintf( + std::FILE* f, const S& format, + basic_format_args>> args) { basic_memory_buffer buffer; printf(buffer, to_string_view(format), args); std::size_t size = buffer.size(); @@ -644,12 +646,13 @@ template ; return vfprintf(f, to_string_view(format), - {make_format_args(args...)}); + make_format_args(args...)); } template > -inline int vprintf(const S& format, - basic_format_args> args) { +inline int vprintf( + const S& format, + basic_format_args>> args) { return vfprintf(stdout, to_string_view(format), args); } @@ -667,12 +670,13 @@ template >; return vprintf(to_string_view(format_str), - {make_format_args(args...)}); + make_format_args(args...)); } template > -inline int vfprintf(std::basic_ostream& os, const S& format, - basic_format_args> args) { +inline int vfprintf( + std::basic_ostream& os, const S& format, + basic_format_args>> args) { basic_memory_buffer buffer; printf(buffer, to_string_view(format), args); internal::write(os, buffer); @@ -683,9 +687,9 @@ inline int vfprintf(std::basic_ostream& os, const S& format, template > -typename ArgFormatter::iterator vprintf(internal::buffer& out, - basic_string_view format_str, - basic_format_args args) { +typename ArgFormatter::iterator vprintf( + internal::buffer& out, basic_string_view format_str, + basic_format_args> args) { typename ArgFormatter::iterator iter(out); Context(iter, format_str, args).template format(); return iter; @@ -705,7 +709,7 @@ inline int fprintf(std::basic_ostream& os, const S& format_str, const Args&... args) { using context = basic_printf_context_t; return vfprintf(os, to_string_view(format_str), - {make_format_args(args...)}); + make_format_args(args...)); } FMT_END_NAMESPACE diff --git a/src/format.cc b/src/format.cc index 44ba77f02869..e6fde7c3a35b 100644 --- a/src/format.cc +++ b/src/format.cc @@ -19,7 +19,7 @@ int format_float(char* buf, std::size_t size, const char* format, int precision, "fuzz mode - avoid large allocation inside snprintf"); #endif // Suppress the warning about nonliteral format string. - auto snprintf_ptr = FMT_SNPRINTF; + int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; return precision < 0 ? snprintf_ptr(buf, size, format, value) : snprintf_ptr(buf, size, format, precision, value); } diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 04b5f2fa020b..828a8aeefefa 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -145,6 +145,48 @@ TEST(ChronoTest, FormatDefault) { fmt::format("{}", std::chrono::duration>(42))); } +TEST(ChronoTest, FormatWide) { + EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); + EXPECT_EQ(L"42as", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42fs", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42ps", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42ns", fmt::format(L"{}", std::chrono::nanoseconds(42))); + EXPECT_EQ(L"42\u00B5s", fmt::format(L"{}", std::chrono::microseconds(42))); + EXPECT_EQ(L"42ms", fmt::format(L"{}", std::chrono::milliseconds(42))); + EXPECT_EQ(L"42cs", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42ds", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); + EXPECT_EQ(L"42das", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42hs", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42ks", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42Ms", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42Gs", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42Ts", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42Ps", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42Es", + fmt::format(L"{}", std::chrono::duration(42))); + EXPECT_EQ(L"42m", fmt::format(L"{}", std::chrono::minutes(42))); + EXPECT_EQ(L"42h", fmt::format(L"{}", std::chrono::hours(42))); + EXPECT_EQ( + L"42[15]s", + fmt::format(L"{}", std::chrono::duration>(42))); + EXPECT_EQ( + L"42[15/4]s", + fmt::format(L"{}", std::chrono::duration>(42))); +} + TEST(ChronoTest, Align) { auto s = std::chrono::seconds(42); EXPECT_EQ("42s ", fmt::format("{:5}", s)); diff --git a/test/format b/test/format index ea26a005ec0c..62fa31672df1 100644 --- a/test/format +++ b/test/format @@ -83,9 +83,9 @@ namespace std { Out format_to(Out out, wstring_view fmt, const Args&... args); template - Out vformat_to(Out out, string_view fmt, format_args_t args); + Out vformat_to(Out out, string_view fmt, format_args_t, char> args); template - Out vformat_to(Out out, wstring_view fmt, format_args_t args); + Out vformat_to(Out out, wstring_view fmt, format_args_t, wchar_t> args); template struct format_to_n_result { @@ -730,17 +730,17 @@ wstring vformat(wstring_view fmt, wformat_args args); template Out format_to(Out out, string_view fmt, const Args&... args) { using context = basic_format_context; - return vformat_to(out, fmt, {make_format_args(args...)}); + return vformat_to(out, fmt, make_format_args(args...)); } template Out format_to(Out out, wstring_view fmt, const Args&... args) { using context = basic_format_context; - return vformat_to(out, fmt, {make_format_args(args...)}); + return vformat_to(out, fmt, make_format_args(args...)); } template - Out vformat_to(Out out, string_view fmt, format_args_t args) { + Out vformat_to(Out out, string_view fmt, format_args_t, char> args) { using range = fmt::internal::output_range; detail::format_handler, char, basic_format_context> h(range(out), fmt, args, {}); @@ -749,7 +749,7 @@ template } template - Out vformat_to(Out out, wstring_view fmt, format_args_t args); + Out vformat_to(Out out, wstring_view fmt, format_args_t, wchar_t> args); template format_to_n_result format_to_n(Out out, iter_difference_t n, diff --git a/test/format-test.cc b/test/format-test.cc index 76f5710651f1..8bce69be20d9 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -815,6 +815,9 @@ TEST(FormatterTest, Fill) { EXPECT_EQ("**0xface", format("{0:*>8}", reinterpret_cast(0xface))); EXPECT_EQ("foo=", format("{:}=", "foo")); EXPECT_EQ(std::string("\0\0\0*", 4), format(string_view("{:\0>4}", 6), '*')); + EXPECT_EQ("жж42", format("{0:ж>4}", 42)); + EXPECT_THROW_MSG(format("{:\x80\x80\x80\x80\x80>}", 0), format_error, + "invalid fill"); } TEST(FormatterTest, PlusSign) { @@ -2173,7 +2176,7 @@ struct test_format_specs_handler { type(other.type) {} FMT_CONSTEXPR void on_align(fmt::align_t a) { align = a; } - FMT_CONSTEXPR void on_fill(char f) { fill = f; } + FMT_CONSTEXPR void on_fill(fmt::string_view f) { fill = f[0]; } FMT_CONSTEXPR void on_plus() { res = PLUS; } FMT_CONSTEXPR void on_minus() { res = MINUS; } FMT_CONSTEXPR void on_space() { res = SPACE; }