Exceptions

All C++ exceptions are converted to Python exceptions and all Python exceptions are converted to C++ exceptions, to allow exception propagation through multiple levels of callbacks, while retaining the option to handle the outstanding exception as needed in either language. To preserve an exception across the language boundaries, it must derive from std::exception. If preserving the exception (or its type) is not possible, generic exceptions are used to propagate the exception: Exception in Python or CPyCppyy::PyException in C++.

In the most common case of an instance of a C++ exception class derived from std::exception that is thrown from a compiled library and which is copyable, the exception can be caught and handled like any other bound C++ object (or with Exception on the Python and std::exception on the C++ side). If the exception is not copyable, but derived from std::exception, the result of its what() reported with an instance of Python’s Exception. In all other cases, including exceptions thrown from interpreted code (due to limitations of the Clang JIT), the exception will turn into an instance of Exception with a generic message.

The standard C++ exceptions are explicitly not mapped onto standard Python exceptions, since other than a few simple cases, the mapping is too crude to be useful as the typical usage in each standard library is too different. Thus, for example, a thrown std::runtime_error instance will become a cppyy.gbl.std.runtime_error instance on the Python side (with Python’s Exception as its base class), not a RuntimeError instance.

The C++ code used for the examples below can be found here, and it is assumed that that code is loaded at the start of any session. Download it, save it under the name features.h, and load it:

>>> import cppyy
>>> cppyy.include('features.h')
>>>

In addition, the examples require the throw to be in compiled code. Save the following and build it into a shared library libfeatures.so (or libfeatures.dll on MS Windows):

#include "features.h"

void throw_an_error(int i) {
    if (i) throw SomeError{"this is an error"};
    throw SomeOtherError{"this is another error"};
}

And load the resulting library:

>>> cppyy.load_library('libfeatures')
>>>

Then try it out:

>>> cppyy.gbl.throw_an_error(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
cppyy.gbl.SomeError: void ::throw_an_error(int i) =>
    SomeError: this is an error
>>>

Note how the full type is preserved and how the result of what() is used for printing the exception. By preserving the full C++ type, it is possible to call any other member functions the exception may provide beyond what or access any additional data it carries.

To catch the exception, you can either use the full type, or any of its base classes, including Exception and cppyy.gbl.std.exception:

>>> try:
...     cppyy.gbl.throw_an_error(0)
... except cppyy.gbl.SomeOtherError as e:  # catch by exact type
...     print("received:", e)
...
received: <cppyy.gbl.SomeOtherError object at 0x7f9e11d3db10>
>>> try:
...     cppyy.gbl.throw_an_error(0)
... except Exception as e:                 # catch through base class
...     print("received:", e)
...
received: <cppyy.gbl.SomeOtherError object at 0x7f9e11e00310>
>>>