Search code examples
c++exceptionlanguage-designcoercionsubtyping

Exception handling and coercion


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?


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.