diff --git a/include/fmt/core.h b/include/fmt/core.h index 2f0cfb0b..b362b235 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1116,6 +1116,9 @@ constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } +struct unformattable {}; +struct unformattable_pointer : unformattable {}; + template struct string_value { const Char* data; size_t size; @@ -1192,6 +1195,8 @@ template class value { typename Context::template formatter_type, fallback_formatter>>; } + value(unformattable); + value(unformattable_pointer); private: // Formats an argument of a custom type, such as a user-defined class. @@ -1216,8 +1221,6 @@ enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t; using ulong_type = conditional_t; -struct unformattable {}; - // Maps formatting arguments to core types. template struct arg_mapper { using char_type = typename Context::char_type; @@ -1320,16 +1323,17 @@ template struct arg_mapper { // We use SFINAE instead of a const T* parameter to avoid conflicting with // the C array overload. template - FMT_CONSTEXPR auto map(T) -> enable_if_t::value, int> { + FMT_CONSTEXPR auto map(T) + -> enable_if_t::value, unformattable_pointer> { // Formatting of arbitrary pointers is disallowed. If you want to output // 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. - static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); - return 0; + return {}; } - template + template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { return values; } @@ -1589,8 +1593,14 @@ template FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { const auto& arg = arg_mapper().map(std::forward(val)); + constexpr bool void_ptr = + !std::is_same::value; + static_assert(void_ptr, + "Formatting of non-void pointers is disallowed."); + constexpr bool formattable = + !std::is_same::value; static_assert( - !std::is_same::value, + formattable, "Cannot format an argument. To make type T formattable provide a " "formatter specialization: https://fmt.dev/latest/api.html#udt"); return {arg}; @@ -1668,9 +1678,9 @@ using format_context = buffer_context; template using is_formattable = bool_constant< - !std::is_same>().map( - std::declval())), - detail::unformattable>::value && + !std::is_base_of>().map( + std::declval()))>::value && !detail::has_fallback_formatter::value>; /** diff --git a/test/core-test.cc b/test/core-test.cc index b79485b7..7e490b56 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -408,15 +408,7 @@ TYPED_TEST(numeric_arg_test, make_and_visit) { CHECK_ARG_SIMPLE(std::numeric_limits::max()); } -namespace fmt { -template <> struct is_char : std::true_type {}; -} // namespace fmt - -TEST(arg_test, char_arg) { - CHECK_ARG(char, 'a', 'a'); - CHECK_ARG(wchar_t, L'a', 'a'); - CHECK_ARG(wchar_t, L'a', L'a'); -} +TEST(arg_test, char_arg) { CHECK_ARG(char, 'a', 'a'); } TEST(arg_test, string_arg) { char str_data[] = "test"; @@ -712,6 +704,8 @@ TEST(core_test, has_formatter) { } 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, "");