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 <dani@ngrt.de>
This commit is contained in:
Daniela Engert 2018-10-08 20:14:39 +02:00 committed by Victor Zverovich
parent 846c644e84
commit 2c81c851b2
6 changed files with 184 additions and 73 deletions

View File

@ -270,7 +270,7 @@ inline void reset_color<wchar_t>(FILE *stream) FMT_NOEXCEPT {
template <
typename String,
typename Char = typename internal::format_string_traits<String>::char_type>
typename Char = typename internal::has_to_string_view<String>::char_type>
void vprint_rgb(rgb fd, const String &format,
basic_format_args<typename buffer_context<Char>::type> args) {
internal::fputs<Char>(internal::make_foreground_color<Char>(fd), stdout);
@ -280,7 +280,7 @@ void vprint_rgb(rgb fd, const String &format,
template <
typename String,
typename Char = typename internal::format_string_traits<String>::char_type>
typename Char = typename internal::has_to_string_view<String>::char_type>
void vprint_rgb(rgb fd, rgb bg, const String &format,
basic_format_args<typename buffer_context<Char>::type> args) {
internal::fputs<Char>(internal::make_foreground_color<Char>(fd), stdout);
@ -299,7 +299,7 @@ template <typename String, typename... Args>
typename std::enable_if<internal::is_string<String>::value>::type
print(rgb fd, const String &format_str, const Args & ... args) {
internal::check_format_string<Args...>(format_str);
typedef typename internal::format_string_traits<String>::char_type char_t;
typedef typename internal::has_to_string_view<String>::char_type char_t;
typedef typename buffer_context<char_t>::type context_t;
format_arg_store<context_t, Args...> as{args...};
vprint_rgb(fd, format_str, basic_format_args<context_t>(as));
@ -316,7 +316,7 @@ template <typename String, typename... Args>
typename std::enable_if<internal::is_string<String>::value>::type
print(rgb fd, rgb bg, const String &format_str, const Args & ... args) {
internal::check_format_string<Args...>(format_str);
typedef typename internal::format_string_traits<String>::char_type char_t;
typedef typename internal::has_to_string_view<String>::char_type char_t;
typedef typename buffer_context<char_t>::type context_t;
format_arg_store<context_t, Args...> as{args...};
vprint_rgb(fd, bg, format_str, basic_format_args<context_t>(as));

View File

@ -435,10 +435,30 @@ class basic_string_view {
typedef basic_string_view<char> string_view;
typedef basic_string_view<wchar_t> wstring_view;
template <typename Char>
inline basic_string_view<Char>
to_string_view(const std::basic_string<Char> &s) { return s; }
template <typename Char>
inline basic_string_view<Char> to_string_view(const Char *s) { return s; }
#ifdef FMT_STRING_VIEW
template <typename Char>
inline basic_string_view<Char>
to_string_view(FMT_STRING_VIEW<Char> 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 <typename S>
struct is_compile_string : std::is_base_of<compile_string, S> {};
template <typename S>
inline typename std::enable_if<is_compile_string<S>::value, string_view>::type
to_string_view(const S &s) { return {s.data(), s.size() - 1}; }
template <typename Context>
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<S>::char_type gives its
// character type.
template <typename S, typename Enable = void>
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 <typename S>
struct get_char_type {
typedef void char_type;
};
template <typename Char>
struct format_string_traits_base { typedef Char char_type; };
struct get_char_type<basic_string_view<Char>> {
typedef Char char_type;
};
template <typename Char>
struct format_string_traits<Char *> : format_string_traits_base<Char> {};
using fmt::v5::to_string_view;
template <typename Char>
struct format_string_traits<const Char *> : format_string_traits_base<Char> {};
template <typename Char, std::size_t N>
struct format_string_traits<Char[N]> : format_string_traits_base<Char> {};
template <typename Char, std::size_t N>
struct format_string_traits<const Char[N]> : format_string_traits_base<Char> {};
template <typename Char>
struct format_string_traits<std::basic_string<Char>> :
format_string_traits_base<Char> {};
#ifdef FMT_STRING_VIEW
template <typename Char>
struct format_string_traits<FMT_STRING_VIEW<Char>> :
format_string_traits_base<Char> {};
#endif
void to_string_view(...);
template <typename S>
struct format_string_traits<
S, typename std::enable_if<std::is_base_of<
basic_string_view<typename S::char_type>, S>::value>::type> :
format_string_traits_base<typename S::char_type> {};
struct has_to_string_view {
typedef typename get_char_type<decltype(to_string_view(internal::declval<S>()))>::char_type char_type;
static const bool value = !std::is_same<void, decltype(to_string_view(internal::declval<S>()))>::value;
};
template <typename S>
struct is_string : std::is_empty<format_string_traits<S>> {};
template <typename S>
struct is_compile_string : std::is_base_of<compile_string, S> {};
struct is_string : internal::has_to_string_view<S> {};
template <typename Char>
struct named_arg_base;
@ -714,7 +711,8 @@ inline typename std::enable_if<
template <typename C, typename T, typename Char = typename C::char_type>
inline typename std::enable_if<
internal::is_constructible<basic_string_view<Char>, T>::value,
internal::is_constructible<basic_string_view<Char>, T>::value &&
!internal::has_to_string_view<T>::value,
init<C, basic_string_view<Char>, string_type>>::type
make_value(const T &val) { return basic_string_view<Char>(val); }
@ -722,7 +720,8 @@ template <typename C, typename T, typename Char = typename C::char_type>
inline typename std::enable_if<
!convert_to_int<T, Char>::value &&
!std::is_convertible<T, basic_string_view<Char>>::value &&
!internal::is_constructible<basic_string_view<Char>, T>::value,
!internal::is_constructible<basic_string_view<Char>, T>::value &&
!internal::has_to_string_view<T>::value,
// Implicit conversion to std::string is not handled here because it's
// unsafe: https://github.com/fmtlib/fmt/issues/729
init<C, const T &, custom_type>>::type
@ -736,6 +735,19 @@ init<C, const void*, named_arg_type>
return static_cast<const void*>(&val);
}
template <typename C, typename S>
FMT_CONSTEXPR11 typename std::enable_if<
internal::has_to_string_view<S>::value,
init<C, basic_string_view<typename C::char_type>, 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<S>::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<wformat_context> {
#if FMT_USE_ALIAS_TEMPLATES
/** String's character type. */
template <typename S>
using char_t = typename internal::format_string_traits<S>::char_type;
using char_t = typename std::enable_if<internal::has_to_string_view<S>::value,
typename internal::has_to_string_view<S>::char_type>::type;
#define FMT_CHAR(S) char_t<S>
template <typename S, typename T>
@ -1264,7 +1277,12 @@ using enable_if_string_t =
typename std::enable_if<internal::is_string<S>::value, T>::type;
#define FMT_ENABLE_IF_STRING(S, T) enable_if_string_t<S, T>
#else
#define FMT_CHAR(S) typename internal::format_string_traits<S>::char_type
template <typename S>
struct char_t : std::enable_if<
internal::has_to_string_view<S>::value,
typename internal::has_to_string_view<S>::char_type> {};
#define FMT_CHAR(S) typename char_t<S>::type
#define FMT_ENABLE_IF_STRING(S, T) \
typename std::enable_if<internal::is_string<S>::value, T>::type
#endif
@ -1316,17 +1334,39 @@ struct checked_args : format_arg_store<
basic_format_args<context> operator*() const { return *this; }
};
template <typename S>
inline basic_string_view<FMT_CHAR(S)> to_string_view(const S &s) {
return basic_string_view<FMT_CHAR(S)>(s);
}
template <typename Char>
std::basic_string<Char> vformat(
basic_string_view<Char> format_str,
basic_format_args<typename buffer_context<Char>::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<Char>`` and returns a ``fmt::basic_string_view<Char>``.
The conversion function must live in the very same namespace as
``StringType<Char>`` 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 <typename Char>
inline basic_string_view<Char> to_string_view(basic_string_view<Char> 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<typename buffer_context<FMT_CHAR(S)>::type> args) {
internal::container_buffer<Container> 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<Container> out, const S &format_str,
const Args &... args) {
internal::checked_args<S, 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 <typename S, typename Char = FMT_CHAR(S)>
inline std::basic_string<Char> vformat(
const S &format_str,
basic_format_args<typename buffer_context<Char>::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 <typename S, typename... Args>
inline std::basic_string<FMT_CHAR(S)> 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<S, Args...>(format_str, args...));
}
@ -1430,7 +1470,7 @@ FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args);
template <typename S, typename... Args>
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<S, Args...>(format_str, args...));
}
@ -1449,7 +1489,7 @@ FMT_API void vprint(wstring_view format_str, wformat_args args);
template <typename S, typename... Args>
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<S, Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@ -1190,11 +1190,6 @@ inline typename std::enable_if<sizeof(Double) != sizeof(uint64_t), bool>::type
template <typename Double>
void sprintf_format(Double, internal::buffer &, core_format_specs);
template <typename S>
struct format_string_traits<
S, typename std::enable_if<std::is_base_of<compile_string, S>::value>::type>:
format_string_traits_base<char> {};
template <typename Handler>
FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler &&handler) {
switch (spec) {
@ -3345,20 +3340,20 @@ inline typename buffer_context<FMT_CHAR(String)>::type::iterator vformat_to(
basic_format_args<typename buffer_context<Char>::type> args) {
typedef back_insert_range<internal::basic_buffer<FMT_CHAR(String)> > range;
return vformat_to<arg_formatter<range>>(
buf, basic_string_view<Char>(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<String>::char_type>
typename Char = typename internal::has_to_string_view<String>::char_type>
inline typename buffer_context<Char>::type::iterator format_to(
basic_memory_buffer<Char, SIZE> &buf, const String &format_str,
const Args &... args) {
internal::check_format_string<Args...>(format_str);
typedef typename buffer_context<Char>::type context;
format_arg_store<context, Args...> as{args...};
return vformat_to(buf, basic_string_view<Char>(format_str),
return vformat_to(buf, to_string_view(format_str),
basic_format_args<context>(as));
}
@ -3378,7 +3373,8 @@ inline OutputIt vformat_to(
OutputIt out, const String &format_str,
typename format_args_t<OutputIt, FMT_CHAR(String)>::type args) {
typedef output_range<OutputIt, FMT_CHAR(String)> range;
return vformat_to<arg_formatter<range>>(range(out), format_str, args);
return vformat_to<arg_formatter<range>>(range(out),
to_string_view(format_str), args);
}
/**
@ -3398,7 +3394,7 @@ inline FMT_ENABLE_IF_STRING(S, OutputIt)
internal::check_format_string<Args...>(format_str);
typedef typename format_context_t<OutputIt, FMT_CHAR(S)>::type context;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, basic_string_view<FMT_CHAR(S)>(format_str),
return vformat_to(out, to_string_view(format_str),
basic_format_args<context>(as));
}
@ -3448,7 +3444,7 @@ inline FMT_ENABLE_IF_STRING(S, format_to_n_result<OutputIt>)
internal::check_format_string<Args...>(format_str);
typedef FMT_CHAR(S) Char;
format_arg_store<format_to_n_context<OutputIt, Char>, 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<OutputIt, Char>(as));
}

View File

@ -146,7 +146,7 @@ inline typename std::enable_if<internal::is_string<S>::value>::type
print(std::basic_ostream<FMT_CHAR(S)> &os, const S &format_str,
const Args & ... args) {
internal::checked_args<S, 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

View File

@ -606,7 +606,7 @@ inline FMT_ENABLE_IF_STRING(S, std::basic_string<FMT_CHAR(S)>)
typedef internal::basic_buffer<FMT_CHAR(S)> buffer;
typedef typename printf_context<buffer>::type context;
format_arg_store<context, Args...> as{ args... };
return vsprintf(internal::to_string_view(format_str),
return vsprintf(to_string_view(format_str),
basic_format_args<context>(as));
}
@ -637,7 +637,7 @@ inline FMT_ENABLE_IF_STRING(S, int)
typedef internal::basic_buffer<FMT_CHAR(S)> buffer;
typedef typename printf_context<buffer>::type context;
format_arg_store<context, Args...> as{ args... };
return vfprintf(f, internal::to_string_view(format_str),
return vfprintf(f, to_string_view(format_str),
basic_format_args<context>(as));
}
@ -664,7 +664,7 @@ inline FMT_ENABLE_IF_STRING(S, int)
typedef internal::basic_buffer<FMT_CHAR(S)> buffer;
typedef typename printf_context<buffer>::type context;
format_arg_store<context, Args...> as{ args... };
return vprintf(internal::to_string_view(format_str),
return vprintf(to_string_view(format_str),
basic_format_args<context>(as));
}
@ -696,7 +696,7 @@ inline FMT_ENABLE_IF_STRING(S, int)
typedef internal::basic_buffer<FMT_CHAR(S)> buffer;
typedef typename printf_context<buffer>::type context;
format_arg_store<context, Args...> as{ args... };
return vfprintf(os, internal::to_string_view(format_str),
return vfprintf(os, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE

View File

@ -13,6 +13,7 @@
#include <limits>
#include <string>
#include <type_traits>
#include <memory>
#include "test-assert.h"
@ -451,6 +452,47 @@ TEST(CoreTest, IsEnumConvertibleToInt) {
EXPECT_TRUE((fmt::convert_to_int<enum_with_underlying_type, char>::value));
}
namespace my_ns {
template <typename Char>
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<Char> s_;
};
template <typename Char>
inline fmt::basic_string_view<Char>
to_string_view(const my_string<Char> &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<std::wstring>(s)) {}
const wchar_t *utf16() const FMT_NOEXCEPT { return s_->data(); }
int size() const FMT_NOEXCEPT { return static_cast<int>(s_->size()); }
#ifdef FMT_STRING_VIEW
operator FMT_STRING_VIEW<wchar_t>() const FMT_NOEXCEPT { return *s_; }
#endif
private:
std::shared_ptr<std::wstring> s_;
};
inline fmt::basic_string_view<wchar_t> to_string_view(
const QString &s) FMT_NOEXCEPT {
return {reinterpret_cast<const wchar_t *>(s.utf16()),
static_cast<std::size_t>(s.size())};
}
}
template <typename T>
class IsStringTest : public testing::Test {};
@ -468,11 +510,16 @@ TYPED_TEST(IsStringTest, IsString) {
EXPECT_TRUE((fmt::internal::is_string<TypeParam[2]>::value));
EXPECT_TRUE((fmt::internal::is_string<const TypeParam[2]>::value));
EXPECT_TRUE((fmt::internal::is_string<std::basic_string<TypeParam>>::value));
EXPECT_TRUE((fmt::internal::is_string<fmt::basic_string_view<TypeParam>>::value));
EXPECT_TRUE((fmt::internal::is_string<derived_from_string_view<TypeParam>>::value));
EXPECT_TRUE(
(fmt::internal::is_string<fmt::basic_string_view<TypeParam>>::value));
EXPECT_TRUE(
(fmt::internal::is_string<derived_from_string_view<TypeParam>>::value));
#ifdef FMT_STRING_VIEW
EXPECT_TRUE((fmt::internal::is_string<FMT_STRING_VIEW<TypeParam>>::value));
#endif
EXPECT_TRUE((fmt::internal::is_string<my_ns::my_string<TypeParam>>::value));
EXPECT_FALSE((fmt::internal::is_string<my_ns::non_string>::value));
EXPECT_TRUE((fmt::internal::is_string<FakeQt::QString>::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<char>("42")), "42");
EXPECT_EQ(to_string_view(my_string<wchar_t>(L"42")), L"42");
EXPECT_EQ(to_string_view(QString(L"42")), L"42");
fmt::internal::type type =
fmt::internal::get_type<fmt::format_context, my_string<char>>::value;
EXPECT_EQ(type, fmt::internal::string_type);
type =
fmt::internal::get_type<fmt::wformat_context, my_string<wchar_t>>::value;
EXPECT_EQ(type, fmt::internal::string_type);
type = fmt::internal::get_type<fmt::wformat_context, QString>::value;
EXPECT_EQ(type, fmt::internal::string_type);
// Does not compile: only wide format contexts are compatible with QString!
// type = fmt::internal::get_type<fmt::format_context, QString>::value;
}
TEST(CoreTest, FormatForeignStrings) {
using namespace my_ns;
using namespace FakeQt;
EXPECT_EQ(fmt::format(my_string<char>("{}"), 42), "42");
EXPECT_EQ(fmt::format(my_string<wchar_t>(L"{}"), 42), L"42");
EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
EXPECT_EQ(fmt::format(QString(L"{}"), my_string<wchar_t>(L"42")), L"42");
EXPECT_EQ(fmt::format(my_string<wchar_t>(L"{}"), QString(L"42")), L"42");
}