Document number: N2509=08-0019

Alisdair Meredith
2008-02-04

Nesting Exceptions

A popular feature of exception frameworks in other successful languages, notably Java and the .NET framework, is the ability to nest a caught exception as part of the context within a new exception when performing exception translation. Typically this is supported by requiring all exceptions to derive from a common base class, which can store a reference to a nested exception of the same type.

While C++ does not enforce a common base class for all exceptions, the standard library provides an exception framework for its own use that is rooted on the class std::exception. This class is also a popular base for many user-supplied exception hierarchies, so one aproach would be to extend this base exception class to support nesting.

A more flexible approach would be to provide a mixin class, that captures and stores a copy of the currently handled exception. This is available through the current_exception API adopted at the Oxford 2007 meeting, see n2079 for details. If this mixin class has a virtual destructor then it can be detected through a dynamic_cast, allowing users an effective query method for all exception types.

To simplify use, the library could provide a factory function template to throw an exception derived from the user requested type, and the new nested exception holder type. This function template must take special case to handle non-class types, unions and [[final]] classes that cannot be derived from, and classes already derived from the nested exception wrapper as we rely on this base being non-ambiguous. Rather than ban these types with a requires clause, it is preferred to simply throw the exception as provided in these cases, so support use generic code.

As the factory function template throws a class publicly derived from both the original type and the new nested wrapper (or just the original type) it is always safe to migrate to the new idiom as all the existing exception handlers will be activated under the same conditions.

Example

The first example shows exception translation, where a function guarantees all exceptions it throws are of a certain type. To avoid losing the whole context, the original exception is now nested inside the new type.

  void some_function() {
    try {
    // Do some work
    }
    catch( ... ) {
      // translate all exceptions into my_failure exceptions
      // nest the original exception inside for future recovery
      throw_with_nested( my_failure() );
    }
  }

To recover context, use dynamic_cast to test for the presence of a nested exception.

  void test() {
    try {
      some_function();
    }
    catch( my_failure const & e ) {
      // Any recover code here
      if( std::nested_exception const * n = dynamic_cast< std::nested_exception const * >( &e ) ) {
        try {
          n->rethrow_nested();
        }
        catch ( another_failure const & ){
          // handle additional cleanup for known nested type
          // otherwise allow nested exception to propogate
        }
      }
    }
  }

Further Applications

Extending bad_cast

While rejecting the idea of extending the std::exception base class, it is noted that the whole purpose of std::bad_exception is to report a problem with the currently active exception. It is therefore proposed that std::bad_exception be required to derive publicly from both std::exception and std::nested_exception.

Automate nested rethrows

The awkward dynamic_cast from the example could be shielded from users with a simple class template:

  template< typename E >
  void rethrow_if_nested( E const & e ) {
      if( std::nested_exception const * ex = dynamic_cast< std::nested_exception const * >( &e ) ) {
          ex->rethrow_nested();
      }
  }

Alternative Scheme

The proposed API offers a function that will throw an exception nesting the currently handled exception inside. An alternative would be to supply a factory function that returned an appropriate-but-unspecified type to be thrown. This could be captured with a new auto declaration, or invoked directly from a throw expression. The same nested_exception specification would follow.

Reference Implementation

namespace std {
  class nested_exception {
  public:
    nested_exception() : ex( current_exception() ) {}
    nested_exception( const nested_exception & ) = default;
    nested_exception& operator=( const nested_exception & ) = default;
    virtual ~nested_exception() = default;

    // access functions
    void rethrow_nested() const [[noreturn]] {
      rethrow_exception( ex );
    }
    exception_ptr nested_ptr() const { return ex; }
    
  private:
    exception_ptr ex;
  };

  template< typename T > struct nested__wrapper : T, nested_exception {
    nested__wrapper( T && ) : T( t ), nested_exception() {}
  };

  template< typename T > void throw_with_nested( T&& t ) [[noreturn]] {
    if( !is_class< T, nested_exception>::value ) {
      throw t; // cannot derive from unions or fundamental types
    }
    else if( is_base_of< T, nested_exception>::value ) {
      throw t; // avoid ambiguous base, context already captured
    }
    else {
      throw nested__wrapper<T>( T );
    }
  }
}

Proposed Wording

Note that this wording assumes the acceptance of the attributes proposal N2418. If this feature is not available at the time this wording is added to the working paper, remove all use of [[noreturn]] attributes, and consider them guidance should that proposal be adopted later.

Add the following to 18.7p1 [support.exception]

namespace std {
  class exception;
  class bad_exception;
  class nested_exception;
  
  typedef void (*unexpected_handler)();
  unexpected_handler set_unexpected(unexpected_handler f) throw();
  void unexpected() [[noreturn]];
  
  typedef void (*terminate_handler)();
  terminate_handler set_terminate(terminate_handler f) throw();
  void terminate() [[noreturn]];
  
  bool uncaught_exception() throw();
  
  typedef unspec exception_ptr;
  
  exception_ptr current_exception();
  void rethrow_exception(exception_ptr p) [[noreturn]];
  template<class E> exception_ptr copy_exception(E e);

  template< typename T > void throw_with_nested( T&& t ) [[noreturn]];
  template< typename E > void rethrow_if_nested( const E & e );
}

Make the following additions to 18.7.2.1 Class bad_exception [bad.exception]

namespace std {
  class bad_exception : public exception, public nested_exception {
  public:
    bad_exception() throw();
    bad_exception(const bad_exception&) throw();
    bad_exception& operator=(const bad_exception&) throw();
    virtual const char* what() const throw();
  };
}

Add the following as
18.7.6 [except.nested]

namespace std {
class nested_exception {
public:
  nested_exception() throw();
  nested_exception( const nested_exception & ) throw() = default;
  nested_exception& operator=( const nested_exception & ) throw() = default;
  virtual ~nested_exception() = default;

  // access functions
  void rethrow_nested() const [[noreturn]];
  exception_ptr nested_ptr() const;
};

template< typename T >
void throw_with_nested( T&& t ) [[noreturn]];

nested_exception is a class designed for use as a mixin through multiple inheritence. It captures the currently handled exception, and stores it for later use.

[Note: nested_exception has a virtual destructor to make it a polymorphic class. Its presence can be tested for with dynamic_cast. --end note]

18.7.6.1 Constructors [except.nested.constr]

nested_exception() throw();

Effects: The nested_exception default constructor shall call current_exception and store the returned value.

18.7.6.2 Nested exception access functions [except.nested.access]

void rethrow_nested() const [[noreturn]];

Throws: the stored exception captured by this nested_ptr object.

exception_ptr nested_ptr() const;

Returns: The exception_ptr object captured by the initial constructor.

18.7.6.3 Nested exception utilities [except.nested.utility]

template< typename T > void throw_with_nested( T&& t ) [[noreturn]];

Requires: T is a CopyConstructible type.

Throws: If T is a non-union class type not derived from nested_exception, an exception of unspecified type that is publicly derived from both T and nested_exception. Otherwise, t.

The thrown exception shall call the copy or move constructor for T with the value of t when initializing that base class member.

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

Effects: Iff e is publicly derived from nested_exception then calls e.rethrow_nested().

Acknowledgements

The inception of this paper was a proposal by Bronek Koziki to the BSI panel modifying std::exception to achieve the same result. After the author suggested these alterations there was much discussion from the whole panel that helped distil the interface presented here.