From d948e3ada810d5265da57f1dc22f75a2d37cd2d5 Mon Sep 17 00:00:00 2001 From: Daniela Engert Date: Mon, 8 Oct 2018 20:14:39 +0200 Subject: [PATCH] Adapt any string-like type to be used by {fmt} just like the standard string types already supported. The adaption is totally non-intrusive. Signed-off-by: Daniela Engert --- include/fmt/color.h | 8 +-- include/fmt/core.h | 154 ++++++++++++++++++++++++++++-------------- include/fmt/format.h | 18 ++--- include/fmt/ostream.h | 2 +- include/fmt/printf.h | 8 +-- test/core-test.cc | 66 ++++++++++++++++++ 6 files changed, 184 insertions(+), 72 deletions(-) diff --git a/include/fmt/color.h b/include/fmt/color.h index f5826bde..68c0f16a 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -270,7 +270,7 @@ inline void reset_color(FILE *stream) FMT_NOEXCEPT { template < typename String, - typename Char = typename internal::format_string_traits::char_type> + typename Char = typename internal::has_to_string_view::char_type> void vprint_rgb(rgb fd, const String &format, basic_format_args::type> args) { internal::fputs(internal::make_foreground_color(fd), stdout); @@ -280,7 +280,7 @@ void vprint_rgb(rgb fd, const String &format, template < typename String, - typename Char = typename internal::format_string_traits::char_type> + typename Char = typename internal::has_to_string_view::char_type> void vprint_rgb(rgb fd, rgb bg, const String &format, basic_format_args::type> args) { internal::fputs(internal::make_foreground_color(fd), stdout); @@ -299,7 +299,7 @@ template typename std::enable_if::value>::type print(rgb fd, const String &format_str, const Args & ... args) { internal::check_format_string(format_str); - typedef typename internal::format_string_traits::char_type char_t; + typedef typename internal::has_to_string_view::char_type char_t; typedef typename buffer_context::type context_t; format_arg_store as{args...}; vprint_rgb(fd, format_str, basic_format_args(as)); @@ -316,7 +316,7 @@ template typename std::enable_if::value>::type print(rgb fd, rgb bg, const String &format_str, const Args & ... args) { internal::check_format_string(format_str); - typedef typename internal::format_string_traits::char_type char_t; + typedef typename internal::has_to_string_view::char_type char_t; typedef typename buffer_context::type context_t; format_arg_store as{args...}; vprint_rgb(fd, bg, format_str, basic_format_args(as)); diff --git a/include/fmt/core.h b/include/fmt/core.h index 2c83f045..88a46174 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -435,10 +435,31 @@ class basic_string_view { typedef basic_string_view string_view; typedef basic_string_view wstring_view; +template +inline basic_string_view + to_string_view(const std::basic_string &s) { return s; } + +template +inline basic_string_view to_string_view(const Char *s) { return s; } + +#ifdef FMT_STRING_VIEW +template +inline basic_string_view + to_string_view(FMT_STRING_VIEW s) { return s; } +#endif + // A base class for compile-time strings. It is defined in the fmt namespace to // make formatting functions visible via ADL, e.g. format(fmt("{}"), 42). struct compile_string {}; +template +struct is_compile_string : std::is_base_of {}; + +template +inline typename std::enable_if< + is_compile_string::value, string_view>::type + to_string_view(const S &s) { return {s.data(), s.size() - 1}; } + template class basic_format_arg; @@ -465,51 +486,28 @@ struct convert_to_int: std::integral_constant< namespace internal { -// If S is a format string type, format_string_traints::char_type gives its -// character type. -template -class format_string_traits { - // Use emptyness as a way to detect if format_string_traits is - // specialized because other methods are broken on MSVC2013 or gcc 4.4. - int dummy; +template +struct get_char_type { + typedef void char_type; }; template -struct format_string_traits_base { typedef Char char_type; }; +struct get_char_type> { + typedef Char char_type; +}; -template -struct format_string_traits : format_string_traits_base {}; +using fmt::v5::to_string_view; -template -struct format_string_traits : format_string_traits_base {}; - -template -struct format_string_traits : format_string_traits_base {}; - -template -struct format_string_traits : format_string_traits_base {}; - -template -struct format_string_traits> : - format_string_traits_base {}; - -#ifdef FMT_STRING_VIEW -template -struct format_string_traits> : - format_string_traits_base {}; -#endif +void to_string_view(...); template -struct format_string_traits< - S, typename std::enable_if, S>::value>::type> : - format_string_traits_base {}; +struct has_to_string_view { + typedef typename get_char_type()))>::char_type char_type; + static const bool value = !std::is_same()))>::value; +}; template -struct is_string : std::is_empty> {}; - -template -struct is_compile_string : std::is_base_of {}; +struct is_string : internal::has_to_string_view {}; template struct named_arg_base; @@ -714,7 +712,8 @@ inline typename std::enable_if< template inline typename std::enable_if< - internal::is_constructible, T>::value, + internal::is_constructible, T>::value && + !internal::has_to_string_view::value, init, string_type>>::type make_value(const T &val) { return basic_string_view(val); } @@ -722,7 +721,8 @@ template inline typename std::enable_if< !convert_to_int::value && !std::is_convertible>::value && - !internal::is_constructible, T>::value, + !internal::is_constructible, T>::value && + !internal::has_to_string_view::value, // Implicit conversion to std::string is not handled here because it's // unsafe: https://github.com/fmtlib/fmt/issues/729 init>::type @@ -736,6 +736,19 @@ init return static_cast(&val); } +template +FMT_CONSTEXPR11 typename std::enable_if< + internal::has_to_string_view::value, + init, string_type>>::type + // handle adapted strings + make_value(const S &val) { + static_assert(std::is_same< + typename C::char_type, + typename internal::has_to_string_view::char_type>::value, + "mismatch between char-types of context and argument"); + return to_string_view(val); +} + // Maximum number of arguments with packed types. enum { max_packed_args = 15 }; @@ -1256,15 +1269,21 @@ struct wformat_args : basic_format_args { #if FMT_USE_ALIAS_TEMPLATES /** String's character type. */ template -using char_t = typename internal::format_string_traits::char_type; +using char_t = typename std::enable_if::value, + typename internal::has_to_string_view::char_type>::type; #define FMT_CHAR(S) char_t template using enable_if_string_t = - typename std::enable_if::value, T>::type; +typename std::enable_if::value, T>::type; #define FMT_ENABLE_IF_STRING(S, T) enable_if_string_t #else -#define FMT_CHAR(S) typename internal::format_string_traits::char_type +template +struct char_t : std::enable_if< + internal::has_to_string_view::value, + typename internal::has_to_string_view::char_type> {}; +#define FMT_CHAR(S) typename char_t::type + #define FMT_ENABLE_IF_STRING(S, T) \ typename std::enable_if::value, T>::type #endif @@ -1316,17 +1335,48 @@ struct checked_args : format_arg_store< basic_format_args operator*() const { return *this; } }; -template -inline basic_string_view to_string_view(const S &s) { - return basic_string_view(s); -} - template std::basic_string vformat( basic_string_view format_str, basic_format_args::type> args); } +/** + \rst + The function ``to_string_view`` adapts non-intrusively any kind of string or + string-like type if the user provides a (possibly templated) overload of + ``to_string_view`` which takes an instance of the string class + ``StringType`` and returns a ``fmt::basic_string_view``. + The conversion function must live in the very same namespace as + ``StringType`` to be picked up by ADL. Non-templated string types + like f.e. QString must return a ``basic_string_view`` with a fixed matching + char type. + + **Example**:: + + namespace ofStringType { + + template + inline basic_string_view to_string_view(const StringType &S) { + return { S.data(), S.length() }; + } + + or + + inline basic_string_view to_string_view(const WideStringType &S) { + return { S.data(), S.length() }; + } + + } + + std::string message = fmt::format(StringType("The answer is {}"), 42); + \endrst + */ +template +inline basic_string_view to_string_view(basic_string_view s) { + return s; +} + /** \rst Returns a named argument to be used in a formatting function. @@ -1374,7 +1424,7 @@ typename std::enable_if< const S &format_str, basic_format_args::type> args) { internal::container_buffer buf(internal::get_container(out)); - vformat_to(buf, internal::to_string_view(format_str), args); + vformat_to(buf, to_string_view(format_str), args); return out; } @@ -1385,14 +1435,14 @@ inline typename std::enable_if< format_to(std::back_insert_iterator out, const S &format_str, const Args &... args) { internal::checked_args ca(format_str, args...); - return vformat_to(out, internal::to_string_view(format_str), *ca); + return vformat_to(out, to_string_view(format_str), *ca); } template inline std::basic_string vformat( const S &format_str, basic_format_args::type> args) { - return internal::vformat(internal::to_string_view(format_str), args); + return internal::vformat(to_string_view(format_str), args); } /** @@ -1409,7 +1459,7 @@ template inline std::basic_string format( const S &format_str, const Args &... args) { return internal::vformat( - internal::to_string_view(format_str), + to_string_view(format_str), *internal::checked_args(format_str, args...)); } @@ -1430,7 +1480,7 @@ FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); template inline FMT_ENABLE_IF_STRING(S, void) print(std::FILE *f, const S &format_str, const Args &... args) { - vprint(f, internal::to_string_view(format_str), + vprint(f, to_string_view(format_str), internal::checked_args(format_str, args...)); } @@ -1449,7 +1499,7 @@ FMT_API void vprint(wstring_view format_str, wformat_args args); template inline FMT_ENABLE_IF_STRING(S, void) print(const S &format_str, const Args &... args) { - vprint(internal::to_string_view(format_str), + vprint(to_string_view(format_str), internal::checked_args(format_str, args...)); } FMT_END_NAMESPACE diff --git a/include/fmt/format.h b/include/fmt/format.h index 914da868..a6cb278b 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1190,11 +1190,6 @@ inline typename std::enable_if::type template void sprintf_format(Double, internal::buffer &, core_format_specs); -template -struct format_string_traits< - S, typename std::enable_if::value>::type>: - format_string_traits_base {}; - template FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler &&handler) { switch (spec) { @@ -3345,20 +3340,20 @@ inline typename buffer_context::type::iterator vformat_to( basic_format_args::type> args) { typedef back_insert_range > range; return vformat_to>( - buf, basic_string_view(format_str), args); + buf, to_string_view(format_str), args); } template < typename String, typename... Args, std::size_t SIZE = inline_buffer_size, - typename Char = typename internal::format_string_traits::char_type> + typename Char = typename internal::has_to_string_view::char_type> inline typename buffer_context::type::iterator format_to( basic_memory_buffer &buf, const String &format_str, const Args &... args) { internal::check_format_string(format_str); typedef typename buffer_context::type context; format_arg_store as{args...}; - return vformat_to(buf, basic_string_view(format_str), + return vformat_to(buf, to_string_view(format_str), basic_format_args(as)); } @@ -3378,7 +3373,8 @@ inline OutputIt vformat_to( OutputIt out, const String &format_str, typename format_args_t::type args) { typedef output_range range; - return vformat_to>(range(out), format_str, args); + return vformat_to>(range(out), + to_string_view(format_str), args); } /** @@ -3398,7 +3394,7 @@ inline FMT_ENABLE_IF_STRING(S, OutputIt) internal::check_format_string(format_str); typedef typename format_context_t::type context; format_arg_store as{args...}; - return vformat_to(out, basic_string_view(format_str), + return vformat_to(out, to_string_view(format_str), basic_format_args(as)); } @@ -3448,7 +3444,7 @@ inline FMT_ENABLE_IF_STRING(S, format_to_n_result) internal::check_format_string(format_str); typedef FMT_CHAR(S) Char; format_arg_store, Args...> as(args...); - return vformat_to_n(out, n, internal::to_string_view(format_str), + return vformat_to_n(out, n, to_string_view(format_str), format_to_n_args(as)); } diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 45e57b5c..85430779 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -146,7 +146,7 @@ inline typename std::enable_if::value>::type print(std::basic_ostream &os, const S &format_str, const Args & ... args) { internal::checked_args ca(format_str, args...); - vprint(os, internal::to_string_view(format_str), *ca); + vprint(os, to_string_view(format_str), *ca); } FMT_END_NAMESPACE diff --git a/include/fmt/printf.h b/include/fmt/printf.h index d58fe9f3..e12a4f6b 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -606,7 +606,7 @@ inline FMT_ENABLE_IF_STRING(S, std::basic_string) typedef internal::basic_buffer buffer; typedef typename printf_context::type context; format_arg_store as{ args... }; - return vsprintf(internal::to_string_view(format_str), + return vsprintf(to_string_view(format_str), basic_format_args(as)); } @@ -637,7 +637,7 @@ inline FMT_ENABLE_IF_STRING(S, int) typedef internal::basic_buffer buffer; typedef typename printf_context::type context; format_arg_store as{ args... }; - return vfprintf(f, internal::to_string_view(format_str), + return vfprintf(f, to_string_view(format_str), basic_format_args(as)); } @@ -664,7 +664,7 @@ inline FMT_ENABLE_IF_STRING(S, int) typedef internal::basic_buffer buffer; typedef typename printf_context::type context; format_arg_store as{ args... }; - return vprintf(internal::to_string_view(format_str), + return vprintf(to_string_view(format_str), basic_format_args(as)); } @@ -696,7 +696,7 @@ inline FMT_ENABLE_IF_STRING(S, int) typedef internal::basic_buffer buffer; typedef typename printf_context::type context; format_arg_store as{ args... }; - return vfprintf(os, internal::to_string_view(format_str), + return vfprintf(os, to_string_view(format_str), basic_format_args(as)); } FMT_END_NAMESPACE diff --git a/test/core-test.cc b/test/core-test.cc index cb983560..12231b85 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include "test-assert.h" @@ -451,6 +452,42 @@ TEST(CoreTest, IsEnumConvertibleToInt) { EXPECT_TRUE((fmt::convert_to_int::value)); } +namespace MyNamespace { +template +struct String { + String(const Char * S) : S_(S) {} + const Char * data() const FMT_NOEXCEPT { return S_.data(); } + std::size_t length() const FMT_NOEXCEPT { return S_.size(); } + operator const Char*() const { return S_.c_str(); } +private: + std::basic_string S_; +}; + +template +inline fmt::basic_string_view to_string_view(const String &S) FMT_NOEXCEPT { + return { S.data(), S.length() }; +} + +struct NoString {}; +} + +namespace FakeQt { +struct QString { + QString(const wchar_t * S) : S_(std::make_shared(S)) {} + const wchar_t * utf16() const FMT_NOEXCEPT { return S_->data(); } + int size() const FMT_NOEXCEPT { return static_cast(S_->size()); } +#ifdef FMT_STRING_VIEW + operator FMT_STRING_VIEW() const FMT_NOEXCEPT { return *S_; } +#endif +private: + std::shared_ptr S_; +}; + +inline fmt::basic_string_view to_string_view(const QString &S) FMT_NOEXCEPT { + return { reinterpret_cast(S.utf16()), static_cast(S.size()) }; +} +} + template class IsStringTest : public testing::Test {}; @@ -473,6 +510,9 @@ TYPED_TEST(IsStringTest, IsString) { #ifdef FMT_STRING_VIEW EXPECT_TRUE((fmt::internal::is_string>::value)); #endif + EXPECT_TRUE((fmt::internal::is_string>::value)); + EXPECT_FALSE((fmt::internal::is_string::value)); + EXPECT_TRUE((fmt::internal::is_string::value)); } TEST(CoreTest, Format) { @@ -482,3 +522,29 @@ TEST(CoreTest, Format) { #endif EXPECT_EQ(fmt::format("{}", 42), "42"); } + +TEST(CoreTest, ToStringViewForeignStrings) { + using namespace MyNamespace; + using namespace FakeQt; + EXPECT_EQ(to_string_view(String("42")), "42"); + EXPECT_EQ(to_string_view(String(L"42")), L"42"); + EXPECT_EQ(to_string_view(QString(L"42")), L"42"); + fmt::internal::type type = fmt::internal::get_type>::value; + EXPECT_EQ(type, fmt::internal::string_type); + type = fmt::internal::get_type>::value; + EXPECT_EQ(type, fmt::internal::string_type); + type = fmt::internal::get_type::value; + EXPECT_EQ(type, fmt::internal::string_type); + // does not compile: only wide format contexts are compatible with QString! + // type = fmt::internal::get_type::value; +} + +TEST(CoreTest, FormatForeignStrings) { + using namespace MyNamespace; + using namespace FakeQt; + EXPECT_EQ(fmt::format(String("{}"), 42), "42"); + EXPECT_EQ(fmt::format(String(L"{}"), 42), L"42"); + EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42"); + EXPECT_EQ(fmt::format(QString(L"{}"), String(L"42")), L"42"); + EXPECT_EQ(fmt::format(String(L"{}"), QString(L"42")), L"42"); +}