Question

try
{
    throw Derived();
}
catch (Base&)
{
    std::cout << "subtyping\n";
}

try
{
    throw "lol";
}
catch (std::string)
{
    std::cout << "coercion\n";
}

Output:

subtyping
terminate called after throwing an instance of 'char const*'

Why does exception handling play nice with subtyping, but not with coercion?

Was it helpful?

Solution

Catching thrown exceptions is quite different from passing arguments to functions. There are similarities, but there are also subtle differences.

The 3 main differences are:

  • exceptions are always copied at least once (not possible to avoid at all)
  • catch clauses are examined in the order they are declared (not best-fit)
  • they are subject to fewer forms of type conversions:
    • inheritance-based coversions,
    • conversion from a typed to an untyped pointer (const void* catches any pointer)

Any other kind of conversion is not allowed (e.g. int to double, or implicit const char* to string - your example).

Regarding your question in the comment Suppose a hierarchy exists:

class Base {}; 
class Derived: public Base {};
class Base2 {};
class Leaf: public Derived, public Base2 {};

Now depending on the order of catch clauses, an appropriate block will be executed.

try {
    cout << "Trying ..." << endl;
    throw Leaf();

} catch (Base& b) {
    cout << "In Base&";

} catch (Base2& m) {
    cout << "In Base2&"; //unreachable due to Base&

} catch (Derived& d) {
    cout << "In Derived&";  // unreachable due to Base& and Base2&
}

If you switch Base and Base2 catch order you will notice a different behavior. If Leaf inherited privately from Base2, then catch Base2& would be unreachable no matter where placed (assuming we throw a Leaf)

Generally it's simple: order matters.

OTHER TIPS

Paragraph 15.3/3 of the C++11 Standard defines the exact conditions for a handler to be a match for a certain exception object, and these do not allow user-defined conversions:

A handler is a match for an exception object of type E if

— The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or

— the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or

— the handler is of type cv1 T* cv2 and E is a pointer type that can be converted to the type of the handler by either or both of

  • a standard pointer conversion (4.10) not involving conversions to pointers to private or protected or ambiguous classes

  • a qualification conversion

— the handler is a pointer or pointer to member type and E is std::nullptr_t.

[ ... ]

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top