diff --git a/include/fmt/core.h b/include/fmt/core.h index 49776c18..d215bfee 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -423,11 +423,24 @@ namespace internal { void to_string_view(...); using fmt::v6::to_string_view; +template struct priority_tag : priority_tag {}; + +template <> struct priority_tag<0> {}; + +// This is required to handle types privately inheriting from std::string, +// which triggers a compilation failure. +template +auto is_string_impl(priority_tag<1>) + -> decltype(to_string_view(std::declval())); + +template void is_string_impl(priority_tag<0>); + // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in // enable_if and MSVC 2015 fails to compile it as an alias template. -template -struct is_string : std::is_class()))> { +template struct is_string { + static constexpr bool value = std::is_class(std::declval>()))>::value; }; template struct char_t_impl {}; @@ -827,7 +840,8 @@ template struct arg_mapper { FMT_CONSTEXPR const char_type* map(char_type* val) { return val; } FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; } template ::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { + FMT_CONSTEXPR basic_string_view map_impl(const T& val, + priority_tag<4>) { static_assert(std::is_same>::value, "mixing character types is disallowed"); return to_string_view(val); @@ -836,7 +850,8 @@ template struct arg_mapper { FMT_ENABLE_IF( std::is_constructible, T>::value && !is_string::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { + FMT_CONSTEXPR auto map_impl(const T& val, priority_tag<3>) + -> decltype(basic_string_view(val)) { return basic_string_view(val); } template < @@ -845,9 +860,31 @@ template struct arg_mapper { std::is_constructible, T>::value && !std::is_constructible, T>::value && !is_string::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { + FMT_CONSTEXPR auto map_impl(const T& val, priority_tag<2>) + -> decltype(std_string_view(val), + basic_string_view{}) { return std_string_view(val); } + template ::value && + !has_formatter::value && + !has_fallback_formatter::value)> + FMT_CONSTEXPR auto map_impl(const T& val, priority_tag<1>) -> decltype( + map(static_cast::type>(val))) { + return map(static_cast::type>(val)); + } + template ::value && !is_char::value && + (has_formatter::value || + has_fallback_formatter::value))> + FMT_CONSTEXPR const T& map_impl(const T& val, priority_tag<0>) { + return val; + } + template + FMT_CONSTEXPR auto map(const T& val) + -> decltype(std::declval().map_impl(val, priority_tag<4>{})) { + return map_impl(val, priority_tag<4>{}); + } FMT_CONSTEXPR const char* map(const signed char* val) { static_assert(std::is_same::value, "invalid string type"); return reinterpret_cast(val); @@ -869,22 +906,6 @@ template struct arg_mapper { return 0; } - template ::value && - !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR auto map(const T& val) -> decltype( - map(static_cast::type>(val))) { - return map(static_cast::type>(val)); - } - template ::value && !is_char::value && - (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR const T& map(const T& val) { - return val; - } - template FMT_CONSTEXPR const named_arg_base& map( const named_arg& val) { diff --git a/test/custom-formatter-test.cc b/test/custom-formatter-test.cc index 36007cae..5d280111 100644 --- a/test/custom-formatter-test.cc +++ b/test/custom-formatter-test.cc @@ -53,4 +53,29 @@ std::string custom_format(const char* format_str, const Args&... args) { TEST(CustomFormatterTest, Format) { EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001)); } + +template class string_wrapper : std::string { + public: + string_wrapper(std::string const& s) : std::string(s) {} + + std::string const& string() const { return *this; } +}; + +FMT_BEGIN_NAMESPACE +template +struct formatter, char> + : formatter, char> { + template + auto format(string_wrapper const& str, FormatContext& ctx) + -> decltype(ctx.out()) { + return formatter, char>::format(str.string(), + ctx); + } +}; +FMT_END_NAMESPACE + +TEST(CustomFormatterTest, FormatStringWrapperPrivateInheritance) { + // static_assert(fmt::internal::is_string>::value); + EXPECT_EQ("foo", fmt::format("{}", string_wrapper("foo"))); +} #endif diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 265f9acd..bae612de 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -120,6 +120,31 @@ TEST(RangesTest, PathLike) { #endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG > // 201402L && _MSC_VER >= 1910) +template class string_wrapper : std::string { + public: + string_wrapper(std::string const& s) : std::string(s) {} + + std::string const& string() const { return *this; } +}; + +FMT_BEGIN_NAMESPACE +template +struct formatter, char> + : formatter, char> { + template + auto format(string_wrapper const& str, FormatContext& ctx) + -> decltype(ctx.out()) { + return formatter, char>::format(str.string(), + ctx); + } +}; +FMT_END_NAMESPACE + +TEST(RangesTest, FormatStringWrapperPrivateInheritance) { + std::vector> strs = {{"foo"}, {"bar"}}; + EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", "))); +} + #ifdef FMT_USE_STRING_VIEW struct string_like { const char* begin();