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.
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 } } } }
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
.
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(); } }
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.
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 ); } } }
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()
.
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.