Skip to content

Commit 6820183

Browse files
Support named args in dynamic_format_arg_store (#1655). (#1663)
Dynamic arguments storage. Implementation of enhancement from issue #1170.
1 parent 7f723fb commit 6820183

File tree

2 files changed

+178
-49
lines changed

2 files changed

+178
-49
lines changed

include/fmt/core.h

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ using wformat_parse_context = basic_format_parse_context<wchar_t>;
610610

611611
template <typename Context> class basic_format_arg;
612612
template <typename Context> class basic_format_args;
613+
template <typename Context> class dynamic_format_arg_store;
613614

614615
// A formatter for objects of type T.
615616
template <typename T, typename Char = char, typename Enable = void>
@@ -1111,6 +1112,7 @@ template <typename Context> class basic_format_arg {
11111112

11121113
friend class basic_format_args<Context>;
11131114
friend class internal::arg_map<Context>;
1115+
friend class dynamic_format_arg_store<Context>;
11141116

11151117
using char_type = typename Context::char_type;
11161118

@@ -1252,10 +1254,14 @@ inline basic_format_arg<Context> make_arg(const T& value) {
12521254
}
12531255

12541256
template <typename T> struct is_reference_wrapper : std::false_type {};
1255-
12561257
template <typename T>
12571258
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
12581259

1260+
template <typename T> const T& unwrap(const T& v) { return v; }
1261+
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
1262+
return static_cast<const T&>(v);
1263+
}
1264+
12591265
class dynamic_arg_list {
12601266
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
12611267
// templates it doesn't complain about inability to deduce single translation
@@ -1404,6 +1410,50 @@ inline format_arg_store<Context, Args...> make_format_args(
14041410
return {args...};
14051411
}
14061412

1413+
namespace internal {
1414+
template <typename Char> struct named_arg_base {
1415+
const Char* name;
1416+
1417+
// Serialized value<context>.
1418+
mutable char data[sizeof(basic_format_arg<buffer_context<Char>>)];
1419+
1420+
named_arg_base(const Char* nm) : name(nm) {}
1421+
1422+
template <typename Context> basic_format_arg<Context> deserialize() const {
1423+
basic_format_arg<Context> arg;
1424+
std::memcpy(&arg, data, sizeof(basic_format_arg<Context>));
1425+
return arg;
1426+
}
1427+
};
1428+
1429+
struct view {};
1430+
1431+
template <typename T, typename Char>
1432+
struct named_arg : view, named_arg_base<Char> {
1433+
const T& value;
1434+
1435+
named_arg(const Char* name, const T& val)
1436+
: named_arg_base<Char>(name), value(val) {}
1437+
};
1438+
1439+
} // namespace internal
1440+
1441+
/**
1442+
\rst
1443+
Returns a named argument to be used in a formatting function. It should only
1444+
be used in a call to a formatting function.
1445+
1446+
**Example**::
1447+
1448+
fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
1449+
\endrst
1450+
*/
1451+
template <typename Char, typename T>
1452+
inline internal::named_arg<T, Char> arg(const Char* name, const T& arg) {
1453+
static_assert(!internal::is_named_arg<T>(), "nested named arguments");
1454+
return {name, arg};
1455+
}
1456+
14071457
/**
14081458
\rst
14091459
A dynamic version of `fmt::format_arg_store<>`.
@@ -1434,8 +1484,7 @@ class dynamic_format_arg_store
14341484
std::is_same<T, internal::std_string_view<char_type>>::value ||
14351485
(mapped_type != internal::type::cstring_type &&
14361486
mapped_type != internal::type::string_type &&
1437-
mapped_type != internal::type::custom_type &&
1438-
mapped_type != internal::type::named_arg_type))
1487+
mapped_type != internal::type::custom_type))
14391488
};
14401489
};
14411490

@@ -1445,6 +1494,7 @@ class dynamic_format_arg_store
14451494

14461495
// Storage of basic_format_arg must be contiguous.
14471496
std::vector<basic_format_arg<Context>> data_;
1497+
std::vector<internal::named_arg_info<char_type>> named_info_;
14481498

14491499
// Storage of arguments not fitting into basic_format_arg must grow
14501500
// without relocation because items in data_ refer to it.
@@ -1453,13 +1503,38 @@ class dynamic_format_arg_store
14531503
friend class basic_format_args<Context>;
14541504

14551505
unsigned long long get_types() const {
1456-
return internal::is_unpacked_bit | data_.size();
1506+
return internal::is_unpacked_bit | data_.size() |
1507+
(named_info_.empty() ? 0ULL
1508+
: static_cast<unsigned long long>(
1509+
internal::has_named_args_bit));
1510+
}
1511+
1512+
const basic_format_arg<Context>* data() const {
1513+
return named_info_.empty() ? data_.data() : data_.data() + 1;
14571514
}
14581515

14591516
template <typename T> void emplace_arg(const T& arg) {
14601517
data_.emplace_back(internal::make_arg<Context>(arg));
14611518
}
14621519

1520+
template <typename T>
1521+
void emplace_arg(const internal::named_arg<T, char_type>& arg) {
1522+
if (named_info_.empty()) {
1523+
constexpr const internal::named_arg_info<char_type>* zero_ptr{nullptr};
1524+
data_.insert(data_.begin(), {zero_ptr, 0});
1525+
}
1526+
data_.emplace_back(
1527+
internal::make_arg<Context>(internal::unwrap(arg.value)));
1528+
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
1529+
data->pop_back();
1530+
};
1531+
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
1532+
guard{&data_, pop_one};
1533+
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
1534+
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
1535+
guard.release();
1536+
}
1537+
14631538
public:
14641539
/**
14651540
\rst
@@ -1485,19 +1560,54 @@ class dynamic_format_arg_store
14851560
if (internal::const_check(need_copy<T>::value))
14861561
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
14871562
else
1488-
emplace_arg(arg);
1563+
emplace_arg(internal::unwrap(arg));
14891564
}
14901565

14911566
/**
1567+
\rst
14921568
Adds a reference to the argument into the dynamic store for later passing to
1493-
a formating function.
1569+
a formating function. Supports named arguments wrapped in
1570+
std::reference_wrapper (via std::ref()/std::cref()).
1571+
1572+
**Example**::
1573+
fmt::dynamic_format_arg_store<fmt::format_context> store;
1574+
char str[] = "1234567890";
1575+
store.push_back(std::cref(str));
1576+
int a1_val{42};
1577+
auto a1 = fmt::arg("a1_", a1_val);
1578+
store.push_back(std::cref(a1));
1579+
1580+
// Changing str affects the output but only for string and custom types.
1581+
str[0] = 'X';
1582+
1583+
std::string result = fmt::vformat("{} and {a1_}");
1584+
assert(result == "X234567890 and 42");
1585+
\endrst
14941586
*/
14951587
template <typename T> void push_back(std::reference_wrapper<T> arg) {
14961588
static_assert(
1497-
need_copy<T>::value,
1589+
internal::is_named_arg<typename std::remove_cv<T>::type>::value ||
1590+
need_copy<T>::value,
14981591
"objects of built-in types and string views are always copied");
14991592
emplace_arg(arg.get());
15001593
}
1594+
1595+
/**
1596+
Adds named argument into the dynamic store for later passing to a formating
1597+
function. std::reference_wrapper is supported to avoid copying of the
1598+
argument.
1599+
*/
1600+
template <typename T>
1601+
void push_back(const internal::named_arg<T, char_type>& arg) {
1602+
const char_type* arg_name =
1603+
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
1604+
if (internal::const_check(need_copy<T>::value)) {
1605+
emplace_arg(
1606+
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
1607+
} else {
1608+
emplace_arg(fmt::arg(arg_name, arg.value));
1609+
}
1610+
}
15011611
};
15021612

15031613
/**
@@ -1582,7 +1692,7 @@ template <typename Context> class basic_format_args {
15821692
\endrst
15831693
*/
15841694
FMT_INLINE basic_format_args(const dynamic_format_arg_store<Context>& store)
1585-
: basic_format_args(store.get_types(), store.data_.data()) {}
1695+
: basic_format_args(store.get_types(), store.data()) {}
15861696

15871697
/**
15881698
\rst
@@ -1644,31 +1754,6 @@ template <typename Container>
16441754
struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
16451755
: is_contiguous<Container> {};
16461756

1647-
template <typename Char> struct named_arg_base {
1648-
const Char* name;
1649-
1650-
// Serialized value<context>.
1651-
mutable char data[sizeof(basic_format_arg<buffer_context<Char>>)];
1652-
1653-
named_arg_base(const Char* nm) : name(nm) {}
1654-
1655-
template <typename Context> basic_format_arg<Context> deserialize() const {
1656-
basic_format_arg<Context> arg;
1657-
std::memcpy(&arg, data, sizeof(basic_format_arg<Context>));
1658-
return arg;
1659-
}
1660-
};
1661-
1662-
struct view {};
1663-
1664-
template <typename T, typename Char>
1665-
struct named_arg : view, named_arg_base<Char> {
1666-
const T& value;
1667-
1668-
named_arg(const Char* name, const T& val)
1669-
: named_arg_base<Char>(name), value(val) {}
1670-
};
1671-
16721757
// Reports a compile-time error if S is not a valid format string.
16731758
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
16741759
FMT_INLINE void check_format_string(const S&) {
@@ -1712,22 +1797,6 @@ inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
17121797
#endif
17131798
} // namespace internal
17141799

1715-
/**
1716-
\rst
1717-
Returns a named argument to be used in a formatting function. It should only
1718-
be used in a call to a formatting function.
1719-
1720-
**Example**::
1721-
1722-
fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
1723-
\endrst
1724-
*/
1725-
template <typename Char, typename T>
1726-
inline internal::named_arg<T, Char> arg(const Char* name, const T& arg) {
1727-
static_assert(!internal::is_named_arg<T>(), "nested named arguments");
1728-
return {name, arg};
1729-
}
1730-
17311800
/** Formats a string and writes the output to ``out``. */
17321801
// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
17331802
// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.

test/core-test.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,66 @@ TEST(FormatDynArgsTest, CustomFormat) {
456456
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
457457
}
458458

459+
TEST(FormatDynArgsTest, NamedInt) {
460+
fmt::dynamic_format_arg_store<fmt::format_context> store;
461+
store.push_back(fmt::arg("a1", 42));
462+
std::string result = fmt::vformat("{a1}", store);
463+
EXPECT_EQ("42", result);
464+
}
465+
466+
TEST(FormatDynArgsTest, NamedStrings) {
467+
fmt::dynamic_format_arg_store<fmt::format_context> store;
468+
char str[]{"1234567890"};
469+
store.push_back(fmt::arg("a1", str));
470+
store.push_back(fmt::arg("a2", std::cref(str)));
471+
str[0] = 'X';
472+
473+
std::string result = fmt::vformat(
474+
"{a1} and {a2}",
475+
store);
476+
477+
EXPECT_EQ("1234567890 and X234567890", result);
478+
}
479+
480+
TEST(FormatDynArgsTest, NamedArgByRef) {
481+
fmt::dynamic_format_arg_store<fmt::format_context> store;
482+
483+
// Note: fmt::arg() constructs an object which holds a reference
484+
// to its value. It's not an aggregate, so it doesn't extend the
485+
// reference lifetime. As a result, it's a very bad idea passing temporary
486+
// as a named argument value. Only GCC with optimization level >0
487+
// complains about this.
488+
//
489+
// A real life usecase is when you have both name and value alive
490+
// guarantee their lifetime and thus don't want them to be copied into
491+
// storages.
492+
int a1_val{42};
493+
auto a1 = fmt::arg("a1_", a1_val);
494+
store.push_back("abc");
495+
store.push_back(1.5f);
496+
store.push_back(std::cref(a1));
497+
498+
std::string result = fmt::vformat(
499+
"{a1_} and {} and {} and {}",
500+
store);
501+
502+
EXPECT_EQ("42 and abc and 1.5 and 42", result);
503+
}
504+
505+
TEST(FormatDynArgsTest, NamedCustomFormat) {
506+
fmt::dynamic_format_arg_store<fmt::format_context> store;
507+
custom_type c{};
508+
store.push_back(fmt::arg("c1", c));
509+
++c.i;
510+
store.push_back(fmt::arg("c2", c));
511+
++c.i;
512+
store.push_back(fmt::arg("c_ref", std::cref(c)));
513+
++c.i;
514+
515+
std::string result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
516+
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
517+
}
518+
459519
struct copy_throwable {
460520
copy_throwable() {}
461521
copy_throwable(const copy_throwable&) { throw "deal with it"; }

0 commit comments

Comments
 (0)