diff --git a/include/fmt/core.h b/include/fmt/core.h index 85503b4a..de07a31b 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1372,6 +1372,12 @@ std::basic_string vformat( basic_string_view format_str, basic_format_args::type> args); +template +OutString vformat_as( + const OutString *tag, + basic_string_view format_str, + basic_format_args::type> args); + template typename buffer_context::type::iterator vformat_to( internal::basic_buffer &buf, basic_string_view format_str, @@ -1458,6 +1464,78 @@ inline std::basic_string format( *internal::checked_args(format_str, args...)); } +namespace internal { +// the 'tag' serves 3 purposes: +// - determines the output string type +// - enables ADL to find 'to_string' +// - supplies the optional type-erased allocator argument to the string +// constructor. Its actual type is 'T::allocator_type' +template +const T *out_string_tag(void *allocator) { + return reinterpret_cast(allocator); +} + +#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 +// gcc 4.4 is lacking std::uses_allocator from C++11 +template +struct has_allocator_type { + // tests for suitable T::allocator_type + template + static std::is_convertible test(int); + template + static std::false_type test(...); + + typedef decltype(test(0)) type; + static const bool value = type::value; +}; + +# define FMT_USES_ALLOCATOR(S,A) internal::has_allocator_type::value +#else +// if std::uses_allocator is available use it because it may be specialized! +# define FMT_USES_ALLOCATOR(S,A) std::uses_allocator::value +#endif +} + +/** + \rst + Formats arguments and returns the result as a string of given type. + + **Example**:: + + #include + auto message = fmt::format_as("The answer is {}", 42); + \endrst +*/ +template +inline OutString format_as(const S &format_str, const Args &... args) { + return internal::vformat_as( + internal::out_string_tag(FMT_NULL), to_string_view(format_str), + *internal::checked_args(format_str, args...)); +} + +/** + \rst + Formats arguments and returns the result as a string of given type. + + **Example**:: + + #include + std::pmr::memory_resource *mr = ...; + std::pmr::polymorphic_allocator alloc(mr); + auto message = fmt::format_as(alloc, "Answer: {}", 42); + \endrst +*/ +template +inline typename + std::enable_if::type +format_as(const Alloc &alloc, S &format_str, const Args &... args) { + typename OutString::allocator_type out_alloc = alloc; + return internal::vformat_as( + internal::out_string_tag(&out_alloc), + to_string_view(format_str), + *internal::checked_args(format_str, args...)); +} + FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); diff --git a/include/fmt/format.h b/include/fmt/format.h index 1bb24a52..a45015da 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3229,6 +3229,72 @@ std::basic_string to_string(const basic_memory_buffer &buf) { return std::basic_string(buf.data(), buf.size()); } +namespace internal { +template +struct output_traits; + +template +struct output_traits> { + typedef std::basic_string type; + static const Alloc &to_allocator(const type *tag) FMT_NOEXCEPT { + return *reinterpret_cast(tag); + } +}; + +template +struct lazy_false { + static const bool value = false; +}; +} + +/** + \rst + The operator ``to_string`` converts a ``fmt::basic_memory_buffer`` to + any kind of string if the user provides a templated overload of + ``to_string`` which takes an instance of ``fmt::basic_memory_buffer`` + and a ``const StringType *`` and returns a ``StringType``. + The conversion function must live in the very same namespace as + ``StringType`` to be picked up by ADL. The value of ``const StringType *`` + is a type-erased reference to an opional allocator instance to be used by the + constructor of ``StringType``. + + **Example**:: + + namespace my_ns { + template + inline my_string to_string(const basic_memory_buffer &buf, + const my_string *tag) { + if (tag) { + using my_allocator_type = my_string::allocator_type; + return my_string{buf.data(), buf.length(), + *reinterpret_cast(tag)}; + } else { + return my_string{buf.data(), buf.length()}; + } + } + } + + my_ns::my_string message + = fmt::format_as("The answer is {}"), 42); + \endrst + */ +template +OutString to_string(const basic_memory_buffer &, OutString *) { + static_assert(internal::lazy_false::value, + "cannot construct OutString from basic_memory_buffer"); +} + +template +typename internal::output_traits::type +to_string(const basic_memory_buffer &buf, const OutString *tag) { + typedef internal::output_traits traits; + if (!tag) { + return OutString(buf.data(), buf.size()); + } else { + return OutString(buf.data(), buf.size(), traits::to_allocator(tag)); + } +} + template typename buffer_context::type::iterator internal::vformat_to( internal::basic_buffer &buf, basic_string_view format_str, @@ -3415,6 +3481,16 @@ inline std::basic_string internal::vformat( return fmt::to_string(buffer); } +template +inline OutString internal::vformat_as( + const OutString *tag, + basic_string_view format_str, + basic_format_args::type> args) { + basic_memory_buffer buffer; + internal::vformat_to(buffer, format_str, args); + return to_string(buffer, tag); +} + /** Returns the number of characters in the output of ``format(format_str, args...)``. diff --git a/test/format-test.cc b/test/format-test.cc index 2ce513b2..4dbba4ca 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -14,6 +14,9 @@ #include #include #include +#if __cplusplus >= 201703 || _MSVC_LANG >= 201703 +#include +#endif // Check if fmt/format.h compiles with windows.h included before it. #ifdef _WIN32 @@ -2470,3 +2473,48 @@ TEST(FormatTest, U8StringViewLiteral) { TEST(FormatTest, FormatU8String) { EXPECT_EQ(format(fmt::u8string_view("{}"), 42), fmt::u8string_view("42")); } + +namespace FakeQt { +class QString { +public: + QString(const wchar_t *s, std::size_t len) + : s_(std::make_shared(s, len)) {} + operator const std::wstring &() const FMT_NOEXCEPT { return *s_; } + int size() const FMT_NOEXCEPT { return static_cast(s_->size()); } +private: + std::shared_ptr s_; +}; + +template +typename std::enable_if::value, QString>::type +to_string(const basic_memory_buffer &buf, const QString *) { + return QString(buf.data(), buf.size()); +} +} + +struct my_allocator : std::allocator {}; + +TEST(FormatTest, FormatAs) { + EXPECT_EQ(fmt::format_as("{}", 42), "42"); + EXPECT_EQ(fmt::format_as(L"{}", 42), L"42"); +// This will fail with static_assert +// "cannot construct OutString from basic_memory_buffer" and types +// OutString=std::string, Char=char16_t +// EXPECT_EQ(fmt::format_as(u"{}", 42), std::string("42")); + + typedef std::basic_string< + wchar_t, std::char_traits, my_allocator> my_wstring; + EXPECT_EQ(fmt::format_as(L"{}", 42), L"42"); + +#if __cplusplus >= 201703 || _MSVC_LANG >= 201703 + // taking a reference requires matching types + const std::pmr::string &ps = fmt::format_as("{}", 42); + EXPECT_EQ(ps, "42"); + + std::pmr::polymorphic_allocator alloc(std::pmr::new_delete_resource()); + EXPECT_EQ(fmt::format_as(alloc, "{}", 42), "42"); +#endif + + const FakeQt::QString &qs = fmt::format_as(L"{}", 42); + EXPECT_EQ(static_cast(qs), std::wstring(L"42")); +}