From 61c5a516048854eb044e47cabe032a092128edc2 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 07:41:08 -0700 Subject: [PATCH 001/113] Fix handling of empty tuples (#1588) --- include/fmt/ranges.h | 6 ++---- test/ranges-test.cc | 8 +++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 6110fdaf..a789a975 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -13,6 +13,7 @@ #define FMT_RANGES_H_ #include + #include "format.h" // output only up to N items from the range. @@ -104,10 +105,7 @@ struct is_range_< /// tuple_size and tuple_element check. template class is_tuple_like_ { template - static auto check(U* p) - -> decltype(std::tuple_size::value, - (void)std::declval::type>(), - int()); + static auto check(U* p) -> decltype(std::tuple_size::value, int()); template static void check(...); public: diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 265f9acd..ee6c4551 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -10,6 +10,7 @@ // {fmt} support for ranges, containers and types tuple interface. #include "fmt/ranges.h" + #include "gtest.h" // Check if 'if constexpr' is supported. @@ -44,9 +45,10 @@ TEST(RangesTest, FormatPair) { } TEST(RangesTest, FormatTuple) { - std::tuple tu1{42, 1.5f, "this is tuple", - 'i'}; - EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1)); + std::tuple t{42, 1.5f, "this is tuple", + 'i'}; + EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t)); + EXPECT_EQ("()", fmt::format("{}", std::tuple<>())); } TEST(RangesTest, JoinTuple) { From 6f01b6ebb6c5cb45c1cd5def4dab5ab9db4d1cb6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 09:50:25 -0700 Subject: [PATCH 002/113] Fix a typo in CMake config: STRINGS -> STRING --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0928961..466b342f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,7 +250,7 @@ if (FMT_INSTALL) "Installation directory for libraries, relative to " "${CMAKE_INSTALL_PREFIX}.") - set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS + set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING "Installation directory for include files, relative to " "${CMAKE_INSTALL_PREFIX}.") From 678341275b62689264ba0fe2a4c47f086874925c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 10:32:34 -0700 Subject: [PATCH 003/113] Deprecate fmt::char8_t --- include/fmt/core.h | 12 +++++++++--- include/fmt/format.h | 26 ++++++++++++++------------ test/format-impl-test.cc | 11 ++++++----- test/format-test.cc | 33 +++++++++++---------------------- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 25647037..4734aba2 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -315,6 +315,12 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { FMT_ASSERT(value >= 0, "negative value"); return static_cast::type>(value); } + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else +enum char8_type : unsigned char {}; +#endif } // namespace internal template @@ -415,15 +421,15 @@ using string_view = basic_string_view; using wstring_view = basic_string_view; #ifndef __cpp_char8_t -// A UTF-8 code unit type. -enum char8_t : unsigned char {}; +// char8_t is deprecated; use char instead. +using char8_t FMT_DEPRECATED_ALIAS = internal::char8_type; #endif /** Specifies if ``T`` is a character type. Can be specialized by users. */ template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; diff --git a/include/fmt/format.h b/include/fmt/format.h index 07d4f5f8..ee7f0f93 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -482,7 +482,7 @@ inline size_t count_code_points(basic_string_view s) { return num_code_points; } -inline size_t count_code_points(basic_string_view s) { +inline size_t count_code_points(basic_string_view s) { return count_code_points(basic_string_view( reinterpret_cast(s.data()), s.size())); } @@ -494,8 +494,8 @@ inline size_t code_point_index(basic_string_view s, size_t n) { } // Calculates the index of the nth code point in a UTF-8 string. -inline size_t code_point_index(basic_string_view s, size_t n) { - const char8_t* data = s.data(); +inline size_t code_point_index(basic_string_view s, size_t n) { + const char8_type* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) { @@ -505,13 +505,13 @@ inline size_t code_point_index(basic_string_view s, size_t n) { return s.size(); } -inline char8_t to_char8_t(char c) { return static_cast(c); } +inline char8_type to_char8_t(char c) { return static_cast(c); } template using needs_conversion = bool_constant< std::is_same::value_type, char>::value && - std::is_same::value>; + std::is_same::value>; template ::value)> @@ -555,20 +555,22 @@ class buffer_range : public internal::output_range< : internal::output_range(std::back_inserter(buf)) {} }; -class FMT_DEPRECATED u8string_view : public basic_string_view { +class FMT_DEPRECATED u8string_view + : public basic_string_view { public: u8string_view(const char* s) - : basic_string_view(reinterpret_cast(s)) {} + : basic_string_view( + reinterpret_cast(s)) {} u8string_view(const char* s, size_t count) FMT_NOEXCEPT - : basic_string_view(reinterpret_cast(s), count) { - } + : basic_string_view( + reinterpret_cast(s), count) {} }; #if FMT_USE_USER_DEFINED_LITERALS inline namespace literals { -FMT_DEPRECATED inline basic_string_view operator"" _u(const char* s, - std::size_t n) { - return {reinterpret_cast(s), n}; +FMT_DEPRECATED inline basic_string_view operator"" _u( + const char* s, std::size_t n) { + return {reinterpret_cast(s), n}; } } // namespace literals #endif diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 34814967..21e91014 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -10,12 +10,11 @@ #include "test-assert.h" // Include format.cc instead of format.h to test implementation. -#include "../src/format.cc" -#include "fmt/printf.h" - #include #include +#include "../src/format.cc" +#include "fmt/printf.h" #include "gmock.h" #include "gtest-extra.h" #include "util.h" @@ -430,8 +429,10 @@ TEST(FormatTest, CountCodePoints) { #ifndef __cpp_char8_t using fmt::char8_t; #endif - EXPECT_EQ(4, fmt::internal::count_code_points( - fmt::basic_string_view(reinterpret_cast("ёжик")))); + EXPECT_EQ( + 4, fmt::internal::count_code_points( + fmt::basic_string_view( + reinterpret_cast("ёжик")))); } // Tests fmt::internal::count_digits for integer type Int. diff --git a/test/format-test.cc b/test/format-test.cc index 92ad5369..af72acaf 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2504,25 +2504,6 @@ TEST(FormatTest, FmtStringInTemplate) { #endif // FMT_USE_CONSTEXPR -// C++20 feature test, since r346892 Clang considers char8_t a fundamental -// type in this mode. If this is the case __cpp_char8_t will be defined. -#ifndef __cpp_char8_t -// Locally provide type char8_t defined in format.h -using fmt::char8_t; -#endif - -// Convert 'char8_t' character sequences to 'char' sequences -// Otherwise GTest will insist on inserting 'char8_t' NTBS into a 'char' stream, -// but basic_ostream::operator<< overloads taking 'char8_t' arguments -// are defined as deleted by P1423. -// Handling individual 'char8_t's is done inline. -std::string from_u8str(const std::basic_string& str) { - return std::string(str.begin(), str.end()); -} -std::string from_u8str(const fmt::basic_string_view& str) { - return std::string(str.begin(), str.end()); -} - TEST(FormatTest, EmphasisNonHeaderOnly) { // Ensure this compiles even if FMT_HEADER_ONLY is not defined. EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), @@ -2561,10 +2542,18 @@ TEST(FormatTest, FormatCustomChar) { EXPECT_EQ(result[0], mychar('x')); } +// Convert a char8_t string to std::string. Otherwise GTest will insist on +// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423. +template std::string from_u8str(const S& str) { + return std::string(str.begin(), str.end()); +} + TEST(FormatTest, FormatUTF8Precision) { - using str_type = std::basic_string; - str_type format(reinterpret_cast(u8"{:.4}")); - str_type str(reinterpret_cast(u8"caf\u00e9s")); // cafés + using str_type = std::basic_string; + str_type format( + reinterpret_cast(u8"{:.4}")); + str_type str(reinterpret_cast( + u8"caf\u00e9s")); // cafés auto result = fmt::format(format, str); EXPECT_EQ(fmt::internal::count_code_points(result), 4); EXPECT_EQ(result.size(), 5); From ff486a72a764ff9f0ea3be460bd713ed7a237db6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 11:37:38 -0700 Subject: [PATCH 004/113] Allow leading zeros in precision (#1579) --- include/fmt/format.h | 10 +++++----- test/format-test.cc | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index ee7f0f93..a529383a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1953,10 +1953,6 @@ template FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end, ErrorHandler&& eh) { FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - if (*begin == '0') { - ++begin; - return 0; - } unsigned value = 0; // Convert to unsigned to prevent a warning. constexpr unsigned max_int = max_value(); @@ -2311,7 +2307,11 @@ FMT_CONSTEXPR const Char* parse_arg_id(const Char* begin, const Char* end, return begin; } if (c >= '0' && c <= '9') { - int index = parse_nonnegative_int(begin, end, handler); + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, handler); + else + ++begin; if (begin == end || (*begin != '}' && *begin != ':')) handler.on_error("invalid format string"); else diff --git a/test/format-test.cc b/test/format-test.cc index af72acaf..a2223749 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1124,6 +1124,7 @@ TEST(FormatterTest, Precision) { "000000000000000000000000000000000000000000000000000P+127", format("{:.838A}", -2.14001164E+38)); EXPECT_EQ("123.", format("{:#.0f}", 123.0)); + EXPECT_EQ("1.23", format("{:.02f}", 1.234)); EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast(0xcafe)), format_error, From 85050aa2e61cfc3c29cd8b74c472df1dd8db588c Mon Sep 17 00:00:00 2001 From: Nikolay Rapotkin Date: Fri, 13 Mar 2020 11:20:24 +0300 Subject: [PATCH 005/113] Ability to join elements of std::initializer_list was added --- include/fmt/ranges.h | 24 ++++++++++++++++++++++++ test/ranges-test.cc | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index a789a975..f8f9adb7 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -12,6 +12,7 @@ #ifndef FMT_RANGES_H_ #define FMT_RANGES_H_ +#include #include #include "format.h" @@ -358,6 +359,29 @@ FMT_CONSTEXPR tuple_arg_join join(const std::tuple& tuple, return {tuple, sep}; } +/** + \rst + Returns an object that formats `initializer_list` with elements separated by + `sep`. + + **Example**:: + + fmt::print("{}", fmt::join({1, 2, 3}, ", ")); + // Output: "1, 2, 3" + \endrst + */ +template +arg_join>, char> join( + std::initializer_list list, string_view sep) { + return join(std::begin(list), std::end(list), sep); +} + +template +arg_join>, wchar_t> join( + std::initializer_list list, wstring_view sep) { + return join(std::begin(list), std::end(list), sep); +} + FMT_END_NAMESPACE #endif // FMT_RANGES_H_ diff --git a/test/ranges-test.cc b/test/ranges-test.cc index ee6c4551..a729948d 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -70,6 +70,12 @@ TEST(RangesTest, JoinTuple) { EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/"))); } +TEST(RangesTest, JoinInitializerList) { + EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join({1, 2, 3}, ", "))); + EXPECT_EQ("fmt rocks !", + fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " "))); +} + struct my_struct { int32_t i; std::string str; // can throw From 6012dc9ab415c7a873e1a1abd06fcbbef13e5581 Mon Sep 17 00:00:00 2001 From: Vladimir Solontsov <56200572+vsolontsov-ll@users.noreply.github.com> Date: Mon, 16 Mar 2020 17:00:29 +0300 Subject: [PATCH 006/113] Dynamic arguments storage. Implementation of enhancement from issue #1170. (#1584) --- include/fmt/core.h | 194 +++++++++++++++++++++++++++++++++++ include/fmt/format.h | 4 - test/CMakeLists.txt | 1 + test/format-dyn-args-test.cc | 87 ++++++++++++++++ 4 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 test/format-dyn-args-test.cc diff --git a/include/fmt/core.h b/include/fmt/core.h index 4734aba2..227d15f7 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -10,9 +10,12 @@ #include // std::FILE #include +#include #include +#include #include #include +#include // The fmt library version in the form major * 10000 + minor * 100 + patch. #define FMT_VERSION 60103 @@ -269,6 +272,10 @@ struct monostate {}; namespace internal { +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template FMT_CONSTEXPR T const_check(T value) { return value; } + // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; @@ -1307,6 +1314,8 @@ inline format_arg_store make_format_args( return {args...}; } +template class dynamic_format_arg_store; + /** \rst A view of a collection of formatting arguments. To avoid lifetime issues it @@ -1378,6 +1387,17 @@ template class basic_format_args { set_data(store.data_); } + /** + \rst + Constructs a `basic_format_args` object from + `~fmt::dynamic_format_arg_store`. + \endrst + */ + basic_format_args(const dynamic_format_arg_store& store) + : types_(store.get_types()) { + set_data(store.data_.data()); + } + /** \rst Constructs a `basic_format_args` object from a dynamic set of arguments. @@ -1619,6 +1639,180 @@ inline void print(const S& format_str, Args&&... args) { internal::make_args_checked(format_str, args...)); #endif } + +namespace internal { + +template struct is_string_view : std::false_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template struct is_reference_wrapper : std::false_type {}; + +template +struct is_reference_wrapper> : std::true_type {}; + +class dyn_arg_storage { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + + template struct storage_node_base { + using owning_ptr = std::unique_ptr>; + virtual ~storage_node_base() = default; + owning_ptr next; + }; + + template struct storage_node : storage_node_base<> { + T value; + template + FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value{arg} { + // Must be initialised after value_ + this->next = std::move(next); + } + + template + FMT_CONSTEXPR storage_node(const basic_string_view& arg, + owning_ptr&& next) + : value{arg.data(), arg.size()} { + // Must be initialised after value + this->next = std::move(next); + } + }; + + storage_node_base<>::owning_ptr head_{nullptr}; + + public: + dyn_arg_storage() = default; + dyn_arg_storage(const dyn_arg_storage&) = delete; + dyn_arg_storage(dyn_arg_storage&&) = default; + + dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; + dyn_arg_storage& operator=(dyn_arg_storage&&) = default; + + template const T& push(const Arg& arg) { + auto node = new storage_node{arg, std::move(head_)}; + head_.reset(node); + return node->value; + } +}; + +} // namespace internal + +/** + \rst + A dynamic version of `fmt::format_arg_store<>`. + It's equipped with a storage to potentially temporary objects which lifetime + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_dyn_copy { + static constexpr internal::type mapped_type = + internal::mapped_type_constant::value; +// static_assert( +// mapped_type != internal::type::named_arg_type, +// "Bug indicator. Named arguments must be processed separately"); + + using type = std::integral_constant< + bool, !(internal::is_reference_wrapper::value || + internal::is_string_view::value || + (mapped_type != internal::type::cstring_type && + mapped_type != internal::type::string_type && + mapped_type != internal::type::custom_type && + mapped_type != internal::type::named_arg_type))>; + }; + + template + using stored_type = conditional_t::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous + // Required by basic_format_args::args_ which is just a pointer. + std::vector> data_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + + internal::dyn_arg_storage storage_; + + friend class basic_format_args; + + unsigned long long get_types() const { + return internal::is_unpacked_bit | data_.size(); + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(internal::make_arg(arg)); + } + + public: + dynamic_format_arg_store() = default; + ~dynamic_format_arg_store() = default; + + dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; + dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; + + dynamic_format_arg_store(dynamic_format_arg_store&&) = default; + dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; + + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function. + + Note that custom types and string types (but not string views!) are copied + into the store with dynamic memory (in addition to resizing vector). + + **Example**:: + + #include + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + static_assert( + !std::is_base_of, T>::value, + "Named arguments are not supported yet"); + if (internal::const_check(need_dyn_copy::type::value)) + emplace_arg(storage_.push>(arg)); + else + emplace_arg(arg); + } + + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function without copying into type-erasing list. + \endrst + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_dyn_copy::type::value, + "Primitive types and string views directly supported by " + "basic_format_arg. Passing them by reference is not allowed"); + emplace_arg(arg.get()); + } +}; FMT_END_NAMESPACE #endif // FMT_CORE_H_ diff --git a/include/fmt/format.h b/include/fmt/format.h index a529383a..36c30b9f 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -213,10 +213,6 @@ FMT_END_NAMESPACE FMT_BEGIN_NAMESPACE namespace internal { -// A helper function to suppress bogus "conditional expression is constant" -// warnings. -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). // Example: uint64_t d = bit_cast(2.718); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89176633..23ea9fcf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -95,6 +95,7 @@ add_fmt_test(grisu-test) target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) +add_fmt_test(format-dyn-args-test) if (MSVC) target_compile_options(format-test PRIVATE /bigobj) endif () diff --git a/test/format-dyn-args-test.cc b/test/format-dyn-args-test.cc new file mode 100644 index 00000000..da4da7ff --- /dev/null +++ b/test/format-dyn-args-test.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2020 Vladimir Solontsov +// SPDX-License-Identifier: MIT Licence + +#include + +#include "gtest-extra.h" + +TEST(FormatDynArgsTest, Basic) { + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + + std::string result = fmt::vformat("{} and {} and {}", store); + + EXPECT_EQ("42 and abc1 and 1.5", result); +} + +TEST(FormatDynArgsTest, StringsAndRefs) { + // Unfortunately the tests are compiled with old ABI + // So strings use COW. + fmt::dynamic_format_arg_store store; + char str[]{"1234567890"}; + store.push_back(str); + store.push_back(std::cref(str)); + store.push_back(fmt::string_view{str}); + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {} and {}", store); + + EXPECT_EQ("1234567890 and X234567890 and X234567890", result); +} + +struct custom_type { + int i{0}; +}; +FMT_BEGIN_NAMESPACE + +template <> struct formatter { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( + ctx.out(), std::declval())) { + return format_to(ctx.out(), "cust={}", p.i); + } +}; +FMT_END_NAMESPACE + +TEST(FormatDynArgsTest, CustomFormat) { + fmt::dynamic_format_arg_store store; + custom_type c{}; + store.push_back(c); + ++c.i; + store.push_back(c); + ++c.i; + store.push_back(std::cref(c)); + ++c.i; + + std::string result = fmt::vformat("{} and {} and {}", store); + + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + +TEST(FormatDynArgsTest, NamedArgByRef) { + fmt::dynamic_format_arg_store store; + + // Note: fmt::arg() constructs an object which holds a reference + // to its value. It's not an aggregate, so it doesn't extend the + // reference lifetime. As a result, it's a very bad idea passing temporary + // as a named argument value. Only GCC with optimization level >0 + // complains about this. + // + // A real life usecase is when you have both name and value alive + // guarantee their lifetime and thus don't want them to be copied into + // storages. + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + std::string result = fmt::vformat("{a1_}", // and {} and {}", + store); + + EXPECT_EQ("42", result); +} From 9f70fc3e7a1725c50de294f8f966789bf6796099 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 16 Mar 2020 07:51:57 -0700 Subject: [PATCH 007/113] Minor tweaks for dynamic_format_arg_store --- include/fmt/core.h | 333 +++++++++++++++++------------------ test/CMakeLists.txt | 1 - test/core-test.cc | 80 ++++++++- test/format-dyn-args-test.cc | 81 --------- 4 files changed, 236 insertions(+), 259 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 227d15f7..8dc770b4 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1205,6 +1205,62 @@ template make_arg(const T& value) { return make_arg(value); } + +template struct is_string_view : std::false_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template struct is_reference_wrapper : std::false_type {}; + +template +struct is_reference_wrapper> : std::true_type {}; + +class dyn_arg_storage { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + template struct storage_node_base { + using owning_ptr = std::unique_ptr>; + virtual ~storage_node_base() = default; + owning_ptr next; + }; + + template struct storage_node : storage_node_base<> { + T value; + + template + FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value(arg) { + this->next = std::move(next); // Must be initialised after value. + } + + template + FMT_CONSTEXPR storage_node(const basic_string_view& arg, + owning_ptr&& next) + : value(arg.data(), arg.size()) { + this->next = std::move(next); // Must be initialised after value. + } + }; + + storage_node_base<>::owning_ptr head_{nullptr}; + + public: + dyn_arg_storage() = default; + dyn_arg_storage(const dyn_arg_storage&) = delete; + dyn_arg_storage(dyn_arg_storage&&) = default; + + dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; + dyn_arg_storage& operator=(dyn_arg_storage&&) = default; + + template const T& push(const Arg& arg) { + auto node = new storage_node(arg, std::move(head_)); + head_.reset(node); + return node->value; + } +}; } // namespace internal // Formatting context. @@ -1314,7 +1370,108 @@ inline format_arg_store make_format_args( return {args...}; } -template class dynamic_format_arg_store; +/** + \rst + A dynamic version of `fmt::format_arg_store<>`. + It's equipped with a storage to potentially temporary objects which lifetime + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_dyn_copy { + static constexpr internal::type mapped_type = + internal::mapped_type_constant::value; + + using type = std::integral_constant< + bool, !(internal::is_reference_wrapper::value || + internal::is_string_view::value || + (mapped_type != internal::type::cstring_type && + mapped_type != internal::type::string_type && + mapped_type != internal::type::custom_type && + mapped_type != internal::type::named_arg_type))>; + }; + + template + using stored_type = conditional_t::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + internal::dyn_arg_storage storage_; + + friend class basic_format_args; + + unsigned long long get_types() const { + return internal::is_unpacked_bit | data_.size(); + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(internal::make_arg(arg)); + } + + public: + dynamic_format_arg_store() = default; + ~dynamic_format_arg_store() = default; + + dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; + dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; + + dynamic_format_arg_store(dynamic_format_arg_store&&) = default; + dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; + + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function. + + Note that custom types and string types (but not string views!) are copied + into the store with dynamic memory (in addition to resizing vector). + + **Example**:: + + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + static_assert( + !std::is_base_of, T>::value, + "named arguments are not supported yet"); + if (internal::const_check(need_dyn_copy::type::value)) + emplace_arg(storage_.push>(arg)); + else + emplace_arg(arg); + } + + /** + Adds a reference to the argument into the dynamic store for later passing to + a formating function. + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_dyn_copy::type::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } +}; /** \rst @@ -1639,180 +1796,6 @@ inline void print(const S& format_str, Args&&... args) { internal::make_args_checked(format_str, args...)); #endif } - -namespace internal { - -template struct is_string_view : std::false_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template struct is_reference_wrapper : std::false_type {}; - -template -struct is_reference_wrapper> : std::true_type {}; - -class dyn_arg_storage { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - - template struct storage_node_base { - using owning_ptr = std::unique_ptr>; - virtual ~storage_node_base() = default; - owning_ptr next; - }; - - template struct storage_node : storage_node_base<> { - T value; - template - FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value{arg} { - // Must be initialised after value_ - this->next = std::move(next); - } - - template - FMT_CONSTEXPR storage_node(const basic_string_view& arg, - owning_ptr&& next) - : value{arg.data(), arg.size()} { - // Must be initialised after value - this->next = std::move(next); - } - }; - - storage_node_base<>::owning_ptr head_{nullptr}; - - public: - dyn_arg_storage() = default; - dyn_arg_storage(const dyn_arg_storage&) = delete; - dyn_arg_storage(dyn_arg_storage&&) = default; - - dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; - dyn_arg_storage& operator=(dyn_arg_storage&&) = default; - - template const T& push(const Arg& arg) { - auto node = new storage_node{arg, std::move(head_)}; - head_.reset(node); - return node->value; - } -}; - -} // namespace internal - -/** - \rst - A dynamic version of `fmt::format_arg_store<>`. - It's equipped with a storage to potentially temporary objects which lifetime - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - using char_type = typename Context::char_type; - - template struct need_dyn_copy { - static constexpr internal::type mapped_type = - internal::mapped_type_constant::value; -// static_assert( -// mapped_type != internal::type::named_arg_type, -// "Bug indicator. Named arguments must be processed separately"); - - using type = std::integral_constant< - bool, !(internal::is_reference_wrapper::value || - internal::is_string_view::value || - (mapped_type != internal::type::cstring_type && - mapped_type != internal::type::string_type && - mapped_type != internal::type::custom_type && - mapped_type != internal::type::named_arg_type))>; - }; - - template - using stored_type = conditional_t::value, - std::basic_string, T>; - - // Storage of basic_format_arg must be contiguous - // Required by basic_format_args::args_ which is just a pointer. - std::vector> data_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - - internal::dyn_arg_storage storage_; - - friend class basic_format_args; - - unsigned long long get_types() const { - return internal::is_unpacked_bit | data_.size(); - } - - template void emplace_arg(const T& arg) { - data_.emplace_back(internal::make_arg(arg)); - } - - public: - dynamic_format_arg_store() = default; - ~dynamic_format_arg_store() = default; - - dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; - dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; - - dynamic_format_arg_store(dynamic_format_arg_store&&) = default; - dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; - - /** - \rst - Adds an argument into the dynamic store for later passing to a formating - function. - - Note that custom types and string types (but not string views!) are copied - into the store with dynamic memory (in addition to resizing vector). - - **Example**:: - - #include - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc1"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template void push_back(const T& arg) { - static_assert( - !std::is_base_of, T>::value, - "Named arguments are not supported yet"); - if (internal::const_check(need_dyn_copy::type::value)) - emplace_arg(storage_.push>(arg)); - else - emplace_arg(arg); - } - - /** - \rst - Adds an argument into the dynamic store for later passing to a formating - function without copying into type-erasing list. - \endrst - */ - template void push_back(std::reference_wrapper arg) { - static_assert( - need_dyn_copy::type::value, - "Primitive types and string views directly supported by " - "basic_format_arg. Passing them by reference is not allowed"); - emplace_arg(arg.get()); - } -}; FMT_END_NAMESPACE #endif // FMT_CORE_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 23ea9fcf..89176633 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -95,7 +95,6 @@ add_fmt_test(grisu-test) target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) -add_fmt_test(format-dyn-args-test) if (MSVC) target_compile_options(format-test PRIVATE /bigobj) endif () diff --git a/test/core-test.cc b/test/core-test.cc index 6a7c0d3b..c2e4593a 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -15,9 +15,8 @@ #include #include -#include "test-assert.h" - #include "gmock.h" +#include "test-assert.h" // Check if fmt/core.h compiles with windows.h included before it. #ifdef _WIN32 @@ -402,6 +401,83 @@ TEST(ArgTest, VisitInvalidArg) { fmt::visit_format_arg(visitor, arg); } +TEST(FormatDynArgsTest, Basic) { + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + + std::string result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("42 and abc1 and 1.5", result); +} + +TEST(FormatDynArgsTest, StringsAndRefs) { + // Unfortunately the tests are compiled with old ABI so strings use COW. + fmt::dynamic_format_arg_store store; + char str[] = "1234567890"; + store.push_back(str); + store.push_back(std::cref(str)); + store.push_back(fmt::string_view{str}); + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("1234567890 and X234567890 and X234567890", result); +} + +struct custom_type { + int i = 0; +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( + ctx.out(), std::declval())) { + return format_to(ctx.out(), "cust={}", p.i); + } +}; +FMT_END_NAMESPACE + +TEST(FormatDynArgsTest, CustomFormat) { + fmt::dynamic_format_arg_store store; + custom_type c{}; + store.push_back(c); + ++c.i; + store.push_back(c); + ++c.i; + store.push_back(std::cref(c)); + ++c.i; + + std::string result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + +TEST(FormatDynArgsTest, NamedArgByRef) { + fmt::dynamic_format_arg_store store; + + // Note: fmt::arg() constructs an object which holds a reference + // to its value. It's not an aggregate, so it doesn't extend the + // reference lifetime. As a result, it's a very bad idea passing temporary + // as a named argument value. Only GCC with optimization level >0 + // complains about this. + // + // A real life usecase is when you have both name and value alive + // guarantee their lifetime and thus don't want them to be copied into + // storages. + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + std::string result = fmt::vformat("{a1_}", // and {} and {}", + store); + + EXPECT_EQ("42", result); +} + TEST(StringViewTest, ValueType) { static_assert(std::is_same::value, ""); } diff --git a/test/format-dyn-args-test.cc b/test/format-dyn-args-test.cc index da4da7ff..acc5ef78 100644 --- a/test/format-dyn-args-test.cc +++ b/test/format-dyn-args-test.cc @@ -4,84 +4,3 @@ #include #include "gtest-extra.h" - -TEST(FormatDynArgsTest, Basic) { - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc1"); - store.push_back(1.5f); - - std::string result = fmt::vformat("{} and {} and {}", store); - - EXPECT_EQ("42 and abc1 and 1.5", result); -} - -TEST(FormatDynArgsTest, StringsAndRefs) { - // Unfortunately the tests are compiled with old ABI - // So strings use COW. - fmt::dynamic_format_arg_store store; - char str[]{"1234567890"}; - store.push_back(str); - store.push_back(std::cref(str)); - store.push_back(fmt::string_view{str}); - str[0] = 'X'; - - std::string result = fmt::vformat("{} and {} and {}", store); - - EXPECT_EQ("1234567890 and X234567890 and X234567890", result); -} - -struct custom_type { - int i{0}; -}; -FMT_BEGIN_NAMESPACE - -template <> struct formatter { - auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { - return ctx.begin(); - } - - template - auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( - ctx.out(), std::declval())) { - return format_to(ctx.out(), "cust={}", p.i); - } -}; -FMT_END_NAMESPACE - -TEST(FormatDynArgsTest, CustomFormat) { - fmt::dynamic_format_arg_store store; - custom_type c{}; - store.push_back(c); - ++c.i; - store.push_back(c); - ++c.i; - store.push_back(std::cref(c)); - ++c.i; - - std::string result = fmt::vformat("{} and {} and {}", store); - - EXPECT_EQ("cust=0 and cust=1 and cust=3", result); -} - -TEST(FormatDynArgsTest, NamedArgByRef) { - fmt::dynamic_format_arg_store store; - - // Note: fmt::arg() constructs an object which holds a reference - // to its value. It's not an aggregate, so it doesn't extend the - // reference lifetime. As a result, it's a very bad idea passing temporary - // as a named argument value. Only GCC with optimization level >0 - // complains about this. - // - // A real life usecase is when you have both name and value alive - // guarantee their lifetime and thus don't want them to be copied into - // storages. - int a1_val{42}; - auto a1 = fmt::arg("a1_", a1_val); - store.push_back(std::cref(a1)); - - std::string result = fmt::vformat("{a1_}", // and {} and {}", - store); - - EXPECT_EQ("42", result); -} From 026f99178ef0698041c34493d344eafcfd7bbe3d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 16 Mar 2020 19:10:41 -0700 Subject: [PATCH 008/113] Simplify dynamic store --- include/fmt/core.h | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 8dc770b4..f58f512b 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1206,14 +1206,6 @@ inline basic_format_arg make_arg(const T& value) { return make_arg(value); } -template struct is_string_view : std::false_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - template struct is_reference_wrapper : std::false_type {}; template @@ -1233,31 +1225,21 @@ class dyn_arg_storage { T value; template - FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value(arg) { - this->next = std::move(next); // Must be initialised after value. - } + FMT_CONSTEXPR storage_node(const Arg& arg) : value(arg) {} template - FMT_CONSTEXPR storage_node(const basic_string_view& arg, - owning_ptr&& next) - : value(arg.data(), arg.size()) { - this->next = std::move(next); // Must be initialised after value. - } + FMT_CONSTEXPR storage_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} }; - storage_node_base<>::owning_ptr head_{nullptr}; + storage_node_base<>::owning_ptr head_; public: - dyn_arg_storage() = default; - dyn_arg_storage(const dyn_arg_storage&) = delete; - dyn_arg_storage(dyn_arg_storage&&) = default; - - dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; - dyn_arg_storage& operator=(dyn_arg_storage&&) = default; - template const T& push(const Arg& arg) { - auto node = new storage_node(arg, std::move(head_)); + auto next = std::move(head_); + auto node = new storage_node(arg); head_.reset(node); + head_->next = std::move(next); return node->value; } }; @@ -1396,7 +1378,8 @@ class dynamic_format_arg_store using type = std::integral_constant< bool, !(internal::is_reference_wrapper::value || - internal::is_string_view::value || + std::is_same>::value || + std::is_same>::value || (mapped_type != internal::type::cstring_type && mapped_type != internal::type::string_type && mapped_type != internal::type::custom_type && From 2559983e7a1f61617f92023b60e8865c179baf2f Mon Sep 17 00:00:00 2001 From: Spirrwell Date: Tue, 17 Mar 2020 09:24:42 -0400 Subject: [PATCH 009/113] Color formatting fixed for wide strings (fixes issue #1594) (#1596) * Use std::char_traits::length for ansi_color_escape::begin -Fixes issue #1594 https://github.com/fmtlib/fmt/issues/1594 --- include/fmt/color.h | 2 +- test/color-test.cc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/fmt/color.h b/include/fmt/color.h index 3756ba3f..96d9ab6b 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -412,7 +412,7 @@ template struct ansi_color_escape { FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { - return buffer + std::strlen(buffer); + return buffer + std::char_traits::length(buffer); } private: diff --git a/test/color-test.cc b/test/color-test.cc index fde3a0c5..c1113a2e 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -50,6 +50,8 @@ TEST(ColorsTest, ColorsPrint) { TEST(ColorsTest, Format) { EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"), + L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m"); EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"), "\x1b[38;2;000;000;255mblue\x1b[0m"); EXPECT_EQ( From 3cf619de553f7cad2e4704745839f02cd6d46bf7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 17 Mar 2020 07:13:46 -0700 Subject: [PATCH 010/113] Simplify dynamic_format_arg_store --- include/fmt/core.h | 45 ++++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index f58f512b..70647c76 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1211,33 +1211,32 @@ template struct is_reference_wrapper : std::false_type {}; template struct is_reference_wrapper> : std::true_type {}; -class dyn_arg_storage { +class dynamic_arg_list { // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for // templates it doesn't complain about inability to deduce single translation // unit for placing vtable. So storage_node_base is made a fake template. - template struct storage_node_base { - using owning_ptr = std::unique_ptr>; - virtual ~storage_node_base() = default; - owning_ptr next; + template struct node { + virtual ~node() = default; + std::unique_ptr> next; }; - template struct storage_node : storage_node_base<> { + template struct typed_node : node<> { T value; template - FMT_CONSTEXPR storage_node(const Arg& arg) : value(arg) {} + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} template - FMT_CONSTEXPR storage_node(const basic_string_view& arg) + FMT_CONSTEXPR typed_node(const basic_string_view& arg) : value(arg.data(), arg.size()) {} }; - storage_node_base<>::owning_ptr head_; + std::unique_ptr> head_; public: template const T& push(const Arg& arg) { auto next = std::move(head_); - auto node = new storage_node(arg); + auto node = new typed_node(arg); head_.reset(node); head_->next = std::move(next); return node->value; @@ -1372,18 +1371,19 @@ class dynamic_format_arg_store private: using char_type = typename Context::char_type; - template struct need_dyn_copy { + template struct need_copy { static constexpr internal::type mapped_type = internal::mapped_type_constant::value; - using type = std::integral_constant< - bool, !(internal::is_reference_wrapper::value || + enum { + value = !(internal::is_reference_wrapper::value || std::is_same>::value || std::is_same>::value || (mapped_type != internal::type::cstring_type && mapped_type != internal::type::string_type && mapped_type != internal::type::custom_type && - mapped_type != internal::type::named_arg_type))>; + mapped_type != internal::type::named_arg_type)) + }; }; template @@ -1395,7 +1395,7 @@ class dynamic_format_arg_store // Storage of arguments not fitting into basic_format_arg must grow // without relocation because items in data_ refer to it. - internal::dyn_arg_storage storage_; + internal::dynamic_arg_list dynamic_args_; friend class basic_format_args; @@ -1408,15 +1408,6 @@ class dynamic_format_arg_store } public: - dynamic_format_arg_store() = default; - ~dynamic_format_arg_store() = default; - - dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; - dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; - - dynamic_format_arg_store(dynamic_format_arg_store&&) = default; - dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; - /** \rst Adds an argument into the dynamic store for later passing to a formating @@ -1438,8 +1429,8 @@ class dynamic_format_arg_store static_assert( !std::is_base_of, T>::value, "named arguments are not supported yet"); - if (internal::const_check(need_dyn_copy::type::value)) - emplace_arg(storage_.push>(arg)); + if (internal::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); else emplace_arg(arg); } @@ -1450,7 +1441,7 @@ class dynamic_format_arg_store */ template void push_back(std::reference_wrapper arg) { static_assert( - need_dyn_copy::type::value, + need_copy::value, "objects of built-in types and string views are always copied"); emplace_arg(arg.get()); } From 5d32ccfc3130fdd122c344b37087ac7fd8c2b62e Mon Sep 17 00:00:00 2001 From: "Attila M. Szilagyi" Date: Thu, 19 Mar 2020 07:01:51 -0700 Subject: [PATCH 011/113] Add back missing OUTPUT_NAME in target properties. (#1598) --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 466b342f..534ac33f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,7 @@ target_include_directories(fmt PUBLIC set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") set_target_properties(fmt PROPERTIES + OUTPUT_NAME "fmt" VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") From 52d0e1bbe35677cb949cf7e45c180d2e6d05d6fd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 19 Mar 2020 08:35:09 -0700 Subject: [PATCH 012/113] Don't use properties when setting FMT_LIB_NAME --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 534ac33f..b2e2ad7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,12 +198,12 @@ target_include_directories(fmt PUBLIC set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") set_target_properties(fmt PROPERTIES - OUTPUT_NAME "fmt" VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") -# Set FMT_LIB_NAME for pkg-config fmt.pc. -get_target_property(FMT_LIB_NAME fmt OUTPUT_NAME) +# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target +# property because it's not set by default. +set(FMT_LIB_NAME fmt) if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX}) endif () From d3e668418f1fbff11c238009f66581f2ace6141f Mon Sep 17 00:00:00 2001 From: Alberto Aguirre Date: Fri, 20 Mar 2020 08:46:31 -0500 Subject: [PATCH 013/113] Allow disabling floating point support (#1590) * Allow disabling floating point support Add FMT_USE_FLOAT, FMT_USE_DOUBLE and FMT_USE_LONG_DOUBLE to allow a user of the library to configure the float types they want to allow. This is specially useful in embedded environements where code size is important. * Avoid conditional macros to disable float support * Add is_supported_floating_point constexpr function * Fix empty-body warning --- include/fmt/core.h | 12 ++++++++++++ include/fmt/format.h | 32 +++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 70647c76..b5c5f252 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -316,6 +316,18 @@ struct int128_t {}; struct uint128_t {}; #endif +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif + +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif + +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { diff --git a/include/fmt/format.h b/include/fmt/format.h index 36c30b9f..af589ea3 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -724,6 +724,13 @@ FMT_CONSTEXPR bool is_negative(T) { return false; } +template ::value)> +FMT_CONSTEXPR bool is_supported_floating_point(T) { + return (std::is_same::value && FMT_USE_FLOAT) || + (std::is_same::value && FMT_USE_DOUBLE) || + (std::is_same::value && FMT_USE_LONG_DOUBLE); +} + // Smallest of uint32_t, uint64_t, uint128_t that is large enough to // represent all values of T. template @@ -1685,6 +1692,9 @@ template class basic_writer { template ::value)> void write(T value, format_specs specs = {}) { + if (const_check(!is_supported_floating_point(value))) { + return; + } float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. @@ -1883,6 +1893,10 @@ class arg_formatter_base { template ::value)> iterator operator()(T value) { + if (const_check(!is_supported_floating_point(value))) { + FMT_ASSERT(false, "unsupported float argument type"); + return out(); + } writer_.write(value, specs_ ? *specs_ : format_specs()); return out(); } @@ -2923,9 +2937,25 @@ struct formatter(specs_.type, eh)); break; case internal::type::float_type: + if (internal::const_check(FMT_USE_FLOAT)) { + internal::parse_float_type_spec(specs_, eh); + } else { + FMT_ASSERT(false, "float support disabled"); + } + break; case internal::type::double_type: + if (internal::const_check(FMT_USE_DOUBLE)) { + internal::parse_float_type_spec(specs_, eh); + } else { + FMT_ASSERT(false, "double support disabled"); + } + break; case internal::type::long_double_type: - internal::parse_float_type_spec(specs_, eh); + if (internal::const_check(FMT_USE_LONG_DOUBLE)) { + internal::parse_float_type_spec(specs_, eh); + } else { + FMT_ASSERT(false, "long double support disabled"); + } break; case internal::type::cstring_type: internal::handle_cstring_type_spec( From 29511694810d978d6061ee46293c9f88e4c4b222 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 20 Mar 2020 06:59:41 -0700 Subject: [PATCH 014/113] Move FMT_USE_FLOAT and friends to fmt/format.h --- include/fmt/core.h | 15 ++------------- include/fmt/format.h | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index b5c5f252..a9116314 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -284,7 +284,8 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, #ifndef FMT_ASSERT # ifdef NDEBUG -# define FMT_ASSERT(condition, message) + // FMT_ASSERT is not empty to avoid -Werror=empty-body. +# define FMT_ASSERT(condition, message) ((void)0) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ @@ -316,18 +317,6 @@ struct int128_t {}; struct uint128_t {}; #endif -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif - -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif - -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - // Casts a nonnegative integer to unsigned. template FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { diff --git a/include/fmt/format.h b/include/fmt/format.h index af589ea3..bb9bc1e5 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -138,6 +138,18 @@ FMT_END_NAMESPACE # endif #endif +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif + +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif + +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + // __builtin_clz is broken in clang with Microsoft CodeGen: // https://github.com/fmtlib/fmt/issues/519 #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER @@ -1893,11 +1905,10 @@ class arg_formatter_base { template ::value)> iterator operator()(T value) { - if (const_check(!is_supported_floating_point(value))) { + if (const_check(is_supported_floating_point(value))) + writer_.write(value, specs_ ? *specs_ : format_specs()); + else FMT_ASSERT(false, "unsupported float argument type"); - return out(); - } - writer_.write(value, specs_ ? *specs_ : format_specs()); return out(); } From dd97f4920caf7e03aff26f40048408de038963cf Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 21 Mar 2020 08:51:48 -0700 Subject: [PATCH 015/113] Improve exception safety in dynamic_format_arg_store --- include/fmt/core.h | 12 ++++++------ test/core-test.cc | 29 +++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index a9116314..d1242b59 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -284,7 +284,7 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, #ifndef FMT_ASSERT # ifdef NDEBUG - // FMT_ASSERT is not empty to avoid -Werror=empty-body. +// FMT_ASSERT is not empty to avoid -Werror=empty-body. # define FMT_ASSERT(condition, message) ((void)0) # else # define FMT_ASSERT(condition, message) \ @@ -1236,11 +1236,11 @@ class dynamic_arg_list { public: template const T& push(const Arg& arg) { - auto next = std::move(head_); - auto node = new typed_node(arg); - head_.reset(node); - head_->next = std::move(next); - return node->value; + auto node = std::unique_ptr>(new typed_node(arg)); + auto& value = node->value; + node->next = std::move(head_); + head_ = std::move(node); + return value; } }; } // namespace internal diff --git a/test/core-test.cc b/test/core-test.cc index c2e4593a..f19d0423 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -435,8 +435,7 @@ template <> struct formatter { } template - auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( - ctx.out(), std::declval())) { + auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) { return format_to(ctx.out(), "cust={}", p.i); } }; @@ -478,6 +477,32 @@ TEST(FormatDynArgsTest, NamedArgByRef) { EXPECT_EQ("42", result); } +struct copy_throwable { + copy_throwable() {} + copy_throwable(const copy_throwable&) { throw "deal with it"; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + +TEST(FormatDynArgsTest, ThrowOnCopy) { + fmt::dynamic_format_arg_store store; + store.push_back(std::string("foo")); + try { + store.push_back(copy_throwable()); + } catch (...) { + } + EXPECT_EQ(fmt::vformat("{}", store), "foo"); +} + TEST(StringViewTest, ValueType) { static_assert(std::is_same::value, ""); } From 08ca40ea9139b9066843e93e308927dc7bb69819 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 22 Mar 2020 07:57:56 -0700 Subject: [PATCH 016/113] Detect /utf-8 in MSVC --- include/fmt/core.h | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index d1242b59..74b9b08d 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -240,9 +240,9 @@ #endif #ifndef FMT_UNICODE -# define FMT_UNICODE 0 +# define FMT_UNICODE !FMT_MSC_VER #endif -#if FMT_UNICODE +#if FMT_UNICODE && FMT_MSC_VER # pragma execution_character_set("utf-8") #endif @@ -324,6 +324,13 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { return static_cast::type>(value); } +constexpr unsigned char micro[] = "\u00B5"; + +constexpr bool is_utf8() { + return FMT_UNICODE || + (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); +} + #ifdef __cpp_char8_t using char8_type = char8_t; #else @@ -1648,6 +1655,10 @@ typename buffer_context::iterator vformat_to( basic_format_args>> args); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); + +#ifndef _WIN32 +inline void vprint_mojibake(std::FILE*, string_view, format_args) {} +#endif } // namespace internal /** @@ -1738,14 +1749,12 @@ FMT_API void vprint(std::FILE*, string_view, format_args); template ::value)> inline void print(std::FILE* f, const S& format_str, Args&&... args) { -#if !defined(_WIN32) || FMT_UNICODE - vprint(f, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#else - internal::vprint_mojibake( - f, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#endif + return internal::is_utf8() + ? vprint(f, to_string_view(format_str), + internal::make_args_checked(format_str, args...)) + : internal::vprint_mojibake( + f, to_string_view(format_str), + internal::make_args_checked(format_str, args...)); } /** @@ -1762,14 +1771,12 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) { template ::value)> inline void print(const S& format_str, Args&&... args) { -#if !defined(_WIN32) || FMT_UNICODE - vprint(to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#else - internal::vprint_mojibake( - stdout, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#endif + return internal::is_utf8() + ? vprint(to_string_view(format_str), + internal::make_args_checked(format_str, args...)) + : internal::vprint_mojibake( + stdout, to_string_view(format_str), + internal::make_args_checked(format_str, args...)); } FMT_END_NAMESPACE From 01a172c9690236bb080b2c99da99d47cbc0459f9 Mon Sep 17 00:00:00 2001 From: Scott Ramsby Date: Mon, 23 Mar 2020 10:33:54 -0700 Subject: [PATCH 017/113] Add .vs to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 694f8f8f..208b808d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/ +.vs/ *.iml .idea/ From 69779b4ed6e78dfc5a87ee2fead6da3dcbf04134 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 24 Mar 2020 09:01:57 -0700 Subject: [PATCH 018/113] Fix handling of small precision in general format --- include/fmt/format.h | 6 ++++-- test/format-test.cc | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index bb9bc1e5..dc2531e8 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1152,9 +1152,11 @@ template class float_writer { // 1234e-6 -> 0.001234 *it++ = static_cast('0'); int num_zeros = -full_exp; - if (specs_.precision >= 0 && specs_.precision < num_zeros) - num_zeros = specs_.precision; int num_digits = num_digits_; + if (num_digits == 0 && specs_.precision >= 0 && + specs_.precision < num_zeros) { + num_zeros = specs_.precision; + } // Remove trailing zeros. if (!specs_.showpoint) while (num_digits > 0 && digits_[num_digits - 1] == '0') --num_digits; diff --git a/test/format-test.cc b/test/format-test.cc index a2223749..4e553022 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1125,6 +1125,7 @@ TEST(FormatterTest, Precision) { format("{:.838A}", -2.14001164E+38)); EXPECT_EQ("123.", format("{:#.0f}", 123.0)); EXPECT_EQ("1.23", format("{:.02f}", 1.234)); + EXPECT_EQ("0.001", format("{:.1g}", 0.001)); EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast(0xcafe)), format_error, From 664dd88e3175b92cdac96cb811dab94b6f3bdc62 Mon Sep 17 00:00:00 2001 From: Scott Ramsby Date: Wed, 11 Mar 2020 17:43:14 -0700 Subject: [PATCH 019/113] Enable FMT_STRING() use with types other than string literals --- include/fmt/core.h | 3 +++ include/fmt/format.h | 40 ++++++++++++++++++++++++++-------------- test/format-test.cc | 14 ++++++++++++++ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 74b9b08d..f038e792 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -371,6 +371,9 @@ template class basic_string_view { the size with ``std::char_traits::length``. \endrst */ +#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr. + FMT_CONSTEXPR +#endif basic_string_view(const Char* s) : data_(s), size_(std::char_traits::length(s)) {} diff --git a/include/fmt/format.h b/include/fmt/format.h index dc2531e8..f81ed97e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2238,6 +2238,7 @@ enum class arg_id_kind { none, index, name }; // An argument reference. template struct arg_ref { FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + FMT_CONSTEXPR explicit arg_ref(int index) : kind(arg_id_kind::index), val(index) {} FMT_CONSTEXPR explicit arg_ref(basic_string_view name) @@ -3525,9 +3526,21 @@ template struct udl_arg { } }; +// Converts string literals to basic_string_view. template -FMT_CONSTEXPR basic_string_view literal_to_view(const Char (&s)[N]) { - return {s, N - 1}; +FMT_CONSTEXPR basic_string_view compile_string_to_view( + const Char (&s)[N]) { + // Remove trailing null character if needed. Won't be present if this is used + // with raw character array (i.e. not defined as a string). + return {s, + N - ((std::char_traits::to_int_type(s[N - 1]) == 0) ? 1 : 0)}; +} + +// Converts string_view to basic_string_view. +template +FMT_CONSTEXPR basic_string_view compile_string_to_view( + const std_string_view& s) { + return {s.data(), s.size()}; } } // namespace internal @@ -3585,18 +3598,17 @@ FMT_CONSTEXPR internal::udl_arg operator"" _a(const wchar_t* s, #endif // FMT_USE_USER_DEFINED_LITERALS FMT_END_NAMESPACE -#define FMT_STRING_IMPL(s, ...) \ - [] { \ - /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_COMPILE_STRING : fmt::compile_string { \ - using char_type = fmt::remove_cvref_t; \ - FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \ - operator fmt::basic_string_view() const { \ - /* FMT_STRING only accepts string literals. */ \ - return fmt::internal::literal_to_view(s); \ - } \ - }; \ - return FMT_COMPILE_STRING(); \ +#define FMT_STRING_IMPL(s, ...) \ + [] { \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_COMPILE_STRING : fmt::compile_string { \ + using char_type = fmt::remove_cvref_t; \ + FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \ + operator fmt::basic_string_view() const { \ + return fmt::internal::compile_string_to_view(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ }() /** diff --git a/test/format-test.cc b/test/format-test.cc index 4e553022..812c1dcc 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -46,6 +46,7 @@ using fmt::format_error; using fmt::memory_buffer; using fmt::string_view; using fmt::wmemory_buffer; +using fmt::wstring_view; using fmt::internal::basic_writer; using fmt::internal::max_value; @@ -1854,10 +1855,23 @@ TEST(FormatTest, UnpackedArgs) { struct string_like {}; fmt::string_view to_string_view(string_like) { return "foo"; } +constexpr char with_null[3] = {'{', '}', '\0'}; +constexpr char no_null[2] = {'{', '}'}; + TEST(FormatTest, CompileTimeString) { EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42)); EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like())); + (void)with_null; + (void)no_null; +#if __cplusplus >= 201703L + EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42)); + EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42)); +#endif +#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L + EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42)); + EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); +#endif } TEST(FormatTest, CustomFormatCompileTimeString) { From 96c68afe6925bbdcb0632c12555d3734223a8abd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 25 Mar 2020 07:08:14 -0700 Subject: [PATCH 020/113] Fix -Wsign-conversion warnings --- include/fmt/core.h | 6 ++++-- include/fmt/format-inl.h | 42 ++++++++++++++++++++++------------------ include/fmt/format.h | 2 +- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index f038e792..cf294e36 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -716,8 +716,10 @@ template class buffer { /** Appends data to the end of the buffer. */ template void append(const U* begin, const U* end); - T& operator[](std::size_t index) { return ptr_[index]; } - const T& operator[](std::size_t index) const { return ptr_[index]; } + template T& operator[](I index) { return ptr_[index]; } + template const T& operator[](I index) const { + return ptr_[index]; + } }; // A container-backed buffer. diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 5d294bc7..674bddbf 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -507,20 +507,23 @@ class bigint { basic_memory_buffer bigits_; int exp_; + bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } + bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } + static FMT_CONSTEXPR_DECL const int bigit_bits = bits::value; friend struct formatter; void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast(bigits_[index]) - other - borrow; - bigits_[index] = static_cast(result); + auto result = static_cast((*this)[index]) - other - borrow; + (*this)[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; - bigits_.resize(num_bigits + 1); + while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. @@ -621,7 +624,7 @@ class bigint { int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs.bigits_[i], rhs_bigit = rhs.bigits_[j]; + bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; @@ -636,7 +639,7 @@ class bigint { if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; if (max_lhs_bigits > num_rhs_bigits) return 1; auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n.bigits_[i - n.exp_] : 0; + return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; }; double_bigit borrow = 0; int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); @@ -676,7 +679,7 @@ class bigint { basic_memory_buffer n(std::move(bigits_)); int num_bigits = static_cast(bigits_.size()); int num_result_bigits = 2 * num_bigits; - bigits_.resize(num_result_bigits); + bigits_.resize(to_unsigned(num_result_bigits)); using accumulator_t = conditional_t; auto sum = accumulator_t(); for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { @@ -686,7 +689,7 @@ class bigint { // Most terms are multiplied twice which can be optimized in the future. sum += static_cast(n[i]) * n[j]; } - bigits_[bigit_index] = static_cast(sum); + (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; // Compute the carry. } // Do the same for the top half. @@ -694,7 +697,7 @@ class bigint { ++bigit_index) { for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) sum += static_cast(n[i++]) * n[j--]; - bigits_[bigit_index] = static_cast(sum); + (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; } --num_result_bigits; @@ -708,11 +711,11 @@ class bigint { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; int num_bigits = static_cast(bigits_.size()); - FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1] != 0, ""); + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); int exp_difference = exp_ - divisor.exp_; if (exp_difference > 0) { // Align bigints by adding trailing zeros to simplify subtraction. - bigits_.resize(num_bigits + exp_difference); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) bigits_[j] = bigits_[i]; std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); @@ -1023,7 +1026,7 @@ void fallback_format(Double d, buffer& buf, int& exp10) { if (result > 0 || (result == 0 && (digit % 2) != 0)) ++data[num_digits - 1]; } - buf.resize(num_digits); + buf.resize(to_unsigned(num_digits)); exp10 -= num_digits - 1; return; } @@ -1157,10 +1160,11 @@ int snprintf_float(T value, int precision, float_specs specs, buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially. continue; } - unsigned size = to_unsigned(result); + auto size = to_unsigned(result); // Size equal to capacity means that the last character was truncated. if (size >= capacity) { - buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'. + // Add 1 for the terminating '\0'. + buf.reserve(size + offset + 1); continue; } auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; @@ -1175,7 +1179,7 @@ int snprintf_float(T value, int precision, float_specs specs, --p; } while (is_digit(*p)); int fraction_size = static_cast(end - p - 1); - std::memmove(p, p + 1, fraction_size); + std::memmove(p, p + 1, to_unsigned(fraction_size)); buf.resize(size - 1); return -fraction_size; } @@ -1204,9 +1208,9 @@ int snprintf_float(T value, int precision, float_specs specs, while (*fraction_end == '0') --fraction_end; // Move the fractional part left to get rid of the decimal point. fraction_size = static_cast(fraction_end - begin - 1); - std::memmove(begin + 1, begin + 2, fraction_size); + std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); } - buf.resize(fraction_size + offset + 1); + buf.resize(to_unsigned(fraction_size) + offset + 1); return exp - fraction_size; } } @@ -1277,7 +1281,7 @@ template <> struct formatter { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { - auto value = n.bigits_[i - 1]; + auto value = n.bigits_[i - 1u]; if (first) { out = format_to(out, "{:x}", value); first = false; @@ -1313,7 +1317,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { } if (auto num_chars_left = s.data() + s.size() - p) { char buf[2 * block_size - 1] = {}; - memcpy(buf, p, num_chars_left); + memcpy(buf, p, to_unsigned(num_chars_left)); p = buf; do { p = transcode(p); diff --git a/include/fmt/format.h b/include/fmt/format.h index f81ed97e..c5ed29be 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2427,7 +2427,7 @@ FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end, auto c = *begin; if (c == '{') return handler.on_error("invalid fill character '{'"), begin; - handler.on_fill(basic_string_view(begin, p - begin)); + handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); begin = p + 1; } else ++begin; From 21a295c272f7ee809774805fd8e31361ac882be2 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 25 Mar 2020 08:14:31 -0700 Subject: [PATCH 021/113] Undo comment change --- include/fmt/format-inl.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 674bddbf..76e5682b 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1163,8 +1163,7 @@ int snprintf_float(T value, int precision, float_specs specs, auto size = to_unsigned(result); // Size equal to capacity means that the last character was truncated. if (size >= capacity) { - // Add 1 for the terminating '\0'. - buf.reserve(size + offset + 1); + buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'. continue; } auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; From 2864e8432a3fd08460f7ea7b2c445b4c01ae43a4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 26 Mar 2020 07:06:25 -0700 Subject: [PATCH 022/113] Update readme and add compatibility option --- ChangeLog.rst | 51 ++++++++++++++++++++++++++++++++++++++++++++ include/fmt/format.h | 4 ++++ 2 files changed, 55 insertions(+) diff --git a/ChangeLog.rst b/ChangeLog.rst index 75a009e4..6d4ecbfb 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,54 @@ +6.2.0 - TBD +----------- + +* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to + ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically + include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be + disabled in the new major release. + +* Improved error reporting when trying to format an object of a non-formattable + type: + + .. code:: c++ + + fmt::format("{}", S()); + + now gives:: + + include/fmt/core.h:1015:5: error: static_assert failed due to requirement + 'formattable' "Cannot format argument. To make type T formattable provide a + formatter specialization: + https://fmt.dev/latest/api.html#formatting-user-defined-types" + static_assert( + ^ + ... + note: in instantiation of function template specialization + 'fmt::v6::format' requested here + fmt::format("{}", S()); + ^ + + if ``S`` is not formattable. + +* Added precision overflow detection in floating-point formatting. + +* Switched links to HTTPS in README + (`#1481 `_). + Thanks `@imba-tjd (谭九鼎) `_. + +* Improved detection of the ``fallthrough`` attribute + (`#1469 `_, + `#1475 `_). + Thanks `@federico-busato (Federico) `_, + `@chronoxor (Ivan Shynkarenka) `_. + +* Fixed various warnings and compilation issues + (`#1433 `_, + `#1470 `_, + `#1480 `_). + Thanks `@marti4d (Chris Martin) `_, + `@iPherian `_, + `@parkertomatoes `_. + 6.1.2 - 2019-12-11 ------------------ diff --git a/include/fmt/format.h b/include/fmt/format.h index c5ed29be..c6898dae 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -43,6 +43,10 @@ #include "core.h" +#ifdef FMT_DEPRECATED_INCLUDE_OS +# include "os.h" +#endif + #ifdef __INTEL_COMPILER # define FMT_ICC_VERSION __INTEL_COMPILER #elif defined(__ICL) From 770a94edefd7589f7fbd410346f68f503d988bfe Mon Sep 17 00:00:00 2001 From: Scott Ramsby Date: Wed, 25 Mar 2020 10:42:29 -0700 Subject: [PATCH 023/113] Use FMT_THROW macro where applicable --- include/fmt/format-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 76e5682b..f632714d 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1371,7 +1371,7 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { if (!WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), static_cast(u16.size()), &written, nullptr)) { - throw format_error("failed to write to console"); + FMT_THROW(format_error("failed to write to console")); } return; } From 80ce222ca64be694f2bf6472169e571d14c68dd3 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 28 Mar 2020 06:31:38 -0700 Subject: [PATCH 024/113] Fix wide print overload (#1609) --- include/fmt/core.h | 19 ++++++++++--------- test/format-test.cc | 2 ++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index cf294e36..ad8194c6 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -326,8 +326,8 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { constexpr unsigned char micro[] = "\u00B5"; -constexpr bool is_utf8() { - return FMT_UNICODE || +template constexpr bool is_unicode() { + return FMT_UNICODE || sizeof(Char) != 1 || (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); } @@ -1659,8 +1659,11 @@ typename buffer_context::iterator vformat_to( buffer& buf, basic_string_view format_str, basic_format_args>> args); -FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); +template ::value)> +inline void vprint_mojibake(std::FILE*, basic_string_view, const Args&) {} +FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif @@ -1751,10 +1754,9 @@ FMT_API void vprint(std::FILE*, string_view, format_args); fmt::print(stderr, "Don't {}!", "panic"); \endrst */ -template ::value)> +template > inline void print(std::FILE* f, const S& format_str, Args&&... args) { - return internal::is_utf8() + return internal::is_unicode() ? vprint(f, to_string_view(format_str), internal::make_args_checked(format_str, args...)) : internal::vprint_mojibake( @@ -1773,10 +1775,9 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) { fmt::print("Elapsed time: {0:.2f} seconds", 1.23); \endrst */ -template ::value)> +template > inline void print(const S& format_str, Args&&... args) { - return internal::is_utf8() + return internal::is_unicode() ? vprint(to_string_view(format_str), internal::make_args_checked(format_str, args...)) : internal::vprint_mojibake( diff --git a/test/format-test.cc b/test/format-test.cc index 812c1dcc..12e32139 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1757,6 +1757,8 @@ TEST(FormatTest, Print) { EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"), "Don't panic!"); #endif + // Check that the wide print overload compiles. + if (fmt::internal::const_check(false)) fmt::print(L"test"); } TEST(FormatTest, Variadic) { From a133187a8cd0d876fe147420a9367f7cf05dd0c7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 28 Mar 2020 09:44:27 -0700 Subject: [PATCH 025/113] Update changelog --- ChangeLog.rst | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 6d4ecbfb..f4a66746 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -4,7 +4,7 @@ * Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be - disabled in the new major release. + disabled in the next major release. * Improved error reporting when trying to format an object of a non-formattable type: @@ -29,8 +29,20 @@ if ``S`` is not formattable. +* Always print decimal point if ``#`` is specified + (`#1476 `_, + `#1498 `_).: + + .. code:: c++ + + fmt::print("{:#.0f}", 42.0); + + now prints ``42.``. + * Added precision overflow detection in floating-point formatting. +* Improved UTF-8 handling. + * Switched links to HTTPS in README (`#1481 `_). Thanks `@imba-tjd (谭九鼎) `_. @@ -44,10 +56,14 @@ * Fixed various warnings and compilation issues (`#1433 `_, `#1470 `_, - `#1480 `_). + `#1480 `_, + `#1485 `_, + `#1492 `_). Thanks `@marti4d (Chris Martin) `_, `@iPherian `_, - `@parkertomatoes `_. + `@parkertomatoes `_, + `@gsjaardema (Greg Sjaardema) `_, + `@chronoxor (Ivan Shynkarenka) `_. 6.1.2 - 2019-12-11 ------------------ From 1a62711d0148b2701deb02d3ddd8b3367c9e83ff Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 31 Mar 2020 07:54:15 -0700 Subject: [PATCH 026/113] Reduce binary size --- include/fmt/format.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index c6898dae..c5668f1d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -44,7 +44,7 @@ #include "core.h" #ifdef FMT_DEPRECATED_INCLUDE_OS -# include "os.h" +# include "os.h" #endif #ifdef __INTEL_COMPILER @@ -67,6 +67,12 @@ # define FMT_HAS_BUILTIN(x) 0 #endif +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + #if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] @@ -1392,7 +1398,7 @@ template struct nonfinite_writer { }; template -OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { +FMT_NOINLINE 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); From e588b02b172f677cdedda602901055ef243d0935 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 1 Apr 2020 08:42:14 -0700 Subject: [PATCH 027/113] Fix posix-mock-test --- include/fmt/os.h | 16 ++++------ test/posix-mock-test.cc | 71 ++++++++++++++++++++++------------------- test/posix-mock.h | 8 +++++ 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/include/fmt/os.h b/include/fmt/os.h index 95fafcf1..f43bb089 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -15,11 +15,10 @@ #include #include // for locale_t +#include #include #include // for strtod_l -#include - #if defined __APPLE__ || defined(__FreeBSD__) # include // for LC_NUMERIC_MASK on OS X #endif @@ -351,12 +350,6 @@ class Locale { # ifdef _WIN32 using locale_t = _locale_t; - enum { LC_NUMERIC_MASK = LC_NUMERIC }; - - static locale_t newlocale(int category_mask, const char* locale, locale_t) { - return _create_locale(category_mask, locale); - } - static void freelocale(locale_t locale) { _free_locale(locale); } static double strtod_l(const char* nptr, char** endptr, _locale_t locale) { @@ -371,7 +364,12 @@ class Locale { Locale(const Locale&) = delete; void operator=(const Locale&) = delete; - Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) { + Locale() { +# ifndef _WIN32 + locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr)); +# else + locale_ = _create_locale(LC_NUMERIC, "C"); +# endif if (!locale_) FMT_THROW(system_error(errno, "cannot create locale")); } ~Locale() { freelocale(locale_); } diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index 6019caa9..acc0e2f5 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -11,13 +11,15 @@ #endif #include "posix-mock.h" -#include "../src/os.cc" #include #include + #include #include +#include "../src/os.cc" + #ifdef _WIN32 # include # undef max @@ -215,10 +217,10 @@ TEST(UtilTest, GetPageSize) { } TEST(FileTest, OpenRetry) { - write_file("test", "there must be something here"); + write_file("temp", "there must be something here"); std::unique_ptr f{nullptr}; - EXPECT_RETRY(f.reset(new file("test", file::RDONLY)), open, - "cannot open file test"); + EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open, + "cannot open file temp"); # ifndef _WIN32 char c = 0; f->read(&c, 1); @@ -230,14 +232,15 @@ TEST(FileTest, CloseNoRetryInDtor) { file::pipe(read_end, write_end); std::unique_ptr f(new file(std::move(read_end))); int saved_close_count = 0; - EXPECT_WRITE(stderr, - { - close_count = 1; - f.reset(nullptr); - saved_close_count = close_count; - close_count = 0; - }, - format_system_error(EINTR, "cannot close file") + "\n"); + EXPECT_WRITE( + stderr, + { + close_count = 1; + f.reset(nullptr); + saved_close_count = close_count; + close_count = 0; + }, + format_system_error(EINTR, "cannot close file") + "\n"); EXPECT_EQ(2, saved_close_count); } @@ -252,8 +255,8 @@ TEST(FileTest, CloseNoRetry) { TEST(FileTest, Size) { std::string content = "top secret, destroy before reading"; - write_file("test", content); - file f("test", file::RDONLY); + write_file("temp", content); + file f("temp", file::RDONLY); EXPECT_GE(f.size(), 0); EXPECT_EQ(content.size(), static_cast(f.size())); # ifdef _WIN32 @@ -270,8 +273,8 @@ TEST(FileTest, Size) { } TEST(FileTest, MaxSize) { - write_file("test", ""); - file f("test", file::RDONLY); + write_file("temp", ""); + file f("temp", file::RDONLY); fstat_sim = MAX_SIZE; EXPECT_GE(f.size(), 0); EXPECT_EQ(max_file_size(), f.size()); @@ -385,10 +388,10 @@ TEST(FileTest, FdopenNoRetry) { } TEST(BufferedFileTest, OpenRetry) { - write_file("test", "there must be something here"); + write_file("temp", "there must be something here"); std::unique_ptr f{nullptr}; - EXPECT_RETRY(f.reset(new buffered_file("test", "r")), fopen, - "cannot open file test"); + EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen, + "cannot open file temp"); # ifndef _WIN32 char c = 0; if (fread(&c, 1, 1, f->get()) < 1) @@ -401,14 +404,15 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) { file::pipe(read_end, write_end); std::unique_ptr f(new buffered_file(read_end.fdopen("r"))); int saved_fclose_count = 0; - EXPECT_WRITE(stderr, - { - fclose_count = 1; - f.reset(nullptr); - saved_fclose_count = fclose_count; - fclose_count = 0; - }, - format_system_error(EINTR, "cannot close file") + "\n"); + EXPECT_WRITE( + stderr, + { + fclose_count = 1; + f.reset(nullptr); + saved_fclose_count = fclose_count; + fclose_count = 0; + }, + format_system_error(EINTR, "cannot close file") + "\n"); EXPECT_EQ(2, saved_fclose_count); } @@ -492,11 +496,6 @@ double _strtod_l(const char* nptr, char** endptr, _locale_t locale) { # define FMT_LOCALE_THROW # endif -LocaleType newlocale(int category_mask, const char* locale, - LocaleType base) FMT_LOCALE_THROW { - return LocaleMock::instance->newlocale(category_mask, locale, base); -} - # if defined(__APPLE__) || \ (defined(__FreeBSD__) && __FreeBSD_version < 1200002) typedef int FreeLocaleResult; @@ -516,12 +515,18 @@ double strtod_l(const char* nptr, char** endptr, # undef FMT_LOCALE_THROW +# ifndef _WIN32 +locale_t test::newlocale(int category_mask, const char* locale, locale_t base) { + return LocaleMock::instance->newlocale(category_mask, locale, base); +} + TEST(LocaleTest, LocaleMock) { ScopedMock mock; LocaleType locale = reinterpret_cast(11); EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale)); - newlocale(222, "foo", locale); + FMT_SYSTEM(newlocale(222, "foo", locale)); } +# endif TEST(LocaleTest, Locale) { # ifndef LC_NUMERIC_MASK diff --git a/test/posix-mock.h b/test/posix-mock.h index 028aeee6..e76af700 100644 --- a/test/posix-mock.h +++ b/test/posix-mock.h @@ -9,7 +9,11 @@ #define FMT_POSIX_TEST_H #include +#include #include +#ifdef __APPLE__ +# include +#endif #ifdef _WIN32 # include @@ -62,6 +66,10 @@ int pipe(int* pfds, unsigned psize, int textmode); FILE* fopen(const char* filename, const char* mode); int fclose(FILE* stream); int(fileno)(FILE* stream); + +#if defined(FMT_LOCALE) && !defined(_WIN32) +locale_t newlocale(int category_mask, const char* locale, locale_t base); +#endif } // namespace test #define FMT_SYSTEM(call) test::call From 73c8437485bf13e92b525e1f107fee2c5d7375c3 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 1 Apr 2020 09:30:28 -0700 Subject: [PATCH 028/113] Follow naming conventions --- include/fmt/os.h | 17 +++++----- test/os-test.cc | 4 +-- test/posix-mock-test.cc | 70 ++++++++++++++++++++--------------------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/include/fmt/os.h b/include/fmt/os.h index f43bb089..a1a0d03b 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -345,15 +345,15 @@ long getpagesize(); #ifdef FMT_LOCALE // A "C" numeric locale. -class Locale { +class locale { private: # ifdef _WIN32 using locale_t = _locale_t; - static void freelocale(locale_t locale) { _free_locale(locale); } + static void freelocale(locale_t loc) { _free_locale(loc); } - static double strtod_l(const char* nptr, char** endptr, _locale_t locale) { - return _strtod_l(nptr, endptr, locale); + static double strtod_l(const char* nptr, char** endptr, _locale_t loc) { + return _strtod_l(nptr, endptr, loc); } # endif @@ -361,10 +361,10 @@ class Locale { public: using type = locale_t; - Locale(const Locale&) = delete; - void operator=(const Locale&) = delete; + locale(const locale&) = delete; + void operator=(const locale&) = delete; - Locale() { + locale() { # ifndef _WIN32 locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr)); # else @@ -372,7 +372,7 @@ class Locale { # endif if (!locale_) FMT_THROW(system_error(errno, "cannot create locale")); } - ~Locale() { freelocale(locale_); } + ~locale() { freelocale(locale_); } type get() const { return locale_; } @@ -385,6 +385,7 @@ class Locale { return result; } }; +using Locale FMT_DEPRECATED_ALIAS = locale; #endif // FMT_LOCALE FMT_END_NAMESPACE diff --git a/test/os-test.cc b/test/os-test.cc index 0854e4fa..ee963087 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -489,9 +489,9 @@ TEST(FileTest, Fdopen) { # ifdef FMT_LOCALE TEST(LocaleTest, Strtod) { - fmt::Locale locale; + fmt::locale loc; const char *start = "4.2", *ptr = start; - EXPECT_EQ(4.2, locale.strtod(ptr)); + EXPECT_EQ(4.2, loc.strtod(ptr)); EXPECT_EQ(start + 3, ptr); } # endif diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index acc0e2f5..c4cd5530 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -53,7 +53,7 @@ std::size_t read_nbyte; std::size_t write_nbyte; bool sysconf_error; -enum FStatSimulation { NONE, MAX_SIZE, ERROR } fstat_sim; +enum { NONE, MAX_SIZE, ERROR } fstat_sim; } // namespace #define EMULATE_EINTR(func, error_result) \ @@ -437,33 +437,33 @@ TEST(BufferedFileTest, FilenoNoRetry) { } #endif // FMT_USE_FCNTL -struct TestMock { - static TestMock* instance; -} * TestMock::instance; +struct test_mock { + static test_mock* instance; +} * test_mock::instance; TEST(ScopedMock, Scope) { { - ScopedMock mock; - EXPECT_EQ(&mock, TestMock::instance); - TestMock& copy = mock; + ScopedMock mock; + EXPECT_EQ(&mock, test_mock::instance); + test_mock& copy = mock; static_cast(copy); } - EXPECT_EQ(nullptr, TestMock::instance); + EXPECT_EQ(nullptr, test_mock::instance); } #ifdef FMT_LOCALE -typedef fmt::Locale::type LocaleType; +typedef fmt::locale::type locale_type; -struct LocaleMock { - static LocaleMock* instance; - MOCK_METHOD3(newlocale, LocaleType(int category_mask, const char* locale, - LocaleType base)); - MOCK_METHOD1(freelocale, void(LocaleType locale)); +struct locale_mock { + static locale_mock* instance; + MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale, + locale_type base)); + MOCK_METHOD1(freelocale, void(locale_type locale)); MOCK_METHOD3(strtod_l, - double(const char* nptr, char** endptr, LocaleType locale)); -} * LocaleMock::instance; + double(const char* nptr, char** endptr, locale_type locale)); +} * locale_mock::instance; # ifdef _MSC_VER # pragma warning(push) @@ -474,15 +474,15 @@ struct LocaleMock { # endif _locale_t _create_locale(int category, const char* locale) { - return LocaleMock::instance->newlocale(category, locale, 0); + return locale_mock::instance->newlocale(category, locale, 0); } void _free_locale(_locale_t locale) { - LocaleMock::instance->freelocale(locale); + locale_mock::instance->freelocale(locale); } double _strtod_l(const char* nptr, char** endptr, _locale_t locale) { - return LocaleMock::instance->strtod_l(nptr, endptr, locale); + return locale_mock::instance->strtod_l(nptr, endptr, locale); } # ifdef __clang__ # pragma clang diagnostic pop @@ -503,26 +503,26 @@ typedef int FreeLocaleResult; typedef void FreeLocaleResult; # endif -FreeLocaleResult freelocale(LocaleType locale) FMT_LOCALE_THROW { - LocaleMock::instance->freelocale(locale); +FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW { + locale_mock::instance->freelocale(locale); return FreeLocaleResult(); } double strtod_l(const char* nptr, char** endptr, - LocaleType locale) FMT_LOCALE_THROW { - return LocaleMock::instance->strtod_l(nptr, endptr, locale); + locale_type locale) FMT_LOCALE_THROW { + return locale_mock::instance->strtod_l(nptr, endptr, locale); } # undef FMT_LOCALE_THROW # ifndef _WIN32 locale_t test::newlocale(int category_mask, const char* locale, locale_t base) { - return LocaleMock::instance->newlocale(category_mask, locale, base); + return locale_mock::instance->newlocale(category_mask, locale, base); } TEST(LocaleTest, LocaleMock) { - ScopedMock mock; - LocaleType locale = reinterpret_cast(11); + ScopedMock mock; + locale_type locale = reinterpret_cast(11); EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale)); FMT_SYSTEM(newlocale(222, "foo", locale)); } @@ -532,26 +532,26 @@ TEST(LocaleTest, Locale) { # ifndef LC_NUMERIC_MASK enum { LC_NUMERIC_MASK = LC_NUMERIC }; # endif - ScopedMock mock; - LocaleType impl = reinterpret_cast(42); + ScopedMock mock; + locale_type impl = reinterpret_cast(42); EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr)) .WillOnce(Return(impl)); EXPECT_CALL(mock, freelocale(impl)); - fmt::Locale locale; - EXPECT_EQ(impl, locale.get()); + fmt::locale loc; + EXPECT_EQ(impl, loc.get()); } TEST(LocaleTest, Strtod) { - ScopedMock mock; + ScopedMock mock; EXPECT_CALL(mock, newlocale(_, _, _)) - .WillOnce(Return(reinterpret_cast(42))); + .WillOnce(Return(reinterpret_cast(42))); EXPECT_CALL(mock, freelocale(_)); - fmt::Locale locale; + fmt::locale loc; const char* str = "4.2"; char end = 'x'; - EXPECT_CALL(mock, strtod_l(str, _, locale.get())) + EXPECT_CALL(mock, strtod_l(str, _, loc.get())) .WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777))); - EXPECT_EQ(777, locale.strtod(str)); + EXPECT_EQ(777, loc.strtod(str)); EXPECT_EQ(&end, str); } From d1d653d8955e1948e904e106e196a3d225b579bc Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 2 Apr 2020 06:58:38 -0700 Subject: [PATCH 029/113] Implement the L specifier --- doc/syntax.rst | 20 ++++++++++---------- include/fmt/format.h | 1 + test/format-test.cc | 5 +++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/doc/syntax.rst b/doc/syntax.rst index af65616a..bf2f1b6b 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -81,8 +81,8 @@ The general form of a *standard format specifier* is: sign: "+" | "-" | " " width: `integer` | "{" `arg_id` "}" precision: `integer` | "{" `arg_id` "}" - type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s" - int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X" + type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s" + int_type: "b" | "B" | "d" | "o" | "x" | "X" The *fill* character can be any Unicode code point other than ``'{'`` or ``'}'``. The presence of a fill character is signaled by the character following @@ -143,7 +143,7 @@ conversions, trailing zeros are not removed from the result. .. ifconfig:: False The ``','`` option signals the use of a comma for a thousands separator. - For a locale aware separator, use the ``'n'`` integer presentation type + For a locale aware separator, use the ``'L'`` integer presentation type instead. *width* is a decimal integer defining the minimum field width. If not @@ -214,9 +214,9 @@ The available integer presentation types are: | | ``'#'`` option with this type adds the prefix ``"0X"`` | | | to the output value. | +---------+----------------------------------------------------------+ -| ``'n'`` | Number. This is the same as ``'d'``, except that it uses | -| | the current locale setting to insert the appropriate | -| | number separator characters. | +| ``'L'`` | Locale-specific format. This is the same as ``'d'``, | +| | except that it uses the current locale setting to insert | +| | the appropriate number separator characters. | +---------+----------------------------------------------------------+ | none | The same as ``'d'``. | +---------+----------------------------------------------------------+ @@ -261,9 +261,9 @@ The available presentation types for floating-point values are: | | ``'E'`` if the number gets too large. The | | | representations of infinity and NaN are uppercased, too. | +---------+----------------------------------------------------------+ -| ``'n'`` | Number. This is the same as ``'g'``, except that it uses | -| | the current locale setting to insert the appropriate | -| | number separator characters. | +| ``'L'`` | Locale-specific format. This is the same as ``'g'``, | +| | except that it uses the current locale setting to insert | +| | the appropriate number separator characters. | +---------+----------------------------------------------------------+ | none | Similar to ``'g'``, except that fixed-point notation, | | | when used, has at least one digit past the decimal | @@ -403,7 +403,7 @@ Using the comma as a thousands separator:: #include - auto s = fmt::format(std::locale("en_US.UTF-8"), "{:n}", 1234567890); + auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); // s == "1,234,567,890" .. ifconfig:: False diff --git a/include/fmt/format.h b/include/fmt/format.h index c5668f1d..4e96539f 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1236,6 +1236,7 @@ FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) { handler.on_oct(); break; case 'n': + case 'L': handler.on_num(); break; default: diff --git a/test/format-test.cc b/test/format-test.cc index 12e32139..e9304cee 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1256,7 +1256,7 @@ TEST(FormatterTest, FormatShort) { TEST(FormatterTest, FormatInt) { EXPECT_THROW_MSG(format("{0:v", 42), format_error, "missing '}' in format string"); - check_unknown_types(42, "bBdoxXn", "integer"); + check_unknown_types(42, "bBdoxXnL", "integer"); } TEST(FormatterTest, FormatBin) { @@ -1397,6 +1397,7 @@ TEST(FormatterTest, FormatOct) { TEST(FormatterTest, FormatIntLocale) { EXPECT_EQ("1234", format("{:n}", 1234)); + EXPECT_EQ("1234", format("{:L}", 1234)); } struct ConvertibleToLongLong { @@ -1491,7 +1492,7 @@ TEST(FormatterTest, FormatLongDouble) { } TEST(FormatterTest, FormatChar) { - const char types[] = "cbBdoxXn"; + const char types[] = "cbBdoxXnL"; check_unknown_types('a', types, "char"); EXPECT_EQ("a", format("{0}", 'a')); EXPECT_EQ("z", format("{0:c}", 'z')); From 1c3c80dc1ff07034f27323ee6561fde0e4fef822 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 2 Apr 2020 07:27:58 -0700 Subject: [PATCH 030/113] Update changelog --- ChangeLog.rst | 63 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index f4a66746..fb240af8 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,11 +1,6 @@ 6.2.0 - TBD ----------- -* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to - ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically - include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be - disabled in the next major release. - * Improved error reporting when trying to format an object of a non-formattable type: @@ -29,6 +24,8 @@ if ``S`` is not formattable. +* Reduced library size by ~10%. + * Always print decimal point if ``#`` is specified (`#1476 `_, `#1498 `_).: @@ -39,9 +36,41 @@ now prints ``42.``. +* Implemented the ``'L'`` specifier for locale-specific numeric formatting to + improve compatibility with ``std::format``. The ``'n'`` specifier is now + deprecated and will be removed in the next major release. + +* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to + ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically + include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be + disabled in the next major release. + * Added precision overflow detection in floating-point formatting. -* Improved UTF-8 handling. +* Implemented detection of invalid use of ``fmt::arg``. + +* Used ``type_identity`` to block unnecessary template argument deduction + Thanks Tim Song. + +* Improved UTF-8 handling + (`#1109 `_): + + .. code:: c++ + + fmt::print("┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Привет, мир!", 20); + + now prints:: + + ┌────────────────────┐ + │ Привет, мир! │ + └────────────────────┘ + + on systems that support Unicode. + +* Fixed handling of output iterators in ``format_to_n`` + (`#1506 `_). * Switched links to HTTPS in README (`#1481 `_). @@ -53,17 +82,35 @@ Thanks `@federico-busato (Federico) `_, `@chronoxor (Ivan Shynkarenka) `_. +* Improved documentation + (`#1523 `_). + Thanks `@JackBoosY (Jack·Boos·Yu) `_. + * Fixed various warnings and compilation issues (`#1433 `_, `#1470 `_, `#1480 `_, `#1485 `_, - `#1492 `_). + `#1492 `_, + `#1505 `_, + `#1512 `_, + `#1515 `_, + `#1516 `_, + `#1518 `_, + `#1519 `_, + `#1520 `_, + `#1521 `_, + `#1521 `_, + `#1530 `_). Thanks `@marti4d (Chris Martin) `_, `@iPherian `_, `@parkertomatoes `_, `@gsjaardema (Greg Sjaardema) `_, - `@chronoxor (Ivan Shynkarenka) `_. + `@chronoxor (Ivan Shynkarenka) `_, + `@DanielaE (Daniela Engert) `_, + `@torsten48 `_, + `@tohammer (Tobias Hammer) `_, + `@lefticus (Jason Turner) `_. 6.1.2 - 2019-12-11 ------------------ From c1ce6e01f76fe0bec0a690b7ad2d3858de1852d1 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 2 Apr 2020 08:18:42 -0700 Subject: [PATCH 031/113] Update changelog --- ChangeLog.rst | 81 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index fb240af8..05a7b36b 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -49,7 +49,7 @@ * Implemented detection of invalid use of ``fmt::arg``. -* Used ``type_identity`` to block unnecessary template argument deduction +* Used ``type_identity`` to block unnecessary template argument deduction. Thanks Tim Song. * Improved UTF-8 handling @@ -69,23 +69,76 @@ on systems that support Unicode. +* Implemented dynamic argument storage + (`#1170 `_, + `#1584 `_): + + .. code:: c++ + + fmt::dynamic_format_arg_store store; + store.push_back("answer"); + store.push_back(42); + fmt::vprint("The {} is {}.\n", store); + + prints:: + + The answer is 42. + + Thanks `vsolontsov-ll (Vladimir Solontsov) + `_. + +* Made ``fmt::join`` accept ``initializer_list`` + (`#1591 `_). + Thanks `Rapotkinnik (Nikolay Rapotkin) `_. + +* Fixed handling of empty tuples + (`#1588 `_). + * Fixed handling of output iterators in ``format_to_n`` (`#1506 `_). +* Fixed formatting of ``std::chrono::duration`` types to wide output + (`#1533 `_). + Thanks `zeffy (pilao) `_. + +* Added const ``begin`` and ``end`` overload to buffers + (`#1553 `_). + Thanks `dominicpoeschko `_. + +* Implemented a minor optimization in the format string parser + (`#1560 `_). + Thanks `IkarusDeveloper `_. + * Switched links to HTTPS in README (`#1481 `_). Thanks `@imba-tjd (谭九鼎) `_. -* Improved detection of the ``fallthrough`` attribute +* Improved attribute detection (`#1469 `_, - `#1475 `_). + `#1475 `_, + `#1576 `_). Thanks `@federico-busato (Federico) `_, - `@chronoxor (Ivan Shynkarenka) `_. + `@chronoxor (Ivan Shynkarenka) `_, + `@refnum `_. * Improved documentation (`#1523 `_). Thanks `@JackBoosY (Jack·Boos·Yu) `_. +* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden`` + (`#1535 `_). + Thanks `@milianw (Milian Wolff) `_. + +* Implemented various build configuration fixes and improvements + (`#1264 `_, + `#1534 `_, + `#1546 `_, + `#1566 `_, + `#1582 `_). + Thanks `@ambitslix (Attila M. Szilagyi) `_, + `@jwillikers (Jordan Williams) `_, + `@stac47 (Laurent Stacul) `_. + * Fixed various warnings and compilation issues (`#1433 `_, `#1470 `_, @@ -101,7 +154,19 @@ `#1520 `_, `#1521 `_, `#1521 `_, - `#1530 `_). + `#1530 `_, + `#1531 `_, + `#1532 `_, + `#1539 `_, + `#1548 `_, + `#1554 `_, + `#1568 `_, + `#1569 `_, + `#1571 `_, + `#1573 `_, + `#1575 `_, + `#1581 `_, + `#1583 `_). Thanks `@marti4d (Chris Martin) `_, `@iPherian `_, `@parkertomatoes `_, @@ -110,7 +175,11 @@ `@DanielaE (Daniela Engert) `_, `@torsten48 `_, `@tohammer (Tobias Hammer) `_, - `@lefticus (Jason Turner) `_. + `@lefticus (Jason Turner) `_, + `@ryusakki (Haise) `_, + `@fghzxm `_, + `@refnum `_, + `@pramodk (Pramod Kumbhar) `_. 6.1.2 - 2019-12-11 ------------------ From 2e32db5b99d63997505c7797f9253c68a8980f5d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 3 Apr 2020 07:42:02 -0700 Subject: [PATCH 032/113] Update changelog --- ChangeLog.rst | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 05a7b36b..7130a866 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -69,7 +69,7 @@ on systems that support Unicode. -* Implemented dynamic argument storage +* Added experimental dynamic argument storage (`#1170 `_, `#1584 `_): @@ -84,12 +84,12 @@ The answer is 42. - Thanks `vsolontsov-ll (Vladimir Solontsov) + Thanks `@vsolontsov-ll (Vladimir Solontsov) `_. * Made ``fmt::join`` accept ``initializer_list`` (`#1591 `_). - Thanks `Rapotkinnik (Nikolay Rapotkin) `_. + Thanks `@Rapotkinnik (Nikolay Rapotkin) `_. * Fixed handling of empty tuples (`#1588 `_). @@ -99,19 +99,25 @@ * Fixed formatting of ``std::chrono::duration`` types to wide output (`#1533 `_). - Thanks `zeffy (pilao) `_. + Thanks `@zeffy (pilao) `_. * Added const ``begin`` and ``end`` overload to buffers (`#1553 `_). - Thanks `dominicpoeschko `_. + Thanks `@dominicpoeschko `_. + +* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``, + ``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely + memory-constrained embedded system + (`#1590 `_). + Thanks `@albaguirre (Alberto Aguirre) `_. + +* Made ``FMT_STRING`` work with ``constexpr`` ``string_view`` + (`#1589 `_). + Thanks `@scramsby (Scott Ramsby) `_. * Implemented a minor optimization in the format string parser (`#1560 `_). - Thanks `IkarusDeveloper `_. - -* Switched links to HTTPS in README - (`#1481 `_). - Thanks `@imba-tjd (谭九鼎) `_. + Thanks `@IkarusDeveloper `_. * Improved attribute detection (`#1469 `_, @@ -122,8 +128,10 @@ `@refnum `_. * Improved documentation - (`#1523 `_). - Thanks `@JackBoosY (Jack·Boos·Yu) `_. + (`#1481 `_, + `#1523 `_). + Thanks `@JackBoosY (Jack·Boos·Yu) `_, + `@imba-tjd (谭九鼎) `_. * Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden`` (`#1535 `_). @@ -134,7 +142,8 @@ `#1534 `_, `#1546 `_, `#1566 `_, - `#1582 `_). + `#1582 `_, + `#1598 `_). Thanks `@ambitslix (Attila M. Szilagyi) `_, `@jwillikers (Jordan Williams) `_, `@stac47 (Laurent Stacul) `_. @@ -166,7 +175,11 @@ `#1573 `_, `#1575 `_, `#1581 `_, - `#1583 `_). + `#1583 `_, + `#1594 `_, + `#1596 `_, + `#1606 `_, + `#1609 `_). Thanks `@marti4d (Chris Martin) `_, `@iPherian `_, `@parkertomatoes `_, @@ -179,7 +192,9 @@ `@ryusakki (Haise) `_, `@fghzxm `_, `@refnum `_, - `@pramodk (Pramod Kumbhar) `_. + `@pramodk (Pramod Kumbhar) `_, + `@Spirrwell `_, + `@scramsby (Scott Ramsby) `_. 6.1.2 - 2019-12-11 ------------------ From 3fc33f62739148a056243918a13aafa776dcf826 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 3 Apr 2020 08:29:02 -0700 Subject: [PATCH 033/113] Update changelog --- ChangeLog.rst | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 7130a866..2e41ccd4 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -139,10 +139,14 @@ * Implemented various build configuration fixes and improvements (`#1264 `_, + `#1460 `_, `#1534 `_, + `#1536 `_, + `#1545 `_, `#1546 `_, `#1566 `_, `#1582 `_, + `#1597 `_, `#1598 `_). Thanks `@ambitslix (Attila M. Szilagyi) `_, `@jwillikers (Jordan Williams) `_, @@ -150,10 +154,13 @@ * Fixed various warnings and compilation issues (`#1433 `_, + `#1461 `_, `#1470 `_, `#1480 `_, `#1485 `_, `#1492 `_, + `#1493 `_, + `#1504 `_, `#1505 `_, `#1512 `_, `#1515 `_, @@ -162,13 +169,16 @@ `#1519 `_, `#1520 `_, `#1521 `_, - `#1521 `_, + `#1522 `_, + `#1524 `_, `#1530 `_, `#1531 `_, `#1532 `_, `#1539 `_, + `#1547 `_, `#1548 `_, `#1554 `_, + `#1567 `_, `#1568 `_, `#1569 `_, `#1571 `_, @@ -176,9 +186,13 @@ `#1575 `_, `#1581 `_, `#1583 `_, + `#1586 `_, + `#1587 `_, `#1594 `_, `#1596 `_, + `#1604 `_, `#1606 `_, + `#1607 `_, `#1609 `_). Thanks `@marti4d (Chris Martin) `_, `@iPherian `_, @@ -190,6 +204,7 @@ `@tohammer (Tobias Hammer) `_, `@lefticus (Jason Turner) `_, `@ryusakki (Haise) `_, + `@adnsv (Alex Denisov) `_, `@fghzxm `_, `@refnum `_, `@pramodk (Pramod Kumbhar) `_, From 51c58a56ba9bb82bb33324f30c32751fccef7eea Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 3 Apr 2020 08:32:45 -0700 Subject: [PATCH 034/113] Bump version --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index ad8194c6..3480fb2f 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -18,7 +18,7 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 60103 +#define FMT_VERSION 60200 #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) From 9eb47d951a48fb50b3f5e2705da614e25bb592f7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 3 Apr 2020 08:49:29 -0700 Subject: [PATCH 035/113] Fix markup --- ChangeLog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 2e41ccd4..60b5a3b0 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -109,7 +109,7 @@ ``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely memory-constrained embedded system (`#1590 `_). - Thanks `@albaguirre (Alberto Aguirre) `_. + Thanks `@albaguirre (Alberto Aguirre) `_. * Made ``FMT_STRING`` work with ``constexpr`` ``string_view`` (`#1589 `_). From a434a8f778ea961ad42d7b2f2153c2ab9637348b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 4 Apr 2020 06:34:23 -0700 Subject: [PATCH 036/113] Update changelog --- ChangeLog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 60b5a3b0..411ffa3a 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -34,7 +34,7 @@ fmt::print("{:#.0f}", 42.0); - now prints ``42.``. + now prints ``42.`` * Implemented the ``'L'`` specifier for locale-specific numeric formatting to improve compatibility with ``std::format``. The ``'n'`` specifier is now From 346500e70b95c0f879d78dc68cd3d54369cc8402 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 4 Apr 2020 11:23:36 -0700 Subject: [PATCH 037/113] Fix gcc version check --- include/fmt/core.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 3480fb2f..6df2875a 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1319,7 +1319,7 @@ using wformat_context = buffer_context; */ template class format_arg_store -#if FMT_GCC_VERSION < 409 +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround a GCC template argument substitution bug. : public basic_format_args #endif @@ -1343,7 +1343,7 @@ class format_arg_store format_arg_store(const Args&... args) : -#if FMT_GCC_VERSION < 409 +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif data_{internal::make_arg(args)...} { @@ -1376,7 +1376,7 @@ inline format_arg_store make_format_args( */ template class dynamic_format_arg_store -#if FMT_GCC_VERSION < 409 +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround a GCC template argument substitution bug. : public basic_format_args #endif From d151562bdd0eb6dd1c4252cf2f57707e57fb03f9 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 4 Apr 2020 12:49:56 -0700 Subject: [PATCH 038/113] Fix punctuation in changelog --- ChangeLog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 411ffa3a..0d5ac23e 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -28,7 +28,7 @@ * Always print decimal point if ``#`` is specified (`#1476 `_, - `#1498 `_).: + `#1498 `_): .. code:: c++ From 9bdd1596cef1b57b9556f8bef32dc4a32322ef3e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 5 Apr 2020 06:46:41 -0700 Subject: [PATCH 039/113] Update version --- ChangeLog.rst | 4 ++-- doc/build.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 0d5ac23e..8e581c47 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,5 +1,5 @@ -6.2.0 - TBD ------------ +6.2.0 - 2020-04-05 +------------------ * Improved error reporting when trying to format an object of a non-formattable type: diff --git a/doc/build.py b/doc/build.py index 7133c7b5..a7bfcf73 100755 --- a/doc/build.py +++ b/doc/build.py @@ -6,7 +6,7 @@ import errno, os, shutil, sys, tempfile from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE from distutils.version import LooseVersion -versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2'] +versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0'] def pip_install(package, commit=None, **kwargs): "Install package using pip." From 27e3c0fe9b5dc99f339637ec2ea7efae5b945ab8 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 6 Apr 2020 07:17:41 -0700 Subject: [PATCH 040/113] Update signature in the docs --- doc/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.rst b/doc/api.rst index 4e370252..654d26fb 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -43,7 +43,7 @@ participate in an overload resolution if the latter is not a string. .. _format: .. doxygenfunction:: format(const S&, Args&&...) -.. doxygenfunction:: vformat(const S&, basic_format_args>) +.. doxygenfunction:: vformat(const S&, basic_format_args>>) .. _print: From 34b3f7b7aa5570cd40a91d96e2203c8b13a3b7cb Mon Sep 17 00:00:00 2001 From: Greg Sjaardema Date: Mon, 6 Apr 2020 10:38:07 -0600 Subject: [PATCH 041/113] Avoid windows issue with min() max() macros Including the ``windows.h`` file without defining ``NOMINMAX`` will define the `min()` and `max()` macros which will result in issues compiling any C++ code that uses any variant of `max`, for example `std::numeric_limits::max()` and many others. Although max() isn't used in Fmt anywhere, it is often used in codes that include a format include file so simply upgrading to the current version of lib::fmt will break the windows build which worked prior to the update... --- include/fmt/format-inl.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index f632714d..788eb8da 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -22,8 +22,14 @@ #endif #ifdef _WIN32 +# if defined(NOMINMAX) +# include +# else +# define NOMINMAX +# include +# undef NOMINMAX +# endif # include -# include #endif #ifdef _MSC_VER From 4999796c15b62b9992feec780a3fcf11cfc33afd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 6 Apr 2020 07:32:15 -0700 Subject: [PATCH 042/113] Fix the docs --- support/manage.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/support/manage.py b/support/manage.py index 517759b3..58fbcddb 100755 --- a/support/manage.py +++ b/support/manage.py @@ -144,9 +144,15 @@ def update_site(env): b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data) b.data = b.data.replace('std::FILE*', 'std::FILE *') b.data = b.data.replace('unsigned int', 'unsigned') - b.data = b.data.replace('operator""_', 'operator"" _') + #b.data = b.data.replace('operator""_', 'operator"" _') b.data = b.data.replace(', size_t', ', std::size_t') b.data = b.data.replace('aa long', 'a long') + if version == '6.2.0': + b.data = b.data.replace( + 'vformat(const S&, basic_format_args<' + + 'buffer_context>)', + 'vformat(const S&, basic_format_args<' + + 'buffer_context>>)') # Fix a broken link in index.rst. index = os.path.join(target_doc_dir, 'index.rst') with rewrite(index) as b: From 7d01859ef16e6b65bc023ad8bebfedecb088bf81 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 8 Apr 2020 12:32:34 -0700 Subject: [PATCH 043/113] Fix handling of unsigned char strings in printf --- include/fmt/core.h | 8 ++++++++ test/printf-test.cc | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/include/fmt/core.h b/include/fmt/core.h index 6df2875a..dc10722b 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -972,6 +972,14 @@ template struct arg_mapper { static_assert(std::is_same::value, "invalid string type"); return reinterpret_cast(val); } + FMT_CONSTEXPR const char* map(signed char* val) { + const auto* const_val = val; + return map(const_val); + } + FMT_CONSTEXPR const char* map(unsigned char* val) { + const auto* const_val = val; + return map(const_val); + } FMT_CONSTEXPR const void* map(void* val) { return val; } FMT_CONSTEXPR const void* map(const void* val) { return val; } diff --git a/test/printf-test.cc b/test/printf-test.cc index 5aaa27b1..545e02aa 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -447,6 +447,12 @@ TEST(PrintfTest, String) { EXPECT_PRINTF(L" (null)", L"%10s", null_wstr); } +TEST(PrintfTest, UCharString) { + unsigned char str[] = "test"; + unsigned char* pstr = str; + EXPECT_EQ("test", fmt::sprintf("%s", pstr)); +} + TEST(PrintfTest, Pointer) { int n; void* p = &n; From 3860edc5d9e7e9a7431d30b7f98cbb206688d0a5 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 8 Apr 2020 14:48:14 -0700 Subject: [PATCH 044/113] Bump version --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index dc10722b..af5842dc 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -18,7 +18,7 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 60200 +#define FMT_VERSION 60201 #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) From 141a00d642a6ce2108ba8ce0425301ea8b0fe70b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Apr 2020 11:54:55 -0700 Subject: [PATCH 045/113] Define FMT_EXTERN_TEMPLATE_API on export --- include/fmt/core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/fmt/core.h b/include/fmt/core.h index af5842dc..b79dbe02 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -198,6 +198,7 @@ # define FMT_CLASS_API FMT_NO_W4275 # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) +# define FMT_EXTERN_TEMPLATE_API FMT_API # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # define FMT_EXTERN_TEMPLATE_API FMT_API From 36ea32640fecc775d16fd337c447762bede3eac1 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 10 Apr 2020 06:36:50 -0700 Subject: [PATCH 046/113] Suppress a bogus MSVC warning --- include/fmt/core.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index b79dbe02..c88439be 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -71,8 +71,10 @@ #ifdef _MSC_VER # define FMT_MSC_VER _MSC_VER +# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n)) #else # define FMT_MSC_VER 0 +# define FMT_SUPPRESS_MSC_WARNING(n) #endif // Check if relaxed C++14 constexpr is supported. @@ -325,7 +327,7 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { return static_cast::type>(value); } -constexpr unsigned char micro[] = "\u00B5"; +FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5"; template constexpr bool is_unicode() { return FMT_UNICODE || sizeof(Char) != 1 || From bbb6b357c7f5f92b883dfd5d97065e76bcce8132 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 10 Apr 2020 07:16:20 -0700 Subject: [PATCH 047/113] Add floating-point L specifier (#1624) --- include/fmt/format.h | 1 + test/format-test.cc | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 4e96539f..20d69351 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1287,6 +1287,7 @@ FMT_CONSTEXPR float_specs parse_float_type_spec( result.format = float_format::hex; break; case 'n': + case 'L': result.locale = true; break; default: diff --git a/test/format-test.cc b/test/format-test.cc index e9304cee..6a873185 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1413,7 +1413,7 @@ TEST(FormatterTest, FormatFloat) { } TEST(FormatterTest, FormatDouble) { - check_unknown_types(1.2, "eEfFgGaAn%", "double"); + check_unknown_types(1.2, "eEfFgGaAnL%", "double"); EXPECT_EQ("0.0", format("{:}", 0.0)); EXPECT_EQ("0.000000", format("{:f}", 0.0)); EXPECT_EQ("0", format("{:g}", 0.0)); @@ -1422,6 +1422,7 @@ TEST(FormatterTest, FormatDouble) { EXPECT_EQ("392.65", format("{:G}", 392.65)); EXPECT_EQ("392.650000", format("{:f}", 392.65)); EXPECT_EQ("392.650000", format("{:F}", 392.65)); + EXPECT_EQ("42", format("{:L}", 42.0)); char buffer[BUFFER_SIZE]; safe_sprintf(buffer, "%e", 392.65); EXPECT_EQ(buffer, format("{0:e}", 392.65)); From 8cd8ef03eb27c1b7285b9a9904eb7b514ed5cf22 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 11 Apr 2020 06:17:31 -0700 Subject: [PATCH 048/113] Simplify warning suppression --- include/fmt/core.h | 10 ++-------- include/fmt/format.h | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index c88439be..8b4c6319 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -192,12 +192,7 @@ #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# if FMT_MSC_VER -# define FMT_NO_W4275 __pragma(warning(suppress : 4275)) -# else -# define FMT_NO_W4275 -# endif -# define FMT_CLASS_API FMT_NO_W4275 +# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275) # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) # define FMT_EXTERN_TEMPLATE_API FMT_API @@ -205,8 +200,7 @@ # define FMT_API __declspec(dllimport) # define FMT_EXTERN_TEMPLATE_API FMT_API # endif -#endif -#ifndef FMT_CLASS_API +#else # define FMT_CLASS_API #endif #ifndef FMT_API diff --git a/include/fmt/format.h b/include/fmt/format.h index 20d69351..9060dc94 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -189,7 +189,7 @@ inline uint32_t clz(uint32_t x) { // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. -# pragma warning(suppress : 6102) + FMT_SUPPRESS_MSC_WARNING(6102) return 31 - r; } # define FMT_BUILTIN_CLZ(n) internal::clz(n) @@ -214,7 +214,7 @@ inline uint32_t clzll(uint64_t x) { // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. -# pragma warning(suppress : 6102) + FMT_SUPPRESS_MSC_WARNING(6102) return 63 - r; } # define FMT_BUILTIN_CLZLL(n) internal::clzll(n) From e30d8391e401f74a48d822c64d96f965ff16c722 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 11 Apr 2020 06:26:42 -0700 Subject: [PATCH 049/113] Suppress an MSVC warning (#1622) --- include/fmt/format.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 9060dc94..3b1ebe59 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2139,12 +2139,18 @@ template class numeric_specs_checker { // A format specifier handler that checks if specifiers are consistent with the // argument type. template class specs_checker : public Handler { + private: + numeric_specs_checker checker_; + + // Suppress an MSVC warning about using this in initializer list. + FMT_CONSTEXPR Handler& error_handler() { return *this; } + public: FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) - : Handler(handler), checker_(*this, arg_type) {} + : Handler(handler), checker_(error_handler(), arg_type) {} FMT_CONSTEXPR specs_checker(const specs_checker& other) - : Handler(other), checker_(*this, other.arg_type_) {} + : Handler(other), checker_(error_handler(), other.arg_type_) {} FMT_CONSTEXPR void on_align(align_t align) { if (align == align::numeric) checker_.require_numeric_argument(); @@ -2177,9 +2183,6 @@ template class specs_checker : public Handler { } FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } - - private: - numeric_specs_checker checker_; }; template