Improve handling of named arguments

This commit is contained in:
Victor Zverovich 2020-04-14 06:48:55 -07:00
parent a9d62d3f35
commit 8a4630686e
8 changed files with 112 additions and 121 deletions

View File

@ -576,9 +576,9 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf,
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value &&
std::is_base_of<internal::basic_compiled_format,
CompiledFormat>::value)>
FMT_ENABLE_IF(
internal::is_output_iterator<OutputIt>::value&& std::is_base_of<
internal::basic_compiled_format, CompiledFormat>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {

View File

@ -762,25 +762,30 @@ template <typename T, typename Char> struct named_arg;
template <typename Char> struct named_arg_info {
const Char* name;
int arg_id;
int id;
};
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
struct arg_data {
T args[NUM_ARGS != 0 ? NUM_ARGS : 1];
named_arg_info<Char> named_args[NUM_NAMED_ARGS];
template <typename... U> arg_data(const U&... init) : args{init...} {}
// args_[0] points to named_args_ to avoid bloating format_args.
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)];
named_arg_info<Char> named_args_[NUM_NAMED_ARGS];
template <typename... U>
arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {}
arg_data(const arg_data& other) = delete;
const T* args() const { return args_ + 1; }
named_arg_info<Char>* named_args() { return named_args_; }
};
template <typename T, typename Char, size_t NUM_ARGS>
struct arg_data<T, Char, NUM_ARGS, 0> {
T args[NUM_ARGS != 0 ? NUM_ARGS : 1];
static constexpr std::nullptr_t named_args = nullptr;
template <typename... U> arg_data(const U&... init) : args{init...} {}
};
T args_[NUM_ARGS != 0 ? NUM_ARGS : 1];
template <typename T, typename Char, size_t NUM_ARGS>
constexpr std::nullptr_t arg_data<T, Char, NUM_ARGS, 0>::named_args;
template <typename... U> arg_data(const U&... init) : args_{init...} {}
const T* args() const { return args_; }
std::nullptr_t named_args() { return nullptr; }
};
template <typename Char>
inline void init_named_args(named_arg_info<Char>*, int, int) {}
@ -880,6 +885,11 @@ template <typename Char> struct string_value {
std::size_t size;
};
template <typename Char> struct named_arg_value {
const named_arg_info<Char>* data;
std::size_t size;
};
template <typename Context> struct custom_value {
using parse_context = basic_format_parse_context<typename Context::char_type>;
const void* value;
@ -907,7 +917,8 @@ template <typename Context> class value {
const void* pointer;
string_value<char_type> string;
custom_value<Context> custom;
const named_arg_base<char_type>* named_arg;
const named_arg_base<char_type>* named_arg; // DEPRECATED
named_arg_value<char_type> named_args;
};
FMT_CONSTEXPR value(int val = 0) : int_value(val) {}
@ -927,6 +938,8 @@ template <typename Context> class value {
string.size = val.size();
}
value(const void* val) : pointer(val) {}
value(const named_arg_info<char_type>* args, size_t size)
: named_args{args, size} {}
template <typename T> value(const T& val) {
custom.value = &val;
@ -1065,11 +1078,9 @@ template <typename Context> struct arg_mapper {
}
template <typename T>
FMT_CONSTEXPR const named_arg_base<char_type>& map(
const named_arg<T, char_type>& val) {
auto arg = make_arg<Context>(val.value);
std::memcpy(val.data, &arg, sizeof(arg));
return val;
FMT_CONSTEXPR auto map(const named_arg<T, char_type>& val)
-> decltype(std::declval<arg_mapper>().map(val.value)) {
return map(val.value);
}
int map(...) {
@ -1091,8 +1102,9 @@ using mapped_type_constant =
enum { packed_arg_bits = 5 };
// Maximum number of arguments with packed types.
enum { max_packed_args = 63 / packed_arg_bits };
enum { max_packed_args = 62 / packed_arg_bits };
enum : unsigned long long { is_unpacked_bit = 1ULL << 63 };
enum : unsigned long long { has_named_args_bit = 1ULL << 62 };
template <typename Context> class arg_map;
} // namespace internal
@ -1118,6 +1130,12 @@ template <typename Context> class basic_format_arg {
using char_type = typename Context::char_type;
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
friend struct internal::arg_data;
basic_format_arg(const internal::named_arg_info<char_type>* args, size_t size)
: value_(args, size) {}
public:
class handle {
public:
@ -1204,13 +1222,11 @@ FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
}
namespace internal {
// A map from argument names to their values for named arguments.
// DEPRECATED.
template <typename Context> class arg_map {
private:
using char_type = typename Context::char_type;
struct entry {
basic_string_view<char_type> name;
basic_string_view<typename Context::char_type> name;
basic_format_arg<Context> arg;
};
@ -1224,19 +1240,7 @@ template <typename Context> class arg_map {
}
public:
arg_map(const arg_map&) = delete;
void operator=(const arg_map&) = delete;
arg_map() : map_(nullptr), size_(0) {}
void init(const basic_format_args<Context>& args);
~arg_map() { delete[] map_; }
basic_format_arg<Context> find(basic_string_view<char_type> name) const {
// The list is unsorted, so just return the first matching name.
for (entry *it = map_, *end = map_ + size_; it != end; ++it) {
if (it->name == name) return it->arg;
}
return {};
}
};
// A type-erased reference to an std::locale to avoid heavy <locale> include.
@ -1328,7 +1332,6 @@ template <typename OutputIt, typename Char> class basic_format_context {
private:
OutputIt out_;
basic_format_args<basic_format_context> args_;
internal::arg_map<basic_format_context> map_;
internal::locale_ref loc_;
public:
@ -1352,7 +1355,7 @@ template <typename OutputIt, typename Char> class basic_format_context {
// Checks if manual indexing is used and returns the argument with the
// specified name.
format_arg arg(basic_string_view<char_type> name);
format_arg arg(basic_string_view<char_type> name) { return args_.get(name); }
internal::error_handler error_handler() { return {}; }
void on_error(const char* message) { error_handler().on_error(message); }
@ -1389,20 +1392,24 @@ class format_arg_store
{
private:
static const size_t num_args = sizeof...(Args);
static const size_t num_named_args = internal::count_named_args<Args...>();
static const bool is_packed = num_args <= internal::max_packed_args;
using value_type = conditional_t<is_packed, internal::value<Context>,
basic_format_arg<Context>>;
internal::arg_data<value_type, typename Context::char_type, num_args,
internal::count_named_args<Args...>()>
num_named_args>
data_;
friend class basic_format_args<Context>;
static constexpr unsigned long long desc =
is_packed ? internal::encode_types<Context, Args...>()
: internal::is_unpacked_bit | num_args;
(is_packed ? internal::encode_types<Context, Args...>()
: internal::is_unpacked_bit | num_args) |
(num_named_args != 0
? static_cast<unsigned long long>(internal::has_named_args_bit)
: 0);
public:
FMT_DEPRECATED static constexpr unsigned long long types = desc;
@ -1413,7 +1420,7 @@ class format_arg_store
basic_format_args<Context>(*this),
#endif
data_{internal::make_arg<is_packed, Context>(args)...} {
internal::init_named_args(data_.named_args, 0, 0, args...);
internal::init_named_args(data_.named_args(), 0, 0, args...);
}
};
@ -1559,6 +1566,9 @@ template <typename Context> class basic_format_args {
};
bool is_packed() const { return (desc_ & internal::is_unpacked_bit) == 0; }
bool has_named_args() const {
return (desc_ & internal::has_named_args_bit) != 0;
}
internal::type type(int index) const {
int shift = index * internal::packed_arg_bits;
@ -1597,7 +1607,7 @@ template <typename Context> class basic_format_args {
template <typename... Args>
basic_format_args(const format_arg_store<Context, Args...>& store)
: desc_(store.desc) {
set_data(store.data_.args);
set_data(store.data_.args());
}
/**
@ -1621,14 +1631,24 @@ template <typename Context> class basic_format_args {
set_data(args);
}
/** Returns the argument at specified index. */
format_arg get(int index) const {
format_arg arg = do_get(index);
/** Returns the argument with the specified id. */
format_arg get(int id) const {
format_arg arg = do_get(id);
if (arg.type_ == internal::type::named_arg_type)
arg = arg.value_.named_arg->template deserialize<Context>();
return arg;
}
template <typename Char> format_arg get(basic_string_view<Char> name) const {
if (!has_named_args()) return {};
const auto& named_args =
(is_packed() ? values_[-1] : args_[-1].value_).named_args;
for (size_t i = 0; i < named_args.size; ++i) {
if (named_args.data[i].name == name) return get(named_args.data[i].id);
}
return {};
}
int max_size() const {
unsigned long long max_packed = internal::max_packed_args;
return static_cast<int>(is_packed() ? max_packed

View File

@ -1367,24 +1367,6 @@ class cstring_type_checker : public ErrorHandler {
FMT_CONSTEXPR void on_pointer() {}
};
template <typename Context>
void arg_map<Context>::init(const basic_format_args<Context>& args) {
if (map_) return;
map_ = new entry[internal::to_unsigned(args.max_size())];
if (args.is_packed()) {
for (int i = 0;; ++i) {
internal::type arg_type = args.type(i);
if (arg_type == internal::type::none_type) return;
if (arg_type == internal::type::named_arg_type)
push_back(args.values_[i]);
}
}
for (int i = 0, n = args.max_size(); i < n; ++i) {
auto type = args.args_[i].type_;
if (type == internal::type::named_arg_type) push_back(args.args_[i].value_);
}
}
template <typename Char> struct nonfinite_writer {
sign_t sign;
const char* str;
@ -2195,10 +2177,10 @@ FMT_CONSTEXPR int get_dynamic_spec(FormatArg arg, ErrorHandler eh) {
struct auto_id {};
template <typename Context>
FMT_CONSTEXPR typename Context::format_arg get_arg(Context& ctx, int id) {
template <typename Context, typename ID>
FMT_CONSTEXPR typename Context::format_arg get_arg(Context& ctx, ID id) {
auto arg = ctx.arg(id);
if (!arg) ctx.on_error("argument index out of range");
if (!arg) ctx.on_error("argument not found");
return arg;
}
@ -2241,7 +2223,7 @@ class specs_handler : public specs_setter<typename Context::char_type> {
FMT_CONSTEXPR format_arg get_arg(basic_string_view<char_type> arg_id) {
parse_context_.check_arg_id(arg_id);
return context_.arg(arg_id);
return internal::get_arg(context_, arg_id);
}
ParseContext& parse_context_;
@ -2682,7 +2664,7 @@ class format_string_checker {
enum { num_args = sizeof...(Args) };
FMT_CONSTEXPR void check_arg_id() {
if (arg_id_ >= num_args) context_.on_error("argument index out of range");
if (arg_id_ >= num_args) context_.on_error("argument not found");
}
// Format specifier parsing function.
@ -3127,16 +3109,6 @@ template <typename Char = char> class dynamic_formatter {
const Char* format_str_;
};
template <typename Range, typename Char>
typename basic_format_context<Range, Char>::format_arg
basic_format_context<Range, Char>::arg(basic_string_view<char_type> name) {
map_.init(args_);
format_arg arg = map_.find(name);
if (arg.type() == internal::type::none_type)
this->on_error("argument not found");
return arg;
}
template <typename Char, typename ErrorHandler>
FMT_CONSTEXPR void advance_to(
basic_format_parse_context<Char, ErrorHandler>& ctx, const Char* p) {
@ -3160,14 +3132,16 @@ struct format_handler : internal::error_handler {
context.advance_to(out);
}
void get_arg(int id) { arg = internal::get_arg(context, id); }
template <typename ID> void get_arg(ID id) {
arg = internal::get_arg(context, id);
}
void on_arg_id() { get_arg(parse_context.next_arg_id()); }
void on_arg_id(int id) {
parse_context.check_arg_id(id);
get_arg(id);
}
void on_arg_id(basic_string_view<Char> id) { arg = context.arg(id); }
void on_arg_id(basic_string_view<Char> id) { get_arg(id); }
void on_replacement_field(const Char* p) {
advance_to(parse_context, p);

View File

@ -476,7 +476,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs);
if (arg_index == 0) on_error("argument index out of range");
if (arg_index == 0) on_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {

View File

@ -114,6 +114,25 @@ char* sprintf_format(Double value, internal::buffer<char>& buf,
}
return decimal_point_pos;
}
// This is deprecated and is kept only to preserve ABI compatibility.
template <typename Context>
void arg_map<Context>::init(const basic_format_args<Context>& args) {
if (map_) return;
map_ = new entry[internal::to_unsigned(args.max_size())];
if (args.is_packed()) {
for (int i = 0;; ++i) {
internal::type arg_type = args.type(i);
if (arg_type == internal::type::none_type) return;
if (arg_type == internal::type::named_arg_type)
push_back(args.values_[i]);
}
}
for (int i = 0, n = args.max_size(); i < n; ++i) {
auto type = args.args_[i].type_;
if (type == internal::type::named_arg_type) push_back(args.args_[i].value_);
}
}
} // namespace internal
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,

View File

@ -456,28 +456,6 @@ TEST(FormatDynArgsTest, CustomFormat) {
EXPECT_EQ("cust=0 and cust=1 and cust=3", 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(std::cref(a1));
std::string result = fmt::vformat("{a1_}", // and {} and {}",
store);
EXPECT_EQ("42", result);
}
struct copy_throwable {
copy_throwable() {}
copy_throwable(const copy_throwable&) { throw "deal with it"; }

View File

@ -640,7 +640,7 @@ TEST(FormatterTest, ArgErrors) {
EXPECT_THROW_MSG(format("{"), format_error, "invalid format string");
EXPECT_THROW_MSG(format("{?}"), format_error, "invalid format string");
EXPECT_THROW_MSG(format("{0"), format_error, "invalid format string");
EXPECT_THROW_MSG(format("{0}"), format_error, "argument index out of range");
EXPECT_THROW_MSG(format("{0}"), format_error, "argument not found");
EXPECT_THROW_MSG(format("{00}", 42), format_error, "invalid format string");
char format_str[BUFFER_SIZE];
@ -648,7 +648,7 @@ TEST(FormatterTest, ArgErrors) {
EXPECT_THROW_MSG(format(format_str), format_error, "invalid format string");
safe_sprintf(format_str, "{%u}", INT_MAX);
EXPECT_THROW_MSG(format(format_str), format_error,
"argument index out of range");
"argument not found");
safe_sprintf(format_str, "{%u", INT_MAX + 1u);
EXPECT_THROW_MSG(format(format_str), format_error, "number is too big");
@ -673,13 +673,13 @@ template <> struct TestFormat<0> {
TEST(FormatterTest, ManyArgs) {
EXPECT_EQ("19", TestFormat<20>::format("{19}"));
EXPECT_THROW_MSG(TestFormat<20>::format("{20}"), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(TestFormat<21>::format("{21}"), format_error,
"argument index out of range");
"argument not found");
enum { max_packed_args = fmt::internal::max_packed_args };
std::string format_str = fmt::format("{{{}}}", max_packed_args + 1);
EXPECT_THROW_MSG(TestFormat<max_packed_args>::format(format_str),
format_error, "argument index out of range");
format_error, "argument not found");
}
TEST(FormatterTest, NamedArg) {
@ -708,7 +708,7 @@ TEST(FormatterTest, AutoArgIndex) {
"cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(format("{:.{0}}", 1.2345, 2), format_error,
"cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(format("{}"), format_error, "argument index out of range");
EXPECT_THROW_MSG(format("{}"), format_error, "argument not found");
}
TEST(FormatterTest, EmptySpecs) { EXPECT_EQ("42", format("{0:}", 42)); }
@ -1012,7 +1012,7 @@ TEST(FormatterTest, RuntimeWidth) {
"cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(format("{0:{?}}", 0), format_error, "invalid format string");
EXPECT_THROW_MSG(format("{0:{1}}", 0), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(format("{0:{0:}}", 0), format_error,
"invalid format string");
@ -1161,7 +1161,7 @@ TEST(FormatterTest, RuntimePrecision) {
EXPECT_THROW_MSG(format("{0:.{1}", 0, 0), format_error,
"precision not allowed for this argument type");
EXPECT_THROW_MSG(format("{0:.{1}}", 0), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(format("{0:.{0:}}", 0), format_error,
"invalid format string");
@ -2441,7 +2441,7 @@ TEST(FormatTest, FormatStringErrors) {
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
EXPECT_ERROR("{:10000000000}", "number is too big", int);
EXPECT_ERROR("{:.10000000000}", "number is too big", int);
EXPECT_ERROR_NOARGS("{:x}", "argument index out of range");
EXPECT_ERROR_NOARGS("{:x}", "argument not found");
# if FMT_NUMERIC_ALIGN
EXPECT_ERROR("{0:=5", "unknown format specifier", int);
EXPECT_ERROR("{:=}", "format specifier requires numeric argument",
@ -2482,8 +2482,8 @@ TEST(FormatTest, FormatStringErrors) {
EXPECT_ERROR("{:.{0x}}", "invalid format string", int);
EXPECT_ERROR("{:.{-}}", "invalid format string", int);
EXPECT_ERROR("{:.x}", "missing precision specifier", int);
EXPECT_ERROR_NOARGS("{}", "argument index out of range");
EXPECT_ERROR("{1}", "argument index out of range", int);
EXPECT_ERROR_NOARGS("{}", "argument not found");
EXPECT_ERROR("{1}", "argument not found", int);
EXPECT_ERROR("{1}{}",
"cannot switch from manual to automatic argument indexing", int,
int);

View File

@ -113,14 +113,14 @@ TEST(PrintfTest, SwitchArgIndexing) {
TEST(PrintfTest, InvalidArgIndex) {
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", INT_MAX), 42), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM), 42), format_error,
"number is too big");
}
@ -223,7 +223,7 @@ TEST(PrintfTest, DynamicWidth) {
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
"width is not integer");
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(test_sprintf("%*d", BIG_NUM, 42), format_error,
"number is too big");
}
@ -269,7 +269,7 @@ TEST(PrintfTest, DynamicPrecision) {
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
"precision is not integer");
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error,
"argument index out of range");
"argument not found");
EXPECT_THROW_MSG(test_sprintf("%.*d", BIG_NUM, 42), format_error,
"number is too big");
if (sizeof(long long) != sizeof(int)) {