diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 64bd3089..75b33313 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -576,9 +576,9 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf, } template ::value && - std::is_base_of::value)> + FMT_ENABLE_IF( + internal::is_output_iterator::value&& std::is_base_of< + internal::basic_compiled_format, CompiledFormat>::value)> format_to_n_result format_to_n(OutputIt out, size_t n, const CompiledFormat& cf, const Args&... args) { diff --git a/include/fmt/core.h b/include/fmt/core.h index 01804f87..2a3c1233 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -762,25 +762,30 @@ template struct named_arg; template struct named_arg_info { const Char* name; - int arg_id; + int id; }; template struct arg_data { - T args[NUM_ARGS != 0 ? NUM_ARGS : 1]; - named_arg_info named_args[NUM_NAMED_ARGS]; - template 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 named_args_[NUM_NAMED_ARGS]; + + template + 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* named_args() { return named_args_; } }; template struct arg_data { - T args[NUM_ARGS != 0 ? NUM_ARGS : 1]; - static constexpr std::nullptr_t named_args = nullptr; - template arg_data(const U&... init) : args{init...} {} -}; + T args_[NUM_ARGS != 0 ? NUM_ARGS : 1]; -template -constexpr std::nullptr_t arg_data::named_args; + template arg_data(const U&... init) : args_{init...} {} + const T* args() const { return args_; } + std::nullptr_t named_args() { return nullptr; } +}; template inline void init_named_args(named_arg_info*, int, int) {} @@ -880,6 +885,11 @@ template struct string_value { std::size_t size; }; +template struct named_arg_value { + const named_arg_info* data; + std::size_t size; +}; + template struct custom_value { using parse_context = basic_format_parse_context; const void* value; @@ -907,7 +917,8 @@ template class value { const void* pointer; string_value string; custom_value custom; - const named_arg_base* named_arg; + const named_arg_base* named_arg; // DEPRECATED + named_arg_value named_args; }; FMT_CONSTEXPR value(int val = 0) : int_value(val) {} @@ -927,6 +938,8 @@ template class value { string.size = val.size(); } value(const void* val) : pointer(val) {} + value(const named_arg_info* args, size_t size) + : named_args{args, size} {} template value(const T& val) { custom.value = &val; @@ -1065,11 +1078,9 @@ template struct arg_mapper { } template - FMT_CONSTEXPR const named_arg_base& map( - const named_arg& val) { - auto arg = make_arg(val.value); - std::memcpy(val.data, &arg, sizeof(arg)); - return val; + FMT_CONSTEXPR auto map(const named_arg& val) + -> decltype(std::declval().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 class arg_map; } // namespace internal @@ -1118,6 +1130,12 @@ template class basic_format_arg { using char_type = typename Context::char_type; + template + friend struct internal::arg_data; + + basic_format_arg(const internal::named_arg_info* 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 class arg_map { private: - using char_type = typename Context::char_type; - struct entry { - basic_string_view name; + basic_string_view name; basic_format_arg arg; }; @@ -1224,19 +1240,7 @@ template 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& args); - ~arg_map() { delete[] map_; } - - basic_format_arg find(basic_string_view 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 include. @@ -1328,7 +1332,6 @@ template class basic_format_context { private: OutputIt out_; basic_format_args args_; - internal::arg_map map_; internal::locale_ref loc_; public: @@ -1352,7 +1355,7 @@ template class basic_format_context { // Checks if manual indexing is used and returns the argument with the // specified name. - format_arg arg(basic_string_view name); + format_arg arg(basic_string_view 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(); static const bool is_packed = num_args <= internal::max_packed_args; using value_type = conditional_t, basic_format_arg>; internal::arg_data()> + num_named_args> data_; friend class basic_format_args; static constexpr unsigned long long desc = - is_packed ? internal::encode_types() - : internal::is_unpacked_bit | num_args; + (is_packed ? internal::encode_types() + : internal::is_unpacked_bit | num_args) | + (num_named_args != 0 + ? static_cast(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(*this), #endif data_{internal::make_arg(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 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 class basic_format_args { template basic_format_args(const format_arg_store& store) : desc_(store.desc) { - set_data(store.data_.args); + set_data(store.data_.args()); } /** @@ -1621,14 +1631,24 @@ template 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(); return arg; } + template format_arg get(basic_string_view 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(is_packed() ? max_packed diff --git a/include/fmt/format.h b/include/fmt/format.h index 3b1ebe59..1a192cca 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1367,24 +1367,6 @@ class cstring_type_checker : public ErrorHandler { FMT_CONSTEXPR void on_pointer() {} }; -template -void arg_map::init(const basic_format_args& 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 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 -FMT_CONSTEXPR typename Context::format_arg get_arg(Context& ctx, int id) { +template +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 { FMT_CONSTEXPR format_arg get_arg(basic_string_view 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 class dynamic_formatter { const Char* format_str_; }; -template -typename basic_format_context::format_arg -basic_format_context::arg(basic_string_view 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 FMT_CONSTEXPR void advance_to( basic_format_parse_context& 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 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 id) { arg = context.arg(id); } + void on_arg_id(basic_string_view id) { get_arg(id); } void on_replacement_field(const Char* p) { advance_to(parse_context, p); diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 9c7b85ac..f80a57ad 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -476,7 +476,7 @@ OutputIt basic_printf_context::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 == '.') { diff --git a/src/format.cc b/src/format.cc index 4b8940a0..3d6da4db 100644 --- a/src/format.cc +++ b/src/format.cc @@ -114,6 +114,25 @@ char* sprintf_format(Double value, internal::buffer& buf, } return decimal_point_pos; } + +// This is deprecated and is kept only to preserve ABI compatibility. +template +void arg_map::init(const basic_format_args& 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&, diff --git a/test/core-test.cc b/test/core-test.cc index 735fcde2..63955d98 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -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 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"; } diff --git a/test/format-test.cc b/test/format-test.cc index e2bd98d2..8caf9ecf 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -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::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); diff --git a/test/printf-test.cc b/test/printf-test.cc index 70b1238d..adb7e65a 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -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)) {