Fix formatting of nullptr with FMT_COMPILE and to_string (#4993)

* Fix formatting of nullptr with FMT_COMPILE and to_string

std::nullptr_t is implicitly convertible to string_view and const char*,
so compiled format strings and fmt::to_string treated nullptr as a null
C string: FMT_COMPILE("{}!") crashed and FMT_COMPILE("{}") and to_string
threw "string pointer is null". Format it as a pointer ("0x0") like the
runtime API does.

* Write nullptr as a pointer in detail::write

detail::write<Char>(out, nullptr) picked the const Char* overload, so
fmt::to_string(nullptr) and FMT_COMPILE("{}") threw "string pointer is
null". Add a nullptr_t overload that forwards to the const void* one.
This commit is contained in:
Marvel Harisson 2026-10-26 01:30:45 +09:00 committed by GitHub
parent 3a2dcb59ef
commit 2e60743c67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 10 additions and 1 deletions

View File

@ -175,7 +175,8 @@ template <typename Char, typename V, int N> struct field {
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value &&
!std::is_same<V, nullptr_t>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
} else if constexpr (use_format_as<V>::value) {

View File

@ -3920,6 +3920,11 @@ auto write(OutputIt out, const T* value, const format_specs& specs = {},
return write_ptr<Char>(out, bit_cast<uintptr_t>(value), &specs);
}
template <typename Char, typename OutputIt>
auto write(OutputIt out, nullptr_t) -> OutputIt {
return write<Char>(out, static_cast<const void*>(nullptr));
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(mapped_type_constant<T, Char>::value ==
type::custom_type &&

View File

@ -76,6 +76,7 @@ TEST(compile_test, format_default) {
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable()));
EXPECT_EQ("0x0", fmt::format(FMT_COMPILE("{}"), nullptr));
auto t = std::chrono::system_clock::now();
EXPECT_EQ(fmt::format("{}", t), fmt::format(FMT_COMPILE("{}"), t));
}
@ -314,6 +315,7 @@ TEST(compile_test, static_format_high_precision) {
TEST(compile_test, text_and_arg) {
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
EXPECT_EQ("0x0!", fmt::format(FMT_COMPILE("{}!"), nullptr));
}
TEST(compile_test, unknown_format_fallback) {

View File

@ -2339,6 +2339,7 @@ FMT_END_NAMESPACE
TEST(format_test, to_string) {
EXPECT_EQ(fmt::to_string(42), "42");
EXPECT_EQ(fmt::to_string(reinterpret_cast<void*>(0x1234)), "0x1234");
EXPECT_EQ(fmt::to_string(nullptr), "0x0");
EXPECT_EQ(fmt::to_string(adl_test::fmt::detail::foo()), "foo");
EXPECT_EQ(fmt::to_string(foo), "0");