Make formatter<T> override ostream<< for templates (#952)
This commit is contained in:
parent
1b11b000c7
commit
5f1ceebc7f
@ -331,16 +331,25 @@ struct error_handler {
|
|||||||
FMT_API void on_error(const char* message);
|
FMT_API void on_error(const char* message);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T> struct no_formatter_error : std::false_type {};
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
// GCC 4.6.x cannot expand `T...`.
|
// GCC 4.6.x cannot expand `T...`.
|
||||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 407
|
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 407
|
||||||
template <typename... T> struct is_constructible : std::false_type {};
|
template <typename... T> struct is_constructible : std::false_type {};
|
||||||
|
|
||||||
|
typedef char yes[1];
|
||||||
|
typedef char no[2];
|
||||||
|
|
||||||
|
template <typename T> struct is_default_constructible {
|
||||||
|
template <typename U> static yes& test(int (*)[sizeof(new U)]);
|
||||||
|
template <typename U> static no& test(...);
|
||||||
|
enum { value = sizeof(test<T>(FMT_NULL)) == sizeof(yes) };
|
||||||
|
};
|
||||||
#else
|
#else
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
struct is_constructible : std::is_constructible<T...> {};
|
struct is_constructible : std::is_constructible<T...> {};
|
||||||
|
template <typename T>
|
||||||
|
struct is_default_constructible : std::is_default_constructible<T> {};
|
||||||
#endif
|
#endif
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An implementation of ``std::basic_string_view`` for pre-C++17. It provides a
|
An implementation of ``std::basic_string_view`` for pre-C++17. It provides a
|
||||||
@ -492,22 +501,12 @@ FMT_CONSTEXPR basic_string_view<typename S::char_type> to_string_view(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Context> class basic_format_arg;
|
template <typename Context> class basic_format_arg;
|
||||||
|
|
||||||
template <typename Context> class basic_format_args;
|
template <typename Context> class basic_format_args;
|
||||||
|
|
||||||
// A formatter for objects of type T.
|
// A formatter for objects of type T.
|
||||||
template <typename T, typename Char = char, typename Enable = void>
|
template <typename T, typename Char = char, typename Enable = void>
|
||||||
struct formatter {
|
struct formatter {
|
||||||
static_assert(
|
formatter() = delete;
|
||||||
internal::no_formatter_error<T>::value,
|
|
||||||
"don't know how to format the type, include fmt/ostream.h if it provides "
|
|
||||||
"an operator<< that should be used");
|
|
||||||
|
|
||||||
// The following functions are not defined intentionally.
|
|
||||||
template <typename ParseContext>
|
|
||||||
typename ParseContext::iterator parse(ParseContext&);
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename Char, typename Enable = void>
|
template <typename T, typename Char, typename Enable = void>
|
||||||
@ -517,6 +516,16 @@ struct convert_to_int
|
|||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
template <typename T> struct no_formatter_error : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Char = char, typename Enable = void>
|
||||||
|
struct fallback_formatter {
|
||||||
|
static_assert(
|
||||||
|
no_formatter_error<T>::value,
|
||||||
|
"don't know how to format the type, include fmt/ostream.h if it provides "
|
||||||
|
"an operator<< that should be used");
|
||||||
|
};
|
||||||
|
|
||||||
struct dummy_string_view {
|
struct dummy_string_view {
|
||||||
typedef void char_type;
|
typedef void char_type;
|
||||||
};
|
};
|
||||||
@ -623,9 +632,29 @@ template <typename Context> class value {
|
|||||||
}
|
}
|
||||||
value(const void* val) { pointer = val; }
|
value(const void* val) { pointer = val; }
|
||||||
|
|
||||||
template <typename T> explicit value(const T& val) {
|
template <typename T,
|
||||||
|
typename std::enable_if<
|
||||||
|
is_default_constructible<
|
||||||
|
typename Context::template formatter_type<T>::type>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
explicit value(const T& val) {
|
||||||
custom.value = &val;
|
custom.value = &val;
|
||||||
custom.format = &format_custom_arg<T>;
|
// Get the formatter type through the context to allow different contexts
|
||||||
|
// have different extension points, e.g. `formatter<T>` for `format` and
|
||||||
|
// `printf_formatter<T>` for `printf`.
|
||||||
|
typedef typename Context::template formatter_type<T>::type formatter;
|
||||||
|
custom.format = &format_custom_arg<T, formatter>;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T,
|
||||||
|
typename std::enable_if<
|
||||||
|
!is_default_constructible<
|
||||||
|
typename Context::template formatter_type<T>::type>::value,
|
||||||
|
int>::type = 0>
|
||||||
|
explicit value(const T& val) {
|
||||||
|
custom.value = &val;
|
||||||
|
custom.format =
|
||||||
|
&format_custom_arg<T, internal::fallback_formatter<T, char_type>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const named_arg_base<char_type>& as_named_arg() {
|
const named_arg_base<char_type>& as_named_arg() {
|
||||||
@ -634,12 +663,9 @@ template <typename Context> class value {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Formats an argument of a custom type, such as a user-defined class.
|
// Formats an argument of a custom type, such as a user-defined class.
|
||||||
template <typename T>
|
template <typename T, typename Formatter>
|
||||||
static void format_custom_arg(const void* arg, Context& ctx) {
|
static void format_custom_arg(const void* arg, Context& ctx) {
|
||||||
// Get the formatter type through the context to allow different contexts
|
Formatter f;
|
||||||
// have different extension points, e.g. `formatter<T>` for `format` and
|
|
||||||
// `printf_formatter<T>` for `printf`.
|
|
||||||
typename Context::template formatter_type<T>::type f;
|
|
||||||
auto&& parse_ctx = ctx.parse_context();
|
auto&& parse_ctx = ctx.parse_context();
|
||||||
parse_ctx.advance_to(f.parse(parse_ctx));
|
parse_ctx.advance_to(f.parse(parse_ctx));
|
||||||
ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
|
ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
|
||||||
|
@ -1702,8 +1702,7 @@ class specs_handler : public specs_setter<typename Context::char_type> {
|
|||||||
|
|
||||||
FMT_CONSTEXPR format_arg get_arg(auto_id) { return context_.next_arg(); }
|
FMT_CONSTEXPR format_arg get_arg(auto_id) { return context_.next_arg(); }
|
||||||
|
|
||||||
template <typename Id>
|
template <typename Id> FMT_CONSTEXPR format_arg get_arg(Id arg_id) {
|
||||||
FMT_CONSTEXPR format_arg get_arg(Id arg_id) {
|
|
||||||
context_.parse_context().check_arg_id(arg_id);
|
context_.parse_context().check_arg_id(arg_id);
|
||||||
return context_.arg(arg_id);
|
return context_.arg(arg_id);
|
||||||
}
|
}
|
||||||
|
@ -93,22 +93,12 @@ void format_value(basic_buffer<Char>& buffer, const T& value) {
|
|||||||
output << value;
|
output << value;
|
||||||
buffer.resize(buffer.size());
|
buffer.resize(buffer.size());
|
||||||
}
|
}
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
// Disable conversion to int if T has an overloaded operator<< which is a free
|
|
||||||
// function (not a member of std::ostream).
|
|
||||||
template <typename T, typename Char> struct convert_to_int<T, Char, void> {
|
|
||||||
static const bool value = convert_to_int<T, Char, int>::value &&
|
|
||||||
!internal::is_streamable<T, Char>::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<T, Char,
|
struct fallback_formatter<
|
||||||
typename std::enable_if<
|
T, Char,
|
||||||
internal::is_streamable<T, Char>::value &&
|
typename std::enable_if<internal::is_streamable<T, Char>::value>::type>
|
||||||
!internal::format_type<typename buffer_context<Char>::type,
|
|
||||||
T>::value>::type>
|
|
||||||
: formatter<basic_string_view<Char>, Char> {
|
: formatter<basic_string_view<Char>, Char> {
|
||||||
template <typename Context>
|
template <typename Context>
|
||||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
|
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
|
||||||
@ -118,6 +108,14 @@ struct formatter<T, Char,
|
|||||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
// Disable conversion to int if T has an overloaded operator<< which is a free
|
||||||
|
// function (not a member of std::ostream).
|
||||||
|
template <typename T, typename Char> struct convert_to_int<T, Char, void> {
|
||||||
|
static const bool value = convert_to_int<T, Char, int>::value &&
|
||||||
|
!internal::is_streamable<T, Char>::value;
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
inline void vprint(
|
inline void vprint(
|
||||||
|
@ -2144,6 +2144,10 @@ struct test_context {
|
|||||||
typedef char char_type;
|
typedef char char_type;
|
||||||
typedef fmt::basic_format_arg<test_context> format_arg;
|
typedef fmt::basic_format_arg<test_context> format_arg;
|
||||||
|
|
||||||
|
template <typename T> struct formatter_type {
|
||||||
|
typedef fmt::formatter<T, char_type> type;
|
||||||
|
};
|
||||||
|
|
||||||
FMT_CONSTEXPR fmt::basic_format_arg<test_context> next_arg() {
|
FMT_CONSTEXPR fmt::basic_format_arg<test_context> next_arg() {
|
||||||
return fmt::internal::make_arg<test_context>(11);
|
return fmt::internal::make_arg<test_context>(11);
|
||||||
}
|
}
|
||||||
|
@ -178,6 +178,29 @@ template <typename Output> Output& operator<<(Output& out, ABC) {
|
|||||||
}
|
}
|
||||||
} // namespace fmt_test
|
} // namespace fmt_test
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TestTemplate {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::ostream& operator<<(std::ostream& os, TestTemplate<T>) {
|
||||||
|
return os << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace fmt {
|
||||||
|
template <typename T>
|
||||||
|
struct formatter<TestTemplate<T>> : formatter<int> {
|
||||||
|
template <typename FormatContext>
|
||||||
|
typename FormatContext::iterator format(TestTemplate<T>, FormatContext& ctx) {
|
||||||
|
return formatter<int>::format(2, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 407
|
||||||
|
TEST(OStreamTest, Template) {
|
||||||
|
EXPECT_EQ("2", fmt::format("{}", TestTemplate<int>()));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(FormatTest, FormatToN) {
|
TEST(FormatTest, FormatToN) {
|
||||||
char buffer[4];
|
char buffer[4];
|
||||||
buffer[3] = 'x';
|
buffer[3] = 'x';
|
||||||
@ -190,6 +213,7 @@ TEST(FormatTest, FormatToN) {
|
|||||||
EXPECT_EQ(buffer + 3, result.out);
|
EXPECT_EQ(buffer + 3, result.out);
|
||||||
EXPECT_EQ("xABx", fmt::string_view(buffer, 4));
|
EXPECT_EQ("xABx", fmt::string_view(buffer, 4));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if FMT_USE_USER_DEFINED_LITERALS
|
#if FMT_USE_USER_DEFINED_LITERALS
|
||||||
TEST(FormatTest, UDL) {
|
TEST(FormatTest, UDL) {
|
||||||
|
Loading…
Reference in New Issue
Block a user