From c9539a7b88c4dac9777a9e21521f9502085821ea Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 5 Jul 2026 15:13:16 +0300 Subject: [PATCH] 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 --- doc/api.md | 2 ++ include/fmt/std.h | 67 +++++++++++++++++++++++++++++++++++++++++++++++ test/std-test.cc | 42 +++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/doc/api.md b/doc/api.md index ff190383..84a7d3b0 100644 --- a/doc/api.md +++ b/doc/api.md @@ -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) diff --git a/include/fmt/std.h b/include/fmt/std.h index f7d8d495..3b7ec972 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -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 +auto write_current_exception(OutputIt out) -> OutputIt { + try { + throw; + } catch (const std::exception& ex) { + // Reuse the std::exception formatter to render ": ". + 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(out, string_view("")); +# else + out = write(out, string_view("")); +# endif + } + + // Recurse into nested exceptions. + try { + throw; + } catch (const std::nested_exception& ne) { + if (ne.nested_ptr()) { + out = write(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 "". +template <> struct formatter { + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return ctx.begin(); + } + + template + auto format(const std::exception_ptr& eptr, Context& ctx) const + -> decltype(ctx.out()) { + if (!eptr) + return detail::write(ctx.out(), string_view("")); + try { + std::rethrow_exception(eptr); + } catch (...) { + return detail::write_current_exception(ctx.out()); + } + } +}; +#endif // FMT_USE_EXCEPTIONS + template struct formatter, Char> : formatter { static_assert(N <= 64, "unsupported _BitInt"); diff --git a/test/std-test.cc b/test/std-test.cc index f6fc1284..cd8234bb 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -404,6 +404,48 @@ TEST(std_test, exception) { #endif } +#if FMT_USE_EXCEPTIONS +TEST(std_test, exception_ptr) { + using testing::StartsWith; + + EXPECT_EQ("", 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("")); +# endif +} +#endif + #if FMT_USE_RTTI TEST(std_test, type_info) { EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)),