diff --git a/format.h b/format.h index 2acd330e29cce..8b2fc88c3c7cc 100644 --- a/format.h +++ b/format.h @@ -937,28 +937,50 @@ typedef char No[2]; // These are non-members to workaround an overload resolution bug in bcc32. Yes &convert(fmt::ULongLong); +Yes &convert(std::ostream &); No &convert(...); template -class IsConvertibleToInt { - protected: - static const T &get(); +T &get(); - public: - enum { value = (sizeof(convert(get())) == sizeof(Yes)) }; +struct DummyStream : std::ostream { + // Hide all operator<< overloads from std::ostream. + void operator<<(Null<>); +}; + +No &operator<<(std::ostream &, int); + +template +struct ConvertToIntImpl { + enum { value = false }; +}; + +template +struct ConvertToIntImpl { + // Convert to int only if T doesn't have an overloaded operator<<. + enum { + value = sizeof(convert(get() << get())) == sizeof(No) + }; +}; + +template +struct ConvertToInt { + enum { is_convertible_to_int = sizeof(convert(get())) == sizeof(Yes) }; + enum { value = ConvertToIntImpl::value }; }; -#define FMT_CONVERTIBLE_TO_INT(Type) \ +#define FMT_DISABLE_CONVERSION_TO_INT(Type) \ template <> \ - class IsConvertibleToInt { \ - public: \ - enum { value = 1 }; \ - } + struct ConvertToInt { enum { value = 0 }; } // Silence warnings about convering float to int. -FMT_CONVERTIBLE_TO_INT(float); -FMT_CONVERTIBLE_TO_INT(double); -FMT_CONVERTIBLE_TO_INT(long double); +FMT_DISABLE_CONVERSION_TO_INT(float); +FMT_DISABLE_CONVERSION_TO_INT(double); +FMT_DISABLE_CONVERSION_TO_INT(long double); + +// Disable conversion from integral types to avoid ambiguity. +FMT_DISABLE_CONVERSION_TO_INT(unsigned); +FMT_DISABLE_CONVERSION_TO_INT(long); template struct EnableIf {}; @@ -1113,20 +1135,20 @@ class MakeValue : public Arg { template MakeValue(const T &value, typename EnableIf::value>::value, int>::type = 0) { + ConvertToInt::value>::value, int>::type = 0) { custom.value = &value; custom.format = &format_custom_arg; } template MakeValue(const T &value, - typename EnableIf::value, int>::type = 0) { + typename EnableIf::value, int>::type = 0) { int_value = value; } template static uint64_t type(const T &) { - return IsConvertibleToInt::value ? Arg::INT : Arg::CUSTOM; + return ConvertToInt::value ? Arg::INT : Arg::CUSTOM; } // Additional template param `Char_` is needed here because make_type always diff --git a/test/format-test.cc b/test/format-test.cc index f2df51eabe0a1..39195c83a0d41 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1637,3 +1637,15 @@ TEST(LiteralsTest, NamedArg) { udl_a_w); } #endif // FMT_USE_USER_DEFINED_LITERALS + +enum TestEnum {}; +std::ostream &operator<<(std::ostream &os, TestEnum) { + return os << "TestEnum"; +} + +enum TestEnum2 { A }; + +TEST(FormatTest, Enum) { + EXPECT_EQ("TestEnum", fmt::format("{}", TestEnum())); + EXPECT_EQ("0", fmt::format("{}", A)); +} diff --git a/test/util-test.cc b/test/util-test.cc index b124e05acddc4..e3b180393dae5 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -870,9 +870,16 @@ TEST(UtilTest, ReportWindowsError) { #endif // _WIN32 -TEST(UtilTest, IsConvertibleToInt) { - EXPECT_TRUE(fmt::internal::IsConvertibleToInt::value); - EXPECT_FALSE(fmt::internal::IsConvertibleToInt::value); +enum TestEnum2 {}; +enum TestEnum3 {}; +std::ostream &operator<<(std::ostream &, TestEnum3); + +TEST(UtilTest, ConvertToInt) { + EXPECT_TRUE(fmt::internal::ConvertToInt::is_convertible_to_int); + EXPECT_FALSE( + fmt::internal::ConvertToInt::is_convertible_to_int); + EXPECT_TRUE(fmt::internal::ConvertToInt::value); + EXPECT_FALSE(fmt::internal::ConvertToInt::value); } #if FMT_USE_ENUM_BASE