Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ deduce the type of a nested exception

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()));
like image 540
Richard Hodges Avatar asked May 16 '16 14:05

Richard Hodges


2 Answers

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

like image 150
Jarod42 Avatar answered Sep 29 '22 01:09

Jarod42


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.

like image 43
Luc Danton Avatar answered Sep 29 '22 01:09

Luc Danton