From 68201831a527ed958a9fa3aeb93a8b9879934d0d Mon Sep 17 00:00:00 2001 From: Vladimir Solontsov <56200572+vsolontsov-ll@users.noreply.github.com> Date: Sat, 9 May 2020 16:25:03 +0300 Subject: [PATCH] Support named args in dynamic_format_arg_store (#1655). (#1663) Dynamic arguments storage. Implementation of enhancement from issue #1170. --- include/fmt/core.h | 167 ++++++++++++++++++++++++++++++++------------- test/core-test.cc | 60 ++++++++++++++++ 2 files changed, 178 insertions(+), 49 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index b6e36d48..d043e9c8 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -610,6 +610,7 @@ using wformat_parse_context = basic_format_parse_context; template class basic_format_arg; template class basic_format_args; +template class dynamic_format_arg_store; // A formatter for objects of type T. template @@ -1111,6 +1112,7 @@ template class basic_format_arg { friend class basic_format_args; friend class internal::arg_map; + friend class dynamic_format_arg_store; using char_type = typename Context::char_type; @@ -1252,10 +1254,14 @@ inline basic_format_arg make_arg(const T& value) { } template struct is_reference_wrapper : std::false_type {}; - template struct is_reference_wrapper> : std::true_type {}; +template const T& unwrap(const T& v) { return v; } +template const T& unwrap(const std::reference_wrapper& v) { + return static_cast(v); +} + 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 @@ -1404,6 +1410,50 @@ inline format_arg_store make_format_args( return {args...}; } +namespace internal { +template struct named_arg_base { + const Char* name; + + // Serialized value. + mutable char data[sizeof(basic_format_arg>)]; + + named_arg_base(const Char* nm) : name(nm) {} + + template basic_format_arg deserialize() const { + basic_format_arg arg; + std::memcpy(&arg, data, sizeof(basic_format_arg)); + return arg; + } +}; + +struct view {}; + +template +struct named_arg : view, named_arg_base { + const T& value; + + named_arg(const Char* name, const T& val) + : named_arg_base(name), value(val) {} +}; + +} // namespace internal + +/** + \rst + Returns a named argument to be used in a formatting function. It should only + be used in a call to a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template +inline internal::named_arg arg(const Char* name, const T& arg) { + static_assert(!internal::is_named_arg(), "nested named arguments"); + return {name, arg}; +} + /** \rst A dynamic version of `fmt::format_arg_store<>`. @@ -1434,8 +1484,7 @@ class dynamic_format_arg_store 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::custom_type)) }; }; @@ -1445,6 +1494,7 @@ class dynamic_format_arg_store // Storage of basic_format_arg must be contiguous. std::vector> data_; + std::vector> named_info_; // Storage of arguments not fitting into basic_format_arg must grow // without relocation because items in data_ refer to it. @@ -1453,13 +1503,38 @@ class dynamic_format_arg_store friend class basic_format_args; unsigned long long get_types() const { - return internal::is_unpacked_bit | data_.size(); + return internal::is_unpacked_bit | data_.size() | + (named_info_.empty() ? 0ULL + : static_cast( + internal::has_named_args_bit)); + } + + const basic_format_arg* data() const { + return named_info_.empty() ? data_.data() : data_.data() + 1; } template void emplace_arg(const T& arg) { data_.emplace_back(internal::make_arg(arg)); } + template + void emplace_arg(const internal::named_arg& arg) { + if (named_info_.empty()) { + constexpr const internal::named_arg_info* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back( + internal::make_arg(internal::unwrap(arg.value))); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); + } + public: /** \rst @@ -1485,19 +1560,54 @@ class dynamic_format_arg_store if (internal::const_check(need_copy::value)) emplace_arg(dynamic_args_.push>(arg)); else - emplace_arg(arg); + emplace_arg(internal::unwrap(arg)); } /** + \rst Adds a reference to the argument into the dynamic store for later passing to - a formating function. + a formating function. Supports named arguments wrapped in + std::reference_wrapper (via std::ref()/std::cref()). + + **Example**:: + fmt::dynamic_format_arg_store store; + char str[] = "1234567890"; + store.push_back(std::cref(str)); + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + // Changing str affects the output but only for string and custom types. + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {a1_}"); + assert(result == "X234567890 and 42"); + \endrst */ template void push_back(std::reference_wrapper arg) { static_assert( - need_copy::value, + internal::is_named_arg::type>::value || + need_copy::value, "objects of built-in types and string views are always copied"); emplace_arg(arg.get()); } + + /** + Adds named argument into the dynamic store for later passing to a formating + function. std::reference_wrapper is supported to avoid copying of the + argument. + */ + template + void push_back(const internal::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (internal::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } }; /** @@ -1582,7 +1692,7 @@ template class basic_format_args { \endrst */ FMT_INLINE basic_format_args(const dynamic_format_arg_store& store) - : basic_format_args(store.get_types(), store.data_.data()) {} + : basic_format_args(store.get_types(), store.data()) {} /** \rst @@ -1644,31 +1754,6 @@ template struct is_contiguous_back_insert_iterator> : is_contiguous {}; -template struct named_arg_base { - const Char* name; - - // Serialized value. - mutable char data[sizeof(basic_format_arg>)]; - - named_arg_base(const Char* nm) : name(nm) {} - - template basic_format_arg deserialize() const { - basic_format_arg arg; - std::memcpy(&arg, data, sizeof(basic_format_arg)); - return arg; - } -}; - -struct view {}; - -template -struct named_arg : view, named_arg_base { - const T& value; - - named_arg(const Char* name, const T& val) - : named_arg_base(name), value(val) {} -}; - // Reports a compile-time error if S is not a valid format string. template ::value)> FMT_INLINE void check_format_string(const S&) { @@ -1712,22 +1797,6 @@ inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif } // namespace internal -/** - \rst - Returns a named argument to be used in a formatting function. It should only - be used in a call to a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template -inline internal::named_arg arg(const Char* name, const T& arg) { - static_assert(!internal::is_named_arg(), "nested named arguments"); - return {name, arg}; -} - /** Formats a string and writes the output to ``out``. */ // GCC 8 and earlier cannot handle std::back_insert_iterator with // vformat_to(...) overload, so SFINAE on iterator type instead. diff --git a/test/core-test.cc b/test/core-test.cc index e6b9db8a..bb48db0b 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -456,6 +456,66 @@ TEST(FormatDynArgsTest, CustomFormat) { EXPECT_EQ("cust=0 and cust=1 and cust=3", result); } +TEST(FormatDynArgsTest, NamedInt) { + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("a1", 42)); + std::string result = fmt::vformat("{a1}", store); + EXPECT_EQ("42", result); +} + +TEST(FormatDynArgsTest, NamedStrings) { + fmt::dynamic_format_arg_store store; + char str[]{"1234567890"}; + store.push_back(fmt::arg("a1", str)); + store.push_back(fmt::arg("a2", std::cref(str))); + str[0] = 'X'; + + std::string result = fmt::vformat( + "{a1} and {a2}", + store); + + EXPECT_EQ("1234567890 and X234567890", 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("abc"); + store.push_back(1.5f); + store.push_back(std::cref(a1)); + + std::string result = fmt::vformat( + "{a1_} and {} and {} and {}", + store); + + EXPECT_EQ("42 and abc and 1.5 and 42", result); +} + +TEST(FormatDynArgsTest, NamedCustomFormat) { + fmt::dynamic_format_arg_store store; + custom_type c{}; + store.push_back(fmt::arg("c1", c)); + ++c.i; + store.push_back(fmt::arg("c2", c)); + ++c.i; + store.push_back(fmt::arg("c_ref", std::cref(c))); + ++c.i; + + std::string result = fmt::vformat("{c1} and {c2} and {c_ref}", store); + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + struct copy_throwable { copy_throwable() {} copy_throwable(const copy_throwable&) { throw "deal with it"; }