From 5c222f0561ad661b06990eb68fe74f31fb9c492f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 24 Jul 2021 08:02:23 -0700 Subject: [PATCH] Add support for nonconst formattable types --- include/fmt/core.h | 71 ++++++++++++++++++++++++++++++++-------------- test/core-test.cc | 40 ++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index af0afd02..dbbf42c8 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -711,6 +711,22 @@ class appender; FMT_BEGIN_DETAIL_NAMESPACE +template +constexpr auto is_const_formattable_impl(T*) + -> decltype(typename Context::template formatter_type().format( + std::declval(), std::declval()), + true) { + return true; +} +template +constexpr auto is_const_formattable_impl(...) -> bool { + return false; +} +template +constexpr auto is_const_formattable() -> bool { + return is_const_formattable_impl(static_cast(nullptr)); +} + // Extracts a reference to the container from back_insert_iterator. template inline auto get_container(std::back_insert_iterator it) @@ -1112,8 +1128,8 @@ template struct named_arg_value { template struct custom_value { using parse_context = typename Context::parse_context_type; - const void* value; - void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); }; // A formatting argument value. @@ -1164,26 +1180,30 @@ template class value { FMT_INLINE value(const named_arg_info* args, size_t size) : named_args{args, size} {} - template FMT_CONSTEXPR FMT_INLINE value(const T& val) { - custom.value = &val; + template FMT_CONSTEXPR FMT_INLINE value(T& val) { + using value_type = remove_cvref_t; + custom.value = const_cast(&val); // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = format_custom_arg< - T, conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>>; + value_type, + conditional_t::value, + typename Context::template formatter_type, + fallback_formatter>>; } private: // Formats an argument of a custom type, such as a user-defined class. template - static void format_custom_arg(const void* arg, + static void format_custom_arg(void* arg, typename Context::parse_context_type& parse_ctx, Context& ctx) { - Formatter f; + auto f = Formatter(); parse_ctx.advance_to(f.parse(parse_ctx)); - ctx.advance_to(f.format(*static_cast(arg), ctx)); + using qualified_type = + conditional_t(), const T, T>; + ctx.advance_to(f.format(*static_cast(arg), ctx)); } }; @@ -1323,11 +1343,16 @@ template struct arg_mapper { static_cast::type>(val))) { return map(static_cast::type>(val)); } - template ::value && !is_char::value && - (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> const T& { + template , + FMT_ENABLE_IF(!is_string::value && !is_char::value && + !std::is_array::value && + (has_formatter::value || + has_fallback_formatter::value))> + FMT_CONSTEXPR FMT_INLINE auto map(T&& val) -> T& { + static_assert(is_const_formattable() || + !std::is_const>() || + has_fallback_formatter(), + "cannot format a const argument"); return val; } @@ -1562,8 +1587,8 @@ FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg { // another (not recommended). template -FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value { - const auto& arg = arg_mapper().map(val); +FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { + const auto& arg = arg_mapper().map(std::forward(val)); static_assert( !std::is_same::value, "Cannot format an argument. To make type T formattable provide a " @@ -1684,14 +1709,16 @@ class format_arg_store : 0); public: - FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args) + template + FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif data_{detail::make_arg< is_packed, Context, - detail::mapped_type_constant::value>(args)...} { + detail::mapped_type_constant, Context>::value>( + std::forward(args))...} { detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1705,9 +1732,9 @@ class format_arg_store \endrst */ template -constexpr auto make_format_args(const Args&... args) - -> format_arg_store { - return {args...}; +constexpr auto make_format_args(Args&&... args) + -> format_arg_store...> { + return {std::forward(args)...}; } /** diff --git a/test/core-test.cc b/test/core-test.cc index 46837116..b79485b7 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -847,3 +847,43 @@ TEST(core_test, adl) { fmt::print("{}", s); fmt::print(stdout, "{}", s); } + +struct const_formattable {}; +struct nonconst_formattable {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(const const_formattable&, format_context& ctx) + -> decltype(ctx.out()) { + auto test = string_view("test"); + return std::copy_n(test.data(), test.size(), ctx.out()); + } +}; + +template <> struct formatter { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + auto format(nonconst_formattable&, format_context& ctx) + -> decltype(ctx.out()) { + auto test = string_view("test"); + return std::copy_n(test.data(), test.size(), ctx.out()); + } +}; +FMT_END_NAMESPACE + +TEST(core_test, is_const_formattable) { + EXPECT_TRUE((fmt::detail::is_const_formattable())); + EXPECT_FALSE((fmt::detail::is_const_formattable())); +} + +TEST(core_test, format_nonconst) { + EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test"); +}