Fix type safety when using custom formatters (#394)

This commit is contained in:
Victor Zverovich
2016-10-07 08:37:06 -07:00
parent 506435bf71
commit dafbec7553
6 changed files with 143 additions and 116 deletions

View File

@ -403,72 +403,11 @@ FMT_FUNC void format_system_error(
fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32.
} }
template <typename Char>
void internal::ArgMap<Char>::init(const format_args &args) {
if (!map_.empty())
return;
typedef internal::NamedArg<Char> NamedArg;
const NamedArg *named_arg = 0;
bool use_values =
args.type(MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
if (use_values) {
for (unsigned i = 0;/*nothing*/; ++i) {
internal::Arg::Type arg_type = args.type(i);
switch (arg_type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
return;
}
for (unsigned i = 0; i != MAX_PACKED_ARGS; ++i) {
internal::Arg::Type arg_type = args.type(i);
if (arg_type == internal::Arg::NAMED_ARG) {
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
}
}
for (unsigned i = MAX_PACKED_ARGS;/*nothing*/; ++i) {
switch (args.args_[i].type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
}
template <typename Char> template <typename Char>
void internal::FixedBuffer<Char>::grow(std::size_t) { void internal::FixedBuffer<Char>::grow(std::size_t) {
FMT_THROW(std::runtime_error("buffer overflow")); FMT_THROW(std::runtime_error("buffer overflow"));
} }
FMT_FUNC Arg internal::FormatterBase::do_get_arg(
unsigned arg_index, const char *&error) {
Arg arg = args_[arg_index];
switch (arg.type) {
case Arg::NONE:
error = "argument index out of range";
break;
case Arg::NAMED_ARG:
arg = *static_cast<const internal::Arg*>(arg.pointer);
break;
default:
/*nothing*/;
}
return arg;
}
FMT_FUNC void report_system_error( FMT_FUNC void report_system_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT { int error_code, fmt::StringRef message) FMT_NOEXCEPT {
// 'fmt::' is for bcc32. // 'fmt::' is for bcc32.
@ -505,7 +444,8 @@ template <typename Char>
void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format, void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format,
format_args args); format_args args);
FMT_FUNC int vfprintf(std::FILE *f, CStringRef format, format_args args) { FMT_FUNC int vfprintf(std::FILE *f, CStringRef format,
basic_format_args<PrintfFormatter<char>> args) {
MemoryWriter w; MemoryWriter w;
printf(w, format, args); printf(w, format, args);
std::size_t size = w.size(); std::size_t size = w.size();

View File

@ -1429,7 +1429,7 @@ constexpr uint64_t make_type<void>() { return 0; }
enum { MAX_PACKED_ARGS = 16 }; enum { MAX_PACKED_ARGS = 16 };
} // namespace internal } // namespace internal
template <typename ...Args> template <typename Formatter, typename ...Args>
class format_arg_store { class format_arg_store {
private: private:
static const size_t NUM_ARGS = sizeof...(Args); static const size_t NUM_ARGS = sizeof...(Args);
@ -1441,13 +1441,12 @@ class format_arg_store {
// If the arguments are not packed, add one more element to mark the end. // If the arguments are not packed, add one more element to mark the end.
std::array<value_type, NUM_ARGS + (IS_PACKED ? 0 : 1)> data_; std::array<value_type, NUM_ARGS + (IS_PACKED ? 0 : 1)> data_;
template <typename Formatter, typename ...A> template <typename ...A>
friend format_arg_store<A...> make_format_args(const A & ... args); friend format_arg_store<Formatter, A...> make_format_args(const A & ... args);
public: public:
static const uint64_t TYPES = internal::make_type<Args..., void>(); static const uint64_t TYPES = internal::make_type<Args..., void>();
template <typename Formatter>
format_arg_store(const Args &... args, Formatter *) format_arg_store(const Args &... args, Formatter *)
: data_{{internal::MakeValue<Formatter>(args)...}} {} : data_{{internal::MakeValue<Formatter>(args)...}} {}
@ -1455,13 +1454,15 @@ class format_arg_store {
}; };
template <typename Formatter, typename ...Args> template <typename Formatter, typename ...Args>
inline format_arg_store<Args...> make_format_args(const Args & ... args) { inline format_arg_store<Formatter, Args...>
make_format_args(const Args & ... args) {
Formatter *f = nullptr; Formatter *f = nullptr;
return format_arg_store<Args...>(args..., f); return format_arg_store<Formatter, Args...>(args..., f);
} }
/** Formatting arguments. */ /** Formatting arguments. */
class format_args { template <typename Formatter>
class basic_format_args {
private: private:
// To reduce compiled code size per formatting function call, types of first // To reduce compiled code size per formatting function call, types of first
// MAX_PACKED_ARGS arguments are passed in the types_ field. // MAX_PACKED_ARGS arguments are passed in the types_ field.
@ -1491,10 +1492,10 @@ class format_args {
public: public:
typedef unsigned size_type; typedef unsigned size_type;
format_args() : types_(0) {} basic_format_args() : types_(0) {}
template <typename... Args> template <typename F, typename... Args>
format_args(const format_arg_store<Args...> &store) basic_format_args(const format_arg_store<F, Args...> &store)
: types_(store.TYPES) { : types_(store.TYPES) {
set_data(store.data()); set_data(store.data());
} }
@ -1525,6 +1526,9 @@ class format_args {
} }
}; };
typedef basic_format_args<BasicFormatter<char>> format_args;
typedef basic_format_args<BasicFormatter<wchar_t>> wformat_args;
#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call #define FMT_DISPATCH(call) static_cast<Impl*>(this)->call
/** /**
@ -1919,7 +1923,8 @@ class ArgMap {
MapType map_; MapType map_;
public: public:
FMT_API void init(const format_args &args); template <typename Formatter>
void init(const basic_format_args<Formatter> &args);
const internal::Arg* find(const fmt::BasicStringRef<Char> &name) const { const internal::Arg* find(const fmt::BasicStringRef<Char> &name) const {
// The list is unsorted, so just return the first matching name. // The list is unsorted, so just return the first matching name.
@ -1932,6 +1937,52 @@ class ArgMap {
} }
}; };
template <typename Char>
template <typename Formatter>
void ArgMap<Char>::init(const basic_format_args<Formatter> &args) {
if (!map_.empty())
return;
typedef internal::NamedArg<Char> NamedArg;
const NamedArg *named_arg = 0;
bool use_values =
args.type(MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
if (use_values) {
for (unsigned i = 0;/*nothing*/; ++i) {
internal::Arg::Type arg_type = args.type(i);
switch (arg_type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
return;
}
for (unsigned i = 0; i != MAX_PACKED_ARGS; ++i) {
internal::Arg::Type arg_type = args.type(i);
if (arg_type == internal::Arg::NAMED_ARG) {
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
}
}
for (unsigned i = MAX_PACKED_ARGS;/*nothing*/; ++i) {
switch (args.args_[i].type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.push_back(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
}
template <typename Impl, typename Char> template <typename Impl, typename Char>
class ArgFormatterBase : public ArgVisitor<Impl, void> { class ArgFormatterBase : public ArgVisitor<Impl, void> {
private: private:
@ -2030,21 +2081,33 @@ class ArgFormatterBase : public ArgVisitor<Impl, void> {
} }
}; };
template <typename Formatter>
class FormatterBase { class FormatterBase {
private: private:
format_args args_; basic_format_args<Formatter> args_;
int next_arg_index_; int next_arg_index_;
// Returns the argument with specified index. // Returns the argument with specified index.
FMT_API Arg do_get_arg(unsigned arg_index, const char *&error); Arg do_get_arg(unsigned arg_index, const char *&error) {
Arg arg = args_[arg_index];
switch (arg.type) {
case Arg::NONE:
error = "argument index out of range";
break;
case Arg::NAMED_ARG:
arg = *static_cast<const internal::Arg*>(arg.pointer);
break;
default:
/*nothing*/;
}
return arg;
}
protected: protected:
const format_args &args() const { return args_; } FormatterBase(basic_format_args<Formatter> args)
: args_(args), next_arg_index_(0) {}
explicit FormatterBase(const format_args &args) { const basic_format_args<Formatter> &args() const { return args_; }
args_ = args;
next_arg_index_ = 0;
}
// Returns the next argument. // Returns the next argument.
Arg next_arg(const char *&error) { Arg next_arg(const char *&error) {
@ -2132,7 +2195,8 @@ class ArgFormatter : public BasicArgFormatter<ArgFormatter<Char>, Char> {
/** This template formats data and writes the output to a writer. */ /** This template formats data and writes the output to a writer. */
template <typename CharType, typename ArgFormatter> template <typename CharType, typename ArgFormatter>
class BasicFormatter : private internal::FormatterBase { class BasicFormatter :
private internal::FormatterBase<BasicFormatter<CharType, ArgFormatter>> {
public: public:
/** The character type for the output. */ /** The character type for the output. */
typedef CharType Char; typedef CharType Char;
@ -2143,7 +2207,8 @@ class BasicFormatter : private internal::FormatterBase {
FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter); FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter);
using internal::FormatterBase::get_arg; typedef internal::FormatterBase<BasicFormatter> Base;
using Base::get_arg;
// Checks if manual indexing is used and returns the argument with // Checks if manual indexing is used and returns the argument with
// specified name. // specified name.
@ -2163,8 +2228,8 @@ class BasicFormatter : private internal::FormatterBase {
appropriate lifetimes. appropriate lifetimes.
\endrst \endrst
*/ */
BasicFormatter(const format_args &args, BasicWriter<Char> &w) BasicFormatter(basic_format_args<BasicFormatter> args, BasicWriter<Char> &w)
: internal::FormatterBase(args), writer_(w) {} : Base(args), writer_(w) {}
/** Returns a reference to the writer associated with this formatter. */ /** Returns a reference to the writer associated with this formatter. */
BasicWriter<Char> &writer() { return writer_; } BasicWriter<Char> &writer() { return writer_; }
@ -2404,7 +2469,8 @@ class BasicWriter {
return std::basic_string<Char>(&buffer_[0], buffer_.size()); return std::basic_string<Char>(&buffer_[0], buffer_.size());
} }
void vwrite(BasicCStringRef<Char> format, format_args args) { void vwrite(BasicCStringRef<Char> format,
basic_format_args<BasicFormatter<Char>> args) {
BasicFormatter<Char>(args, *this).format(format); BasicFormatter<Char>(args, *this).format(format);
} }
@ -3111,7 +3177,7 @@ inline std::string format(CStringRef format_str, const Args & ... args) {
return vformat(format_str, make_format_args<BasicFormatter<char>>(args...)); return vformat(format_str, make_format_args<BasicFormatter<char>>(args...));
} }
inline std::wstring vformat(WCStringRef format_str, format_args args) { inline std::wstring vformat(WCStringRef format_str, wformat_args args) {
WMemoryWriter w; WMemoryWriter w;
w.vwrite(format_str, args); w.vwrite(format_str, args);
return w.str(); return w.str();
@ -3348,8 +3414,8 @@ void check_sign(const Char *&s, const Arg &arg) {
template <typename Char, typename AF> template <typename Char, typename AF>
inline internal::Arg BasicFormatter<Char, AF>::get_arg( inline internal::Arg BasicFormatter<Char, AF>::get_arg(
BasicStringRef<Char> arg_name, const char *&error) { BasicStringRef<Char> arg_name, const char *&error) {
if (check_no_auto_index(error)) { if (this->check_no_auto_index(error)) {
map_.init(args()); map_.init(this->args());
const internal::Arg *arg = map_.find(arg_name); const internal::Arg *arg = map_.find(arg_name);
if (arg) if (arg)
return *arg; return *arg;
@ -3362,7 +3428,7 @@ template <typename Char, typename AF>
inline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) { inline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) {
const char *error = 0; const char *error = 0;
internal::Arg arg = *s < '0' || *s > '9' ? internal::Arg arg = *s < '0' || *s > '9' ?
next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error); this->next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error);
if (error) { if (error) {
FMT_THROW(format_error( FMT_THROW(format_error(
*s != '}' && *s != ':' ? "invalid format string" : error)); *s != '}' && *s != ':' ? "invalid format string" : error));
@ -3563,18 +3629,18 @@ void BasicFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
Char c = *s++; Char c = *s++;
if (c != '{' && c != '}') continue; if (c != '{' && c != '}') continue;
if (*s == c) { if (*s == c) {
write(writer_, start, s); this->write(writer_, start, s);
start = ++s; start = ++s;
continue; continue;
} }
if (c == '}') if (c == '}')
FMT_THROW(format_error("unmatched '}' in format string")); FMT_THROW(format_error("unmatched '}' in format string"));
write(writer_, start, s - 1); this->write(writer_, start, s - 1);
internal::Arg arg = internal::is_name_start(*s) ? internal::Arg arg = internal::is_name_start(*s) ?
parse_arg_name(s) : parse_arg_index(s); parse_arg_name(s) : parse_arg_index(s);
start = s = format(s, arg); start = s = format(s, arg);
} }
write(writer_, start, s); this->write(writer_, start, s);
} }
} // namespace fmt } // namespace fmt

View File

@ -262,7 +262,8 @@ class BasicPrintfArgFormatter : public internal::ArgFormatterBase<Impl, Char> {
/** Formats an argument of a custom (user-defined) type. */ /** Formats an argument of a custom (user-defined) type. */
void visit_custom(internal::Arg::CustomValue c) { void visit_custom(internal::Arg::CustomValue c) {
BasicFormatter<Char> formatter(format_args(), this->writer()); BasicFormatter<Char> formatter(basic_format_args<BasicFormatter<Char>>(),
this->writer());
const Char format_str[] = {'}', 0}; const Char format_str[] = {'}', 0};
const Char *format = format_str; const Char *format = format_str;
c.format(&formatter, c.value, &format); c.format(&formatter, c.value, &format);
@ -281,10 +282,13 @@ class PrintfArgFormatter
/** This template formats data and writes the output to a writer. */ /** This template formats data and writes the output to a writer. */
template <typename Char, typename ArgFormatter = PrintfArgFormatter<Char> > template <typename Char, typename ArgFormatter = PrintfArgFormatter<Char> >
class PrintfFormatter : private internal::FormatterBase { class PrintfFormatter :
private internal::FormatterBase<PrintfFormatter<Char, ArgFormatter>> {
private: private:
BasicWriter<Char> &writer_; BasicWriter<Char> &writer_;
typedef internal::FormatterBase<PrintfFormatter> Base;
void parse_flags(FormatSpec &spec, const Char *&s); void parse_flags(FormatSpec &spec, const Char *&s);
// Returns the argument with specified index or, if arg_index is equal // Returns the argument with specified index or, if arg_index is equal
@ -304,8 +308,9 @@ class PrintfFormatter : private internal::FormatterBase {
appropriate lifetimes. appropriate lifetimes.
\endrst \endrst
*/ */
explicit PrintfFormatter(const format_args &args, BasicWriter<Char> &w) explicit PrintfFormatter(basic_format_args<PrintfFormatter> args,
: FormatterBase(args), writer_(w) {} BasicWriter<Char> &w)
: Base(args), writer_(w) {}
/** Formats stored arguments and writes the output to the writer. */ /** Formats stored arguments and writes the output to the writer. */
FMT_API void format(BasicCStringRef<Char> format_str); FMT_API void format(BasicCStringRef<Char> format_str);
@ -343,7 +348,7 @@ internal::Arg PrintfFormatter<Char, AF>::get_arg(const Char *s,
(void)s; (void)s;
const char *error = 0; const char *error = 0;
internal::Arg arg = arg_index == std::numeric_limits<unsigned>::max() ? internal::Arg arg = arg_index == std::numeric_limits<unsigned>::max() ?
next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); this->next_arg(error) : Base::get_arg(arg_index - 1, error);
if (error) if (error)
FMT_THROW(format_error(!*s ? "invalid format string" : error)); FMT_THROW(format_error(!*s ? "invalid format string" : error));
return arg; return arg;
@ -391,11 +396,11 @@ void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
Char c = *s++; Char c = *s++;
if (c != '%') continue; if (c != '%') continue;
if (*s == c) { if (*s == c) {
write(writer_, start, s); this->write(writer_, start, s);
start = ++s; start = ++s;
continue; continue;
} }
write(writer_, start, s - 1); this->write(writer_, start, s - 1);
FormatSpec spec; FormatSpec spec;
spec.align_ = ALIGN_RIGHT; spec.align_ = ALIGN_RIGHT;
@ -480,16 +485,17 @@ void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
// Format argument. // Format argument.
AF(writer_, spec).visit(arg); AF(writer_, spec).visit(arg);
} }
write(writer_, start, s); this->write(writer_, start, s);
} }
template <typename Char> template <typename Char>
void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format, void printf(BasicWriter<Char> &w, BasicCStringRef<Char> format,
format_args args) { basic_format_args<PrintfFormatter<Char>> args) {
PrintfFormatter<Char>(args, w).format(format); PrintfFormatter<Char>(args, w).format(format);
} }
inline std::string vsprintf(CStringRef format, format_args args) { inline std::string vsprintf(CStringRef format,
basic_format_args<PrintfFormatter<char>> args) {
MemoryWriter w; MemoryWriter w;
printf(w, format, args); printf(w, format, args);
return w.str(); return w.str();
@ -509,7 +515,8 @@ inline std::string sprintf(CStringRef format_str, const Args & ... args) {
return vsprintf(format_str, make_format_args<BasicFormatter<char>>(args...)); return vsprintf(format_str, make_format_args<BasicFormatter<char>>(args...));
} }
inline std::wstring vsprintf(WCStringRef format, format_args args) { inline std::wstring vsprintf(WCStringRef format,
basic_format_args<PrintfFormatter<wchar_t>> args) {
WMemoryWriter w; WMemoryWriter w;
printf(w, format, args); printf(w, format, args);
return w.str(); return w.str();
@ -521,7 +528,8 @@ inline std::wstring sprintf(WCStringRef format_str, const Args & ... args) {
return vsprintf(format_str, vargs); return vsprintf(format_str, vargs);
} }
FMT_API int vfprintf(std::FILE *f, CStringRef format, format_args args); FMT_API int vfprintf(std::FILE *f, CStringRef format,
basic_format_args<PrintfFormatter<char>> args);
/** /**
\rst \rst
@ -534,11 +542,12 @@ FMT_API int vfprintf(std::FILE *f, CStringRef format, format_args args);
*/ */
template <typename... Args> template <typename... Args>
inline int fprintf(std::FILE *f, CStringRef format_str, const Args & ... args) { inline int fprintf(std::FILE *f, CStringRef format_str, const Args & ... args) {
auto vargs = make_format_args<BasicFormatter<char>>(args...); auto vargs = make_format_args<PrintfFormatter<char>>(args...);
return vfprintf(f, format_str, vargs); return vfprintf(f, format_str, vargs);
} }
inline int vprintf(CStringRef format, format_args args) { inline int vprintf(CStringRef format,
basic_format_args<PrintfFormatter<char>> args) {
return vfprintf(stdout, format, args); return vfprintf(stdout, format, args);
} }
@ -556,7 +565,8 @@ inline int printf(CStringRef format_str, const Args & ... args) {
return vprintf(format_str, make_format_args<BasicFormatter<char>>(args...)); return vprintf(format_str, make_format_args<BasicFormatter<char>>(args...));
} }
inline int vfprintf(std::ostream &os, CStringRef format_str, format_args args) { inline int vfprintf(std::ostream &os, CStringRef format_str,
basic_format_args<PrintfFormatter<char>> args) {
MemoryWriter w; MemoryWriter w;
printf(w, format_str, args); printf(w, format_str, args);
internal::write(os, w); internal::write(os, w);

View File

@ -45,10 +45,13 @@ class CustomPrintfArgFormatter :
} }
}; };
std::string custom_vformat(const char *format_str, fmt::format_args args) { typedef fmt::BasicFormatter<char, CustomArgFormatter> CustomFormatter;
std::string custom_vformat(const char *format_str,
fmt::basic_format_args<CustomFormatter> args) {
fmt::MemoryWriter writer; fmt::MemoryWriter writer;
// Pass custom argument formatter as a template arg to BasicFormatter. // Pass custom argument formatter as a template arg to BasicFormatter.
fmt::BasicFormatter<char, CustomArgFormatter> formatter(args, writer); CustomFormatter formatter(args, writer);
formatter.format(format_str); formatter.format(format_str);
return writer.str(); return writer.str();
} }
@ -59,9 +62,14 @@ std::string custom_format(const char *format_str, const Args & ... args) {
return custom_vformat(format_str, va); return custom_vformat(format_str, va);
} }
std::string custom_vsprintf(const char* format_str, fmt::format_args args) { typedef fmt::PrintfFormatter<char, CustomPrintfArgFormatter>
CustomPrintfFormatter;
std::string custom_vsprintf(
const char* format_str,
fmt::basic_format_args<CustomPrintfFormatter> args) {
fmt::MemoryWriter writer; fmt::MemoryWriter writer;
fmt::PrintfFormatter<char, CustomPrintfArgFormatter> formatter(args, writer); CustomPrintfFormatter formatter(args, writer);
formatter.format(format_str); formatter.format(format_str);
return writer.str(); return writer.str();
} }

View File

@ -1633,16 +1633,19 @@ class MockArgFormatter :
MOCK_METHOD1(visit_int, void (int value)); MOCK_METHOD1(visit_int, void (int value));
}; };
void vcustom_format(const char *format_str, fmt::format_args args) { typedef fmt::BasicFormatter<char, MockArgFormatter> CustomFormatter;
void custom_vformat(const char *format_str,
fmt::basic_format_args<CustomFormatter> args) {
fmt::MemoryWriter writer; fmt::MemoryWriter writer;
fmt::BasicFormatter<char, MockArgFormatter> formatter(args, writer); CustomFormatter formatter(args, writer);
formatter.format(format_str); formatter.format(format_str);
} }
template <typename... Args> template <typename... Args>
void custom_format(const char *format_str, const Args & ... args) { void custom_format(const char *format_str, const Args & ... args) {
auto va = fmt::make_format_args<fmt::BasicFormatter<char>>(args...); auto va = fmt::make_format_args<fmt::BasicFormatter<char>>(args...);
return vcustom_format(format_str, va); return custom_vformat(format_str, va);
} }
TEST(FormatTest, CustomArgFormatter) { TEST(FormatTest, CustomArgFormatter) {

View File

@ -67,7 +67,7 @@ struct TestArgFormatter : fmt::BasicArgFormatter<TestArgFormatter, char> {
TEST(OStreamTest, CustomArg) { TEST(OStreamTest, CustomArg) {
fmt::MemoryWriter writer; fmt::MemoryWriter writer;
typedef fmt::BasicFormatter<char, TestArgFormatter> Formatter; typedef fmt::BasicFormatter<char, TestArgFormatter> Formatter;
Formatter formatter(fmt::format_args(), writer); Formatter formatter(fmt::basic_format_args<Formatter>(), writer);
fmt::FormatSpec spec; fmt::FormatSpec spec;
TestArgFormatter af(formatter, spec, "}"); TestArgFormatter af(formatter, spec, "}");
af.visit(fmt::internal::MakeArg<Formatter>(TestEnum())); af.visit(fmt::internal::MakeArg<Formatter>(TestEnum()));