diff --git a/include/fmt/core.h b/include/fmt/core.h index a0d86d06..6cca7645 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1127,8 +1127,9 @@ constexpr bool is_arithmetic_type(type t) { } struct unformattable {}; -struct unformattable_pointer : unformattable {}; struct unformattable_char : unformattable {}; +struct unformattable_const : unformattable {}; +struct unformattable_pointer : unformattable {}; template struct string_value { const Char* data; @@ -1207,8 +1208,9 @@ template class value { fallback_formatter>>; } value(unformattable); - value(unformattable_pointer); value(unformattable_char); + value(unformattable_const) {} + value(unformattable_pointer); private: // Formats an argument of a custom type, such as a user-defined class. @@ -1365,17 +1367,37 @@ template struct arg_mapper { static_cast::type>(val))) { return map(static_cast::type>(val)); } + + template > + using formattable = + bool_constant() || + !std::is_const>::value || + has_fallback_formatter::value>; + +#if FMT_MSC_VER != 0 && FMT_MSC_VER < 1910 + // Workaround a bug in MSVC. + template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } +#else + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } + template ::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { + return {}; + } +#endif + 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; + FMT_CONSTEXPR FMT_INLINE auto map(T&& val) + -> decltype(this->do_map(std::forward(val))) { + return do_map(std::forward(val)); } template ::value)> @@ -1616,13 +1638,18 @@ FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { // a pointer cast it to "void *" or "const void *". In particular, this // forbids formatting of "[const] volatile char *" which is printed as bool // by iostreams. - constexpr bool void_ptr = + constexpr bool formattable_pointer = !std::is_same::value; - static_assert(void_ptr, "Formatting of non-void pointers is disallowed."); + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); - constexpr bool same_char = + constexpr bool formattable_char = !std::is_same::value; - static_assert(same_char, "Mixing character types is disallowed."); + static_assert(formattable_char, "Mixing character types is disallowed."); + + constexpr bool formattable_const = + !std::is_same::value; + static_assert(formattable_const, "Cannot format a const argument."); constexpr bool formattable = !std::is_same::value; diff --git a/test/core-test.cc b/test/core-test.cc index 12b28964..4c4fc9da 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -703,6 +703,35 @@ TEST(core_test, has_formatter) { ""); } +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_formattable) { static_assert(fmt::is_formattable::value, ""); static_assert(fmt::is_formattable::value, ""); @@ -717,6 +746,14 @@ TEST(core_test, is_formattable) { static_assert(!fmt::is_formattable::value, ""); static_assert(fmt::is_formattable::value, ""); + static_assert(fmt::is_formattable::value, ""); + static_assert(fmt::is_formattable::value, ""); + + static_assert(fmt::is_formattable::value, ""); +#if !FMT_MSC_VER || FMT_MSC_VER >= 1910 + static_assert(!fmt::is_formattable::value, ""); +#endif + static_assert(!fmt::is_formattable::value, ""); static_assert(!fmt::is_formattable::value, ""); static_assert(!fmt::is_formattable::value, ""); @@ -854,35 +891,6 @@ TEST(core_test, adl) { 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()));