2484. rethrow_if_nested() is doubly unimplementable

Section: 17.9.8 [except.nested] Status: C++17 Submitter: Stephan T. Lavavej Opened: 2015-03-27 Last modified: 2017-07-30

Priority: 2

View all other issues in [except.nested].

View all issues with C++17 status.

Discussion:

rethrow_if_nested() wants to determine "If the dynamic type of e is publicly and unambiguously derived from nested_exception", but 7.6.1.7 [expr.dynamic.cast] specifies that dynamic_cast has a couple of limitations.

First, nonpolymorphic inputs. These could be non-classes, or nonpolymorphic classes. The Standardese handles non-classes, although implementers need special logic. (If E is int, the dynamic type can't possibly derive from nested_exception. Implementers need to detect this and avoid dynamic_cast, which would be ill-formed due to 7.6.1.7 [expr.dynamic.cast]/2.) The Standardese is defective when E is a nonpolymorphic class. Consider the following example:

struct Nonpolymorphic { };
struct MostDerived : Nonpolymorphic, nested_exception { };
MostDerived md;
const Nonpolymorphic& np = md;
rethrow_if_nested(np);

According to 3.18 [defns.dynamic.type], the dynamic type of np is MostDerived. However, it's physically impossible to discover this, and attempting to do so will lead to an ill-formed dynamic_cast (7.6.1.7 [expr.dynamic.cast]/6). The Standardese must be changed to say that if E is nonpolymorphic, nothing happens.

Second, statically good but dynamically bad inputs. Consider the following example:

struct Nested1 : nested_exception { };
struct Nested2 : nested_exception { };
struct Ambiguous : Nested1, Nested2 { };
Ambiguous amb;
const Nested1& n1 = amb;
rethrow_if_nested(n1);

Here, the static type Nested1 is good (i.e. publicly and unambiguously derived from nested_exception), but the dynamic type Ambiguous is bad. The Standardese currently says that we have to detect the dynamic badness, but dynamic_cast won't let us. 7.6.1.7 [expr.dynamic.cast]/3 and /5 are special cases (identity-casting and upcasting, respectively) that activate before the "run-time check" behavior that we want (/6 and below). Statically good inputs succeed (regardless of the dynamic type) and statically bad inputs are ill-formed (implementers must use type traits to avoid this).

It might be possible to implement this with clever trickery involving virtual base classes, but implementers shouldn't be asked to do that. It would definitely be possible to implement this with a compiler hook (a special version of dynamic_cast), but implementers shouldn't be asked to do so much work for such an unimportant case. (This case is pathological because the usual way of adding nested_exception inheritance is throw_with_nested(), which avoids creating bad inheritance.) The Standardese should be changed to say that statically good inputs are considered good.

Finally, we want is_base_of's "base class or same class" semantics. If the static type is nested_exception, we have to consider it good due to dynamic_cast's identity-casting behavior. And if the dynamic type is nested_exception, it is definitely good.

[2015-05, Lenexa]

WB: so the is_polymorphic trait must be used?
STL and JW: yes, that must be used to decide whether to try using dynamic_cast or not.
JW: I'd already made this fix in our implementation
STL: the harder case also involves dynamic_cast. should not try using dynamic_cast if we can statically detect it is OK, doing the dynamic_cast might fail.
STL: finally, want "is the same or derived from" behaviour of is_base_of
WB: should there be an "else no effect" at the end? We have "Otherwise, if ..." and nothing saying what if the condition is false.
TP I agree.
MC: move to Ready and bring to motion on Friday
7 in favor, none opposed

Proposed resolution:

This wording is relative to N4296.

  1. Change 17.9.8 [except.nested] as depicted:

    template <class E> void rethrow_if_nested(const E& e);
    

    -9- Effects: If E is not a polymorphic class type, there is no effect. Otherwise, if the static type or the dynamic type of e is nested_exception or is publicly and unambiguously derived from nested_exception, calls dynamic_cast<const nested_exception&>(e).rethrow_nested().