Support named args in dynamic_format_arg_store (#1655).
<!-- Please read the contribution guidelines before submitting a pull request. -->
<!-- By submitting this pull request, you agree that your contributions are licensed under the {fmt} license,
     and agree to future changes to the licensing. -->
<!-- If you're a first-time contributor, please acknowledge it by leaving the statement below. -->
* Fix #1655: Support named args in dynamic_format_arg_store
I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing.

{"label": "fmtlib:master", "ref": "master", "sha": "8d9d528bf52c60864802844e8acf16db09dae19a"}
Hey, I noticed in the latest release that there is now a `dynamic_format_arg_store`, but it doesn't support named arguments. Is there a plan to add such capability? I'm thinking of something along the lines of:
```
fmt::dynamic_format_named_argument_store<fmt::format_context> store;
store.emplace_back("name", 123);
store.emplace_back("another_name", 456);
fmt::vprint("{name} + {another_name}", store);
```
Would this be hard to implement? I can take a crack at it given some starting guidance.
Dynamic construction of *named* argument lists?
diff --git a/include/fmt/core.h b/include/fmt/core.h
index 2e5a51fec1da..edab4ac8e02c 100644
--- a/include/fmt/core.h
+++ b/include/fmt/core.h
@@ -629,6 +629,7 @@ using wparse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context<wchar_t>;
 
 template <typename Context> class basic_format_arg;
 template <typename Context> class basic_format_args;
+template <typename Context> class dynamic_format_arg_store;
 
 // A formatter for objects of type T.
 template <typename T, typename Char = char, typename Enable = void>
@@ -1131,6 +1132,7 @@ template <typename Context> class basic_format_arg {
 
   friend class basic_format_args<Context>;
   friend class internal::arg_map<Context>;
+  friend class dynamic_format_arg_store<Context>;
 
   using char_type = typename Context::char_type;
 
@@ -1269,10 +1271,14 @@ inline basic_format_arg<Context> make_arg(const T& value) {
 }
 
 template <typename T> struct is_reference_wrapper : std::false_type {};
-
 template <typename T>
 struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
 
+template <typename T> const T& unwrap(const T& v) { return v; }
+template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
+  return static_cast<const T&>(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
@@ -1419,6 +1425,50 @@ inline format_arg_store<Context, Args...> make_format_args(
   return {args...};
 }
 
+namespace internal {
+template <typename Char> struct named_arg_base {
+  const Char* name;
+
+  // Serialized value<context>.
+  mutable char data[sizeof(basic_format_arg<buffer_context<Char>>)];
+
+  named_arg_base(const Char* nm) : name(nm) {}
+
+  template <typename Context> basic_format_arg<Context> deserialize() const {
+    basic_format_arg<Context> arg;
+    std::memcpy(&arg, data, sizeof(basic_format_arg<Context>));
+    return arg;
+  }
+};
+
+struct view {};
+
+template <typename T, typename Char>
+struct named_arg : view, named_arg_base<Char> {
+  const T& value;
+
+  named_arg(const Char* name, const T& val)
+      : named_arg_base<Char>(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 <typename Char, typename T>
+inline internal::named_arg<T, Char> arg(const Char* name, const T& arg) {
+  static_assert(!internal::is_named_arg<T>(), "nested named arguments");
+  return {name, arg};
+}
+
 /**
   \rst
   A dynamic version of `fmt::format_arg_store<>`.
@@ -1449,8 +1499,7 @@ class dynamic_format_arg_store
                 std::is_same<T, internal::std_string_view<char_type>>::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))
     };
   };
 
@@ -1460,6 +1509,7 @@ class dynamic_format_arg_store
 
   // Storage of basic_format_arg must be contiguous.
   std::vector<basic_format_arg<Context>> data_;
+  std::vector<internal::named_arg_info<char_type>> named_info_;
 
   // Storage of arguments not fitting into basic_format_arg must grow
   // without relocation because items in data_ refer to it.
@@ -1468,13 +1518,38 @@ class dynamic_format_arg_store
   friend class basic_format_args<Context>;
 
   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<unsigned long long>(
+                                      internal::has_named_args_bit));
+  }
+
+  const basic_format_arg<Context>* data() const {
+    return named_info_.empty() ? data_.data() : data_.data() + 1;
   }
 
   template <typename T> void emplace_arg(const T& arg) {
     data_.emplace_back(internal::make_arg<Context>(arg));
   }
 
+  template <typename T>
+  void emplace_arg(const internal::named_arg<T, char_type>& arg) {
+    if (named_info_.empty()) {
+      constexpr const internal::named_arg_info<char_type>* zero_ptr{nullptr};
+      data_.insert(data_.begin(), {zero_ptr, 0});
+    }
+    data_.emplace_back(
+        internal::make_arg<Context>(internal::unwrap(arg.value)));
+    auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
+      data->pop_back();
+    };
+    std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
+        guard{&data_, pop_one};
+    named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
+    data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
+    guard.release();
+  }
+
  public:
   /**
     \rst
@@ -1500,19 +1575,54 @@ class dynamic_format_arg_store
     if (internal::const_check(need_copy<T>::value))
       emplace_arg(dynamic_args_.push<stored_type<T>>(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<fmt::format_context> 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 <typename T> void push_back(std::reference_wrapper<T> arg) {
     static_assert(
-        need_copy<T>::value,
+        internal::is_named_arg<typename std::remove_cv<T>::type>::value ||
+            need_copy<T>::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 <typename T>
+  void push_back(const internal::named_arg<T, char_type>& arg) {
+    const char_type* arg_name =
+        dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
+    if (internal::const_check(need_copy<T>::value)) {
+      emplace_arg(
+          fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
+    } else {
+      emplace_arg(fmt::arg(arg_name, arg.value));
+    }
+  }
 };
 
 /**
@@ -1597,7 +1707,7 @@ template <typename Context> class basic_format_args {
    \endrst
    */
   FMT_INLINE basic_format_args(const dynamic_format_arg_store<Context>& store)
-      : basic_format_args(store.get_types(), store.data_.data()) {}
+      : basic_format_args(store.get_types(), store.data()) {}
 
   /**
    \rst
@@ -1659,31 +1769,6 @@ template <typename Container>
 struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
     : is_contiguous<Container> {};
 
-template <typename Char> struct named_arg_base {
-  const Char* name;
-
-  // Serialized value<context>.
-  mutable char data[sizeof(basic_format_arg<buffer_context<Char>>)];
-
-  named_arg_base(const Char* nm) : name(nm) {}
-
-  template <typename Context> basic_format_arg<Context> deserialize() const {
-    basic_format_arg<Context> arg;
-    std::memcpy(&arg, data, sizeof(basic_format_arg<Context>));
-    return arg;
-  }
-};
-
-struct view {};
-
-template <typename T, typename Char>
-struct named_arg : view, named_arg_base<Char> {
-  const T& value;
-
-  named_arg(const Char* name, const T& val)
-      : named_arg_base<Char>(name), value(val) {}
-};
-
 // Reports a compile-time error if S is not a valid format string.
 template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
 FMT_INLINE void check_format_string(const S&) {
@@ -1727,22 +1812,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 <typename Char, typename T>
-inline internal::named_arg<T, Char> arg(const Char* name, const T& arg) {
-  static_assert(!internal::is_named_arg<T>(), "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<Container> with
 // vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.

diff --git a/test/core-test.cc b/test/core-test.cc
index adda8943b49d..8c2f7bd9e878 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<fmt::format_context> 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<fmt::format_context> 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<fmt::format_context> 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<fmt::format_context> 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"; }

{"locale-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "compile-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grisu-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "custom-formatter-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "os-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"locale-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "compile-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "grisu-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "custom-formatter-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "os-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 17, "failed_count": 0, "skipped_count": 0, "passed_tests": ["format-test", "gtest-extra-test", "locale-test", "compile-test", "ostream-test", "ranges-test", "assert-test", "posix-mock-test", "core-test", "printf-test", "color-test", "scan-test", "format-impl-test", "grisu-test", "custom-formatter-test", "os-test", "chrono-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 0, "failed_count": 0, "skipped_count": 0, "passed_tests": [], "failed_tests": [], "skipped_tests": []}
{"passed_count": 17, "failed_count": 0, "skipped_count": 0, "passed_tests": ["format-test", "gtest-extra-test", "locale-test", "compile-test", "ostream-test", "ranges-test", "assert-test", "posix-mock-test", "core-test", "printf-test", "color-test", "scan-test", "format-impl-test", "grisu-test", "custom-formatter-test", "os-test", "chrono-test"], "failed_tests": [], "skipped_tests": []}