Fix is_string<> to allow string like class

The previous implementation did not compile while formatting a type
with private inheritance of std::string.
This commit is contained in:
Alexandre Bossard 2019-10-31 13:50:04 +01:00
parent 791294d17b
commit 8b52d33b69
No known key found for this signature in database
GPG Key ID: DF599C202481B00F
3 changed files with 92 additions and 21 deletions

View File

@ -423,11 +423,24 @@ namespace internal {
void to_string_view(...); void to_string_view(...);
using fmt::v6::to_string_view; using fmt::v6::to_string_view;
template <std::size_t N> struct priority_tag : priority_tag<N - 1> {};
template <> struct priority_tag<0> {};
// This is required to handle types privately inheriting from std::string,
// which triggers a compilation failure.
template <typename S>
auto is_string_impl(priority_tag<1>)
-> decltype(to_string_view(std::declval<S>()));
template <typename S> void is_string_impl(priority_tag<0>);
// Specifies whether S is a string type convertible to fmt::basic_string_view. // 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 // 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. // enable_if and MSVC 2015 fails to compile it as an alias template.
template <typename S> template <typename S> struct is_string {
struct is_string : std::is_class<decltype(to_string_view(std::declval<S>()))> { static constexpr bool value = std::is_class<decltype(
is_string_impl<S>(std::declval<priority_tag<1>>()))>::value;
}; };
template <typename S, typename = void> struct char_t_impl {}; template <typename S, typename = void> struct char_t_impl {};
@ -827,7 +840,8 @@ template <typename Context> struct arg_mapper {
FMT_CONSTEXPR const char_type* map(char_type* val) { return val; } FMT_CONSTEXPR const char_type* map(char_type* val) { return val; }
FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; } FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; }
template <typename T, FMT_ENABLE_IF(is_string<T>::value)> template <typename T, FMT_ENABLE_IF(is_string<T>::value)>
FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) { FMT_CONSTEXPR basic_string_view<char_type> map_impl(const T& val,
priority_tag<4>) {
static_assert(std::is_same<char_type, char_t<T>>::value, static_assert(std::is_same<char_type, char_t<T>>::value,
"mixing character types is disallowed"); "mixing character types is disallowed");
return to_string_view(val); return to_string_view(val);
@ -836,7 +850,8 @@ template <typename Context> struct arg_mapper {
FMT_ENABLE_IF( FMT_ENABLE_IF(
std::is_constructible<basic_string_view<char_type>, T>::value && std::is_constructible<basic_string_view<char_type>, T>::value &&
!is_string<T>::value)> !is_string<T>::value)>
FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) { FMT_CONSTEXPR auto map_impl(const T& val, priority_tag<3>)
-> decltype(basic_string_view<char_type>(val)) {
return basic_string_view<char_type>(val); return basic_string_view<char_type>(val);
} }
template < template <
@ -845,9 +860,31 @@ template <typename Context> struct arg_mapper {
std::is_constructible<std_string_view<char_type>, T>::value && std::is_constructible<std_string_view<char_type>, T>::value &&
!std::is_constructible<basic_string_view<char_type>, T>::value && !std::is_constructible<basic_string_view<char_type>, T>::value &&
!is_string<T>::value)> !is_string<T>::value)>
FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) { FMT_CONSTEXPR auto map_impl(const T& val, priority_tag<2>)
-> decltype(std_string_view<char_type>(val),
basic_string_view<char_type>{}) {
return std_string_view<char_type>(val); return std_string_view<char_type>(val);
} }
template <typename T,
FMT_ENABLE_IF(std::is_enum<T>::value &&
!has_formatter<T, Context>::value &&
!has_fallback_formatter<T, Context>::value)>
FMT_CONSTEXPR auto map_impl(const T& val, priority_tag<1>) -> decltype(
map(static_cast<typename std::underlying_type<T>::type>(val))) {
return map(static_cast<typename std::underlying_type<T>::type>(val));
}
template <typename T,
FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
(has_formatter<T, Context>::value ||
has_fallback_formatter<T, Context>::value))>
FMT_CONSTEXPR const T& map_impl(const T& val, priority_tag<0>) {
return val;
}
template <typename T>
FMT_CONSTEXPR auto map(const T& val)
-> decltype(std::declval<arg_mapper>().map_impl(val, priority_tag<4>{})) {
return map_impl(val, priority_tag<4>{});
}
FMT_CONSTEXPR const char* map(const signed char* val) { FMT_CONSTEXPR const char* map(const signed char* val) {
static_assert(std::is_same<char_type, char>::value, "invalid string type"); static_assert(std::is_same<char_type, char>::value, "invalid string type");
return reinterpret_cast<const char*>(val); return reinterpret_cast<const char*>(val);
@ -869,22 +906,6 @@ template <typename Context> struct arg_mapper {
return 0; return 0;
} }
template <typename T,
FMT_ENABLE_IF(std::is_enum<T>::value &&
!has_formatter<T, Context>::value &&
!has_fallback_formatter<T, Context>::value)>
FMT_CONSTEXPR auto map(const T& val) -> decltype(
map(static_cast<typename std::underlying_type<T>::type>(val))) {
return map(static_cast<typename std::underlying_type<T>::type>(val));
}
template <typename T,
FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
(has_formatter<T, Context>::value ||
has_fallback_formatter<T, Context>::value))>
FMT_CONSTEXPR const T& map(const T& val) {
return val;
}
template <typename T> template <typename T>
FMT_CONSTEXPR const named_arg_base<char_type>& map( FMT_CONSTEXPR const named_arg_base<char_type>& map(
const named_arg<T, char_type>& val) { const named_arg<T, char_type>& val) {

View File

@ -53,4 +53,29 @@ std::string custom_format(const char* format_str, const Args&... args) {
TEST(CustomFormatterTest, Format) { TEST(CustomFormatterTest, Format) {
EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001)); EXPECT_EQ("0.00", custom_format("{:.2f}", -.00001));
} }
template <typename T> 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 <typename Tag>
struct formatter<string_wrapper<Tag>, char>
: formatter<fmt::basic_string_view<char>, char> {
template <typename FormatContext>
auto format(string_wrapper<Tag> const& str, FormatContext& ctx)
-> decltype(ctx.out()) {
return formatter<fmt::basic_string_view<char>, char>::format(str.string(),
ctx);
}
};
FMT_END_NAMESPACE
TEST(CustomFormatterTest, FormatStringWrapperPrivateInheritance) {
// static_assert(fmt::internal::is_string<string_wrapper<struct Tag>>::value);
EXPECT_EQ("foo", fmt::format("{}", string_wrapper<struct Tag>("foo")));
}
#endif #endif

View File

@ -120,6 +120,31 @@ TEST(RangesTest, PathLike) {
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG > #endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
// 201402L && _MSC_VER >= 1910) // 201402L && _MSC_VER >= 1910)
template <typename T> 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 <typename Tag>
struct formatter<string_wrapper<Tag>, char>
: formatter<fmt::basic_string_view<char>, char> {
template <typename FormatContext>
auto format(string_wrapper<Tag> const& str, FormatContext& ctx)
-> decltype(ctx.out()) {
return formatter<fmt::basic_string_view<char>, char>::format(str.string(),
ctx);
}
};
FMT_END_NAMESPACE
TEST(RangesTest, FormatStringWrapperPrivateInheritance) {
std::vector<string_wrapper<struct Tag2>> strs = {{"foo"}, {"bar"}};
EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", ")));
}
#ifdef FMT_USE_STRING_VIEW #ifdef FMT_USE_STRING_VIEW
struct string_like { struct string_like {
const char* begin(); const char* begin();