Exception handling and coercion
Asked Answered
G

2

12
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?

Gallic answered 26/2, 2013 at 20:12 Comment(7)
New objects aren't created when you throw an exception to try to match an exception handler.Cajolery
Because Derived() is a Base and can be bound by a Base&, but "coercion" is not a std::string. The catch clauses capture the existing object.Trapeziform
What would happen if there was a second catch block taking a type myOwnString? Wouldn't that be ambiggy?Gerrard
@Gerrard Well, what if there was a second catch block taking a different Base?Gallic
Any particular reason why this question still doesnt have an accepted answer?Limelight
@elmes I usually accept answers in bursts. Are you on a rep hunt? :)Gallic
@FredOverflow Just wondered what else could I add, or what did I describe wrong..Limelight
L
18

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.

Limelight answered 26/2, 2013 at 20:41 Comment(0)
S
8

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.

[ ... ]

Semination answered 26/2, 2013 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.