From 2c81c851b2ce79ea151ca917d8adae65fc0789c1 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 | 142 +++++++++++++++++++++++++++--------------- include/fmt/format.h | 18 +++--- include/fmt/ostream.h | 2 +- include/fmt/printf.h | 8 +-- test/core-test.cc | 79 ++++++++++++++++++++++- 6 files changed, 184 insertions(+), 73 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..3c522403 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -435,10 +435,30 @@ 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::value, string_view>::type + to_string_view(const S &s) { return {s.data(), s.size() - 1}; } + template class basic_format_arg; @@ -465,51 +485,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 +711,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 +720,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 +735,19 @@ init return static_cast(&val); } +template +FMT_CONSTEXPR11 typename std::enable_if< + internal::has_to_string_view::value, + init, string_type>>::type + make_value(const S &val) { + // Handle adapted strings. + 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,7 +1268,8 @@ 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 @@ -1264,7 +1277,12 @@ using enable_if_string_t = 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 +1334,39 @@ 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 my_ns { + inline string_view to_string_view(const my_string &s) { + return { s.data(), s.length() }; + } + } + + std::string message = fmt::format(my_string("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 +1414,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 +1425,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 +1449,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 +1470,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 +1489,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..245c1768 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,47 @@ TEST(CoreTest, IsEnumConvertibleToInt) { EXPECT_TRUE((fmt::convert_to_int::value)); } +namespace my_ns { +template +class my_string { + public: + my_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 my_string &s) FMT_NOEXCEPT { + return { s.data(), s.length() }; +} + +struct non_string {}; +} + +namespace FakeQt { +class QString { + public: + 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 {}; @@ -468,11 +510,16 @@ TYPED_TEST(IsStringTest, IsString) { EXPECT_TRUE((fmt::internal::is_string::value)); EXPECT_TRUE((fmt::internal::is_string::value)); EXPECT_TRUE((fmt::internal::is_string>::value)); - EXPECT_TRUE((fmt::internal::is_string>::value)); - EXPECT_TRUE((fmt::internal::is_string>::value)); + EXPECT_TRUE( + (fmt::internal::is_string>::value)); + EXPECT_TRUE( + (fmt::internal::is_string>::value)); #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 +529,31 @@ TEST(CoreTest, Format) { #endif EXPECT_EQ(fmt::format("{}", 42), "42"); } + +TEST(CoreTest, ToStringViewForeignStrings) { + using namespace my_ns; + using namespace FakeQt; + EXPECT_EQ(to_string_view(my_string("42")), "42"); + EXPECT_EQ(to_string_view(my_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 my_ns; + using namespace FakeQt; + EXPECT_EQ(fmt::format(my_string("{}"), 42), "42"); + EXPECT_EQ(fmt::format(my_string(L"{}"), 42), L"42"); + EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42"); + EXPECT_EQ(fmt::format(QString(L"{}"), my_string(L"42")), L"42"); + EXPECT_EQ(fmt::format(my_string(L"{}"), QString(L"42")), L"42"); +}