Add formatter for std::exception_ptr

The formatter prints std::exception::what() (if that's what the
exception is) and prints the type name for exceptions
that are not derived from std::exception.

Fixes #4808
This commit is contained in:
Avi Kivity 2026-07-05 15:13:16 +03:00
parent cd67635a19
commit c9539a7b88
3 changed files with 111 additions and 0 deletions

View File

@ -506,6 +506,8 @@ chrono-format-specifications).
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception)
- [`std::exception_ptr`](
https://en.cppreference.com/w/cpp/error/exception_ptr)
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
- [`std::monostate`](
https://en.cppreference.com/w/cpp/utility/variant/monostate)

View File

@ -649,6 +649,73 @@ struct formatter<
}
};
#if FMT_USE_EXCEPTIONS
namespace detail {
// Formats the exception currently being handled, writing its (dynamic) type
// name and message, and recursing into nested exceptions. Must be called from
// within a catch block.
template <typename OutputIt>
auto write_current_exception(OutputIt out) -> OutputIt {
try {
throw;
} catch (const std::exception& ex) {
// Reuse the std::exception formatter to render "<type>: <what>".
out = fmt::format_to(out, "{:t}", ex);
} catch (...) {
// The thrown object does not derive from std::exception, so it has no
// what(); fall back to its type name obtained from the ABI, if available.
# if FMT_USE_RTTI && defined(FMT_HAS_ABI_CXA_DEMANGLE)
if (const std::type_info* tp = abi::__cxa_current_exception_type())
out = write_demangled_name(out, *tp);
else
out = write<char>(out, string_view("<unknown exception>"));
# else
out = write<char>(out, string_view("<unknown exception>"));
# endif
}
// Recurse into nested exceptions.
try {
throw;
} catch (const std::nested_exception& ne) {
if (ne.nested_ptr()) {
out = write<char>(out, string_view(": "));
try {
std::rethrow_exception(ne.nested_ptr());
} catch (...) {
out = write_current_exception(out);
}
}
} catch (...) {
}
return out;
}
} // namespace detail
// {fmt} has no formatter for std::exception_ptr through the std::exception
// formatter above because std::exception_ptr is not derived from
// std::exception. An empty exception_ptr is formatted as "<no exception>".
template <> struct formatter<std::exception_ptr> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
return ctx.begin();
}
template <typename Context>
auto format(const std::exception_ptr& eptr, Context& ctx) const
-> decltype(ctx.out()) {
if (!eptr)
return detail::write<char>(ctx.out(), string_view("<no exception>"));
try {
std::rethrow_exception(eptr);
} catch (...) {
return detail::write_current_exception(ctx.out());
}
}
};
#endif // FMT_USE_EXCEPTIONS
template <int N, typename Char>
struct formatter<detail::bitint<N>, Char> : formatter<long long, Char> {
static_assert(N <= 64, "unsupported _BitInt");

View File

@ -404,6 +404,48 @@ TEST(std_test, exception) {
#endif
}
#if FMT_USE_EXCEPTIONS
TEST(std_test, exception_ptr) {
using testing::StartsWith;
EXPECT_EQ("<no exception>", fmt::format("{}", std::exception_ptr()));
auto eptr = std::make_exception_ptr(std::runtime_error("Test Exception"));
# if FMT_USE_RTTI
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{}", eptr));
# else
EXPECT_EQ("Test Exception", fmt::format("{}", eptr));
# endif
// A nested exception is formatted recursively. The outer type name is an
// implementation-defined wrapper (e.g. std::_Nested_exception<...>) so only
// the messages and the nested rendering are checked here.
std::exception_ptr nested;
try {
try {
throw std::logic_error("inner");
} catch (...) {
std::throw_with_nested(std::runtime_error("outer"));
}
} catch (...) {
nested = std::current_exception();
}
# if FMT_USE_RTTI
EXPECT_THAT(fmt::format("{}", nested),
testing::AllOf(testing::HasSubstr("outer"),
testing::HasSubstr(": std::logic_error: inner")));
# endif
// A non-std::exception object is formatted by its type name when available.
auto int_eptr = std::make_exception_ptr(42);
# if FMT_USE_RTTI && defined(FMT_HAS_ABI_CXA_DEMANGLE)
EXPECT_EQ("int", fmt::format("{}", int_eptr));
# else
EXPECT_THAT(fmt::format("{}", int_eptr), StartsWith("<unknown exception>"));
# endif
}
#endif
#if FMT_USE_RTTI
TEST(std_test, type_info) {
EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)),