Skip to content

Commit

Permalink
Make formatter<T> override ostream<< for templates (#952)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Jan 21, 2019
1 parent 1b11b00 commit b8f3078
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 40 deletions.
68 changes: 43 additions & 25 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -331,16 +331,17 @@ struct error_handler {
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...`.
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 407
template <typename... T> struct is_constructible : std::false_type {};
template <typename... T> struct is_constructible_false : std::false_type {};
template <typename... T> struct is_constructible_true : std::true_type {};
#else
template <typename... T>
struct is_constructible : std::is_constructible<T...> {};
struct is_constructible_false : std::is_constructible<T...> {};
template <typename... T>
struct is_constructible_true : std::is_constructible<T...> {};
#endif
} // namespace internal

/**
An implementation of ``std::basic_string_view`` for pre-C++17. It provides a
Expand Down Expand Up @@ -492,22 +493,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_args;

// A formatter for objects of type T.
template <typename T, typename Char = char, typename Enable = void>
struct formatter {
static_assert(
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());
formatter() = delete;
};

template <typename T, typename Char, typename Enable = void>
Expand All @@ -517,6 +508,16 @@ struct convert_to_int

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 {
typedef void char_type;
};
Expand Down Expand Up @@ -623,9 +624,29 @@ template <typename Context> class value {
}
value(const void* val) { pointer = val; }

template <typename T> explicit value(const T& val) {
template <typename T,
typename std::enable_if<
is_constructible_true<
typename Context::template formatter_type<T>::type>{},
int>::type = 0>
explicit value(const T& 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_constructible_true<
typename Context::template formatter_type<T>::type>{},
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() {
Expand All @@ -634,12 +655,9 @@ template <typename Context> class value {

private:
// 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) {
// 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`.
typename Context::template formatter_type<T>::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<const T*>(arg), ctx));
Expand Down Expand Up @@ -759,7 +777,7 @@ inline

template <typename C, typename T, typename Char = typename C::char_type>
inline typename std::enable_if<
is_constructible<basic_string_view<Char>, T>::value &&
is_constructible_false<basic_string_view<Char>, T>::value &&
!internal::is_string<T>::value,
init<C, basic_string_view<Char>, string_type>>::type
make_value(const T& val) {
Expand All @@ -770,7 +788,7 @@ template <typename C, typename T, typename Char = typename C::char_type>
inline typename std::enable_if<
!convert_to_int<T, Char>::value && !std::is_same<T, Char>::value &&
!std::is_convertible<T, basic_string_view<Char>>::value &&
!is_constructible<basic_string_view<Char>, T>::value &&
!is_constructible_false<basic_string_view<Char>, T>::value &&
!internal::is_string<T>::value,
// Implicit conversion to std::string is not handled here because it's
// unsafe: https://github.com/fmtlib/fmt/issues/729
Expand Down
3 changes: 1 addition & 2 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }

template <typename Id>
FMT_CONSTEXPR format_arg get_arg(Id arg_id) {
template <typename Id> FMT_CONSTEXPR format_arg get_arg(Id arg_id) {
context_.parse_context().check_arg_id(arg_id);
return context_.arg(arg_id);
}
Expand Down
24 changes: 11 additions & 13 deletions include/fmt/ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,12 @@ void format_value(basic_buffer<Char>& 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 <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<<.
template <typename T, typename Char>
struct formatter<T, Char,
typename std::enable_if<
internal::is_streamable<T, Char>::value &&
!internal::format_type<typename buffer_context<Char>::type,
T>::value>::type>
struct fallback_formatter<
T, Char,
typename std::enable_if<internal::is_streamable<T, Char>::value>::type>
: formatter<basic_string_view<Char>, Char> {
template <typename Context>
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
Expand All @@ -118,6 +108,14 @@ struct formatter<T, Char,
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>
inline void vprint(
Expand Down
4 changes: 4 additions & 0 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,10 @@ struct test_context {
typedef char char_type;
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() {
return fmt::internal::make_arg<test_context>(11);
}
Expand Down
24 changes: 24 additions & 0 deletions test/ostream-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,29 @@ template <typename Output> Output& operator<<(Output& out, ABC) {
}
} // 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) {
char buffer[4];
buffer[3] = 'x';
Expand All @@ -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) {
Expand Down

0 comments on commit b8f3078

Please sign in to comment.