Support any string_type as result type of 'fmt::format_as<string_type>(...)'.

In particular, support 'std::basic_string' with non-defaulted allocators like f.e. 'std::pmr::polymorphic_allocator'.

This requires two overloads 'fmt::format_as<string_type>(...)' for stateless allocators and 'fmt::format_as<string_type>(const allocator &, ...)' for stateful allocators. A user-supplied conversion operator 'to_string(const fmt::basic_memory_buffer &, const string_type *)' found by ADL does the actual conversion to the requested 'string_type'.

Signed-off-by: Daniela Engert <dani@ngrt.de>
This commit is contained in:
Daniela Engert 2019-01-08 14:02:26 +01:00
parent f5cc77cea0
commit 816daa7f40
No known key found for this signature in database
GPG Key ID: 7B95EE52040C5975
3 changed files with 202 additions and 0 deletions

View File

@ -1372,6 +1372,12 @@ std::basic_string<Char> vformat(
basic_string_view<Char> format_str,
basic_format_args<typename buffer_context<Char>::type> args);
template <typename Char, typename OutString>
OutString vformat_as(
const OutString *tag,
basic_string_view<Char> format_str,
basic_format_args<typename buffer_context<Char>::type> args);
template <typename Char>
typename buffer_context<Char>::type::iterator vformat_to(
internal::basic_buffer<Char> &buf, basic_string_view<Char> format_str,
@ -1458,6 +1464,78 @@ inline std::basic_string<FMT_CHAR(S)> format(
*internal::checked_args<S, 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 <typename T>
const T *out_string_tag(void *allocator) {
return reinterpret_cast<const T *>(allocator);
}
#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404
// gcc 4.4 is lacking std::uses_allocator from C++11
template <typename T, typename Alloc>
struct has_allocator_type {
// tests for suitable T::allocator_type
template <typename U>
static std::is_convertible<Alloc, typename U::allocator_type> test(int);
template<typename U>
static std::false_type test(...);
typedef decltype(test<T>(0)) type;
static const bool value = type::value;
};
# define FMT_USES_ALLOCATOR(S,A) internal::has_allocator_type<S, A>::value
#else
// if std::uses_allocator is available use it because it may be specialized!
# define FMT_USES_ALLOCATOR(S,A) std::uses_allocator<S, A>::value
#endif
}
/**
\rst
Formats arguments and returns the result as a string of given type.
**Example**::
#include <fmt/core.h>
auto message = fmt::format_as<std::pmr::string>("The answer is {}", 42);
\endrst
*/
template <typename OutString, typename S, typename... Args>
inline OutString format_as(const S &format_str, const Args &... args) {
return internal::vformat_as(
internal::out_string_tag<OutString>(FMT_NULL), to_string_view(format_str),
*internal::checked_args<S, Args...>(format_str, args...));
}
/**
\rst
Formats arguments and returns the result as a string of given type.
**Example**::
#include <fmt/core.h>
std::pmr::memory_resource *mr = ...;
std::pmr::polymorphic_allocator alloc(mr);
auto message = fmt::format_as<std::pmr::string>(alloc, "Answer: {}", 42);
\endrst
*/
template <typename OutString, typename Alloc, typename S, typename... Args>
inline typename
std::enable_if<FMT_USES_ALLOCATOR(OutString, Alloc), OutString>::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<OutString>(&out_alloc),
to_string_view(format_str),
*internal::checked_args<S, 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);

View File

@ -3229,6 +3229,72 @@ std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE> &buf) {
return std::basic_string<Char>(buf.data(), buf.size());
}
namespace internal {
template <typename Char, typename OutString>
struct output_traits;
template <typename Char, typename Traits, typename Alloc>
struct output_traits<Char, std::basic_string<Char, Traits, Alloc>> {
typedef std::basic_string<Char, Traits, Alloc> type;
static const Alloc &to_allocator(const type *tag) FMT_NOEXCEPT {
return *reinterpret_cast<const Alloc *>(tag);
}
};
template <typename T = void>
struct lazy_false {
static const bool value = false;
};
}
/**
\rst
The operator ``to_string`` converts a ``fmt::basic_memory_buffer<Char>`` to
any kind of string if the user provides a templated overload of
``to_string`` which takes an instance of ``fmt::basic_memory_buffer<Char>``
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 <typename Char, std::size_t SIZE>
inline my_string to_string(const basic_memory_buffer<Char, SIZE> &buf,
const my_string *tag) {
if (tag) {
using my_allocator_type = my_string::allocator_type;
return my_string{buf.data(), buf.length(),
*reinterpret_cast<const my_allocator_type *>(tag)};
} else {
return my_string{buf.data(), buf.length()};
}
}
}
my_ns::my_string message
= fmt::format_as<my_ns::my_string>("The answer is {}"), 42);
\endrst
*/
template <typename Char, std::size_t SIZE, typename OutString>
OutString to_string(const basic_memory_buffer<Char, SIZE> &, OutString *) {
static_assert(internal::lazy_false<OutString>::value,
"cannot construct OutString from basic_memory_buffer<Char>");
}
template <typename Char, std::size_t SIZE, typename OutString>
typename internal::output_traits<Char, OutString>::type
to_string(const basic_memory_buffer<Char, SIZE> &buf, const OutString *tag) {
typedef internal::output_traits<Char, OutString> traits;
if (!tag) {
return OutString(buf.data(), buf.size());
} else {
return OutString(buf.data(), buf.size(), traits::to_allocator(tag));
}
}
template <typename Char>
typename buffer_context<Char>::type::iterator internal::vformat_to(
internal::basic_buffer<Char> &buf, basic_string_view<Char> format_str,
@ -3415,6 +3481,16 @@ inline std::basic_string<Char> internal::vformat(
return fmt::to_string(buffer);
}
template <typename Char, typename OutString>
inline OutString internal::vformat_as(
const OutString *tag,
basic_string_view<Char> format_str,
basic_format_args<typename buffer_context<Char>::type> args) {
basic_memory_buffer<Char> 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...)``.

View File

@ -14,6 +14,9 @@
#include <memory>
#include <string>
#include <stdint.h>
#if __cplusplus >= 201703 || _MSVC_LANG >= 201703
#include <memory_resource>
#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<std::wstring>(s, len)) {}
operator const std::wstring &() const FMT_NOEXCEPT { return *s_; }
int size() const FMT_NOEXCEPT { return static_cast<int>(s_->size()); }
private:
std::shared_ptr<std::wstring> s_;
};
template <typename Char, std::size_t SIZE>
typename std::enable_if<std::is_same<Char, wchar_t>::value, QString>::type
to_string(const basic_memory_buffer<Char, SIZE> &buf, const QString *) {
return QString(buf.data(), buf.size());
}
}
struct my_allocator : std::allocator<wchar_t> {};
TEST(FormatTest, FormatAs) {
EXPECT_EQ(fmt::format_as<std::string>("{}", 42), "42");
EXPECT_EQ(fmt::format_as<std::wstring>(L"{}", 42), L"42");
// This will fail with static_assert
// "cannot construct OutString from basic_memory_buffer<Char>" and types
// OutString=std::string, Char=char16_t
// EXPECT_EQ(fmt::format_as<std::string>(u"{}", 42), std::string("42"));
typedef std::basic_string<
wchar_t, std::char_traits<wchar_t>, my_allocator> my_wstring;
EXPECT_EQ(fmt::format_as<my_wstring>(L"{}", 42), L"42");
#if __cplusplus >= 201703 || _MSVC_LANG >= 201703
// taking a reference requires matching types
const std::pmr::string &ps = fmt::format_as<std::pmr::string>("{}", 42);
EXPECT_EQ(ps, "42");
std::pmr::polymorphic_allocator<char> alloc(std::pmr::new_delete_resource());
EXPECT_EQ(fmt::format_as<std::pmr::string>(alloc, "{}", 42), "42");
#endif
const FakeQt::QString &qs = fmt::format_as<FakeQt::QString>(L"{}", 42);
EXPECT_EQ(static_cast<std::wstring>(qs), std::wstring(L"42"));
}