From 5f1ceebc7f69be5c29a8dd94f798110b1f727b01 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 21 Jan 2019 07:11:49 -0800 Subject: [PATCH] Make formatter override ostream<< for templates (#952) --- include/fmt/core.h | 68 ++++++++++++++++++++++++++++++------------- include/fmt/format.h | 3 +- include/fmt/ostream.h | 24 +++++++-------- test/format-test.cc | 4 +++ test/ostream-test.cc | 24 +++++++++++++++ 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 3bbbcce4..b607fb0a 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -331,16 +331,25 @@ struct error_handler { FMT_API void on_error(const char* message); }; -template struct no_formatter_error : std::false_type {}; -} // namespace internal - // GCC 4.6.x cannot expand `T...`. #if FMT_GCC_VERSION && FMT_GCC_VERSION < 407 template struct is_constructible : std::false_type {}; + +typedef char yes[1]; +typedef char no[2]; + +template struct is_default_constructible { + template static yes& test(int (*)[sizeof(new U)]); + template static no& test(...); + enum { value = sizeof(test(FMT_NULL)) == sizeof(yes) }; +}; #else template struct is_constructible : std::is_constructible {}; +template +struct is_default_constructible : std::is_default_constructible {}; #endif +} // namespace internal /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -492,22 +501,12 @@ FMT_CONSTEXPR basic_string_view to_string_view( } template class basic_format_arg; - template class basic_format_args; // A formatter for objects of type T. template struct formatter { - static_assert( - internal::no_formatter_error::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::iterator parse(ParseContext&); - template - auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()); + formatter() = delete; }; template @@ -517,6 +516,16 @@ struct convert_to_int namespace internal { +template struct no_formatter_error : std::false_type {}; + +template +struct fallback_formatter { + static_assert( + no_formatter_error::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 { typedef void char_type; }; @@ -623,9 +632,29 @@ template class value { } value(const void* val) { pointer = val; } - template explicit value(const T& val) { + template ::type>::value, + int>::type = 0> + explicit value(const T& val) { custom.value = &val; - custom.format = &format_custom_arg; + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + typedef typename Context::template formatter_type::type formatter; + custom.format = &format_custom_arg; + } + + template ::type>::value, + int>::type = 0> + explicit value(const T& val) { + custom.value = &val; + custom.format = + &format_custom_arg>; } const named_arg_base& as_named_arg() { @@ -634,12 +663,9 @@ template class value { private: // Formats an argument of a custom type, such as a user-defined class. - template + template static void format_custom_arg(const void* arg, Context& ctx) { - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter` for `format` and - // `printf_formatter` for `printf`. - typename Context::template formatter_type::type f; + Formatter f; auto&& parse_ctx = ctx.parse_context(); parse_ctx.advance_to(f.parse(parse_ctx)); ctx.advance_to(f.format(*static_cast(arg), ctx)); diff --git a/include/fmt/format.h b/include/fmt/format.h index e04391e4..3b84976a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1702,8 +1702,7 @@ class specs_handler : public specs_setter { FMT_CONSTEXPR format_arg get_arg(auto_id) { return context_.next_arg(); } - template - FMT_CONSTEXPR format_arg get_arg(Id arg_id) { + template FMT_CONSTEXPR format_arg get_arg(Id arg_id) { context_.parse_context().check_arg_id(arg_id); return context_.arg(arg_id); } diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 8ae1e9a4..d136a0e2 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -93,22 +93,12 @@ void format_value(basic_buffer& buffer, const T& value) { output << value; 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 struct convert_to_int { - static const bool value = convert_to_int::value && - !internal::is_streamable::value; -}; // Formats an object of type T that has an overloaded ostream operator<<. template -struct formatter::value && - !internal::format_type::type, - T>::value>::type> +struct fallback_formatter< + T, Char, + typename std::enable_if::value>::type> : formatter, Char> { template auto format(const T& value, Context& ctx) -> decltype(ctx.out()) { @@ -118,6 +108,14 @@ struct formatter, 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 struct convert_to_int { + static const bool value = convert_to_int::value && + !internal::is_streamable::value; +}; template inline void vprint( diff --git a/test/format-test.cc b/test/format-test.cc index 8c1977c8..14022468 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2144,6 +2144,10 @@ struct test_context { typedef char char_type; typedef fmt::basic_format_arg format_arg; + template struct formatter_type { + typedef fmt::formatter type; + }; + FMT_CONSTEXPR fmt::basic_format_arg next_arg() { return fmt::internal::make_arg(11); } diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 860a6211..e27369ba 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -178,6 +178,29 @@ template Output& operator<<(Output& out, ABC) { } } // namespace fmt_test +template +struct TestTemplate {}; + +template +std::ostream& operator<<(std::ostream& os, TestTemplate) { + return os << 1; +} + +namespace fmt { +template +struct formatter> : formatter { + template + typename FormatContext::iterator format(TestTemplate, FormatContext& ctx) { + return formatter::format(2, ctx); + } +}; +} + +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 407 +TEST(OStreamTest, Template) { + EXPECT_EQ("2", fmt::format("{}", TestTemplate())); +} + TEST(FormatTest, FormatToN) { char buffer[4]; buffer[3] = 'x'; @@ -190,6 +213,7 @@ TEST(FormatTest, FormatToN) { EXPECT_EQ(buffer + 3, result.out); EXPECT_EQ("xABx", fmt::string_view(buffer, 4)); } +#endif #if FMT_USE_USER_DEFINED_LITERALS TEST(FormatTest, UDL) {