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:
parent
f5cc77cea0
commit
816daa7f40
@ -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);
|
||||
|
||||
|
||||
@ -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...)``.
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user