Introduction:
given:
struct X : std::runtime_error {
using std::runtime_error::runtime_error;
};
When we call std::throw_with_nested(X("foo"))
, what is actually thrown is not an X
. It is some type that is derived from both X
and std::nested_exception
.
therefore, the following assertion will fail:
const std::type_info *a = nullptr, *b = nullptr;
try
{
throw X("1");
}
catch(X& x) {
a = std::addressof(typeid(x));
try {
std::throw_with_nested(X("2"));
}
catch(X& x) {
b = std::addressof(typeid(x));
}
}
assert(std::string(a->name()) == std::string(b->name()));
What I would like to do is deduce that these two exceptions are related.
First attempt:
std::type_index
deduce_exception_type(const std::exception* pe)
{
if (auto pnested = dynamic_cast<const std::nested_exception*>(pe))
{
try {
std::rethrow_exception(pnested->nested_ptr());
}
catch(const std::exception& e)
{
return deduce_exception_type(std::addressof(e));
}
}
else {
return typeid(*pe);
}
}
This fails because std::nested_exception::nested_ptr()
returns a pointer to the next exception down the line, not the X
interface of the current exception.
I'm looking for (portable) ideas and solutions that allow me to recover the typeid(X) from the 'exception with unknown name' thrown by the standard library during std::rethrow_exception
.
c++14 and c++1z are fine.
Why?:
Because I want to be able to unwrap a complete exception hierarchy and transmit it across an rpc session, complete with exception type names.
I ideally don't want to have to write a catch block featuring every exception type in the system, which would have to be weakly ordered by derivation depth.
A further example of expected functionality (and an illustration of why my approach does not work):
const std::type_info *b = nullptr;
try
{
throw std::runtime_error("1");
}
catch(std::exception&) {
try {
std::throw_with_nested(X("2"));
}
catch(X& x) {
// PROBLEM HERE <<== X& catches a std::_1::__nested<X>, which
// is derived from X and std::nested_exception
b = std::addressof(typeid(x));
}
}
assert(std::string(typeid(X).name()) == std::string(b->name()));
Adapted print_exception
from http://en.cppreference.com/w/cpp/error/nested_exception :
const std::type_info&
deduce_exception_type(const std::exception& e)
{
try {
std::rethrow_if_nested(e);
} catch(const std::exception& inner_e) {
return deduce_exception_type(inner_e);
} catch(...) {
}
return typeid(e);
}
Demo
One way around is to consistently use your own throw_with_nested
, wherein you inject the functionality you want:
#include <typeinfo>
#include <exception>
struct identifiable_base {
virtual std::type_info const& type_info() const = 0;
};
template<typename Exception>
struct identifiable_exception: Exception, identifiable_base {
using Exception::Exception;
explicit identifiable_exception(Exception base)
: Exception(std::move(base))
{}
std::type_info const& type_info() const override
{
// N.B.: this is a static use of typeid
return typeid(Exception);
}
};
template<typename Exception>
identifiable_exception<std::decay_t<Exception>> make_identifiable_exception(Exception&& exception)
{ return identifiable_exception<std::decay_t<Exception>> { std::forward<Exception>(exception) }; }
// N.B.: declared with a different name than std::throw_with_nested to avoid ADL mistakes
template<typename Exception>
[[noreturn]] void throw_with_nested_identifiable(Exception&& exception)
{
std::throw_with_nested(make_identifiable_exception(std::forward<Exception>(exception)));
}
Live On Coliru
Any time you want more functionality, you can tweak identifiable_base
and identifiable_exception
to support what you want.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With