title: Polymorphic exception handling: How to catch subclass exception?
body: <p>I have the following simple hierarchy of two C++ exceptions:</p>
  
  <pre><code>class LIB_EXP ClusterException : public std::exception {
  public:
      ClusterException() { }
      ClusterException(const std::string&amp; what) { init(what); }
      virtual const char* what() const throw() { return what_.c_str(); }
      virtual ~ClusterException() throw() {}
      virtual ClusterException* clone() { return new ClusterException(*this);  } 
  protected:
      void init(const std::string&amp; what) { what_ = what; }
  private:
      std::string what_;
  };
  
  class LIB_EXP ClusterExecutionException : public ClusterException {
  public:
      ClusterExecutionException(const std::string&amp; jsonResponse);
      std::string getErrorType() const throw() { return errorType_; }
      std::string getClusterResponse() const throw() { return clusterResponse_; }
      virtual ~ClusterExecutionException() throw() {}
      virtual ClusterExecutionException* clone() { return new ClusterExecutionException(*this);  } 
  private:
      std::string errorType_;
      std::string clusterResponse_;
  };
  </code></pre>
  
  <p>I then export them to Python with Boost-Python as follows. Note my use of the <code>bases</code> to make sure that the inheritance relationship is preserved in the translation:</p>
  
  <pre><code>class_&lt;ClusterException&gt; clusterException("ClusterException", no_init);
  clusterException.add_property("message", &amp;ClusterException::what);
  clusterExceptionType = clusterException.ptr();
  register_exception_translator&lt;ClusterException&gt;(&amp;translateClusterException);
  
  class_&lt;ClusterExecutionException, bases&lt;ClusterException&gt; &gt; clusterExecutionException("ClusterExecutionException", no_init);
  clusterExecutionException.add_property("message", &amp;ClusterExecutionException::what)
                           .add_property("errorType", &amp;ClusterExecutionException::getErrorType)
                           .add_property("clusterResponse", &amp;ClusterExecutionException::getClusterResponse);
  clusterExecutionExceptionType = clusterExecutionException.ptr();
  register_exception_translator&lt;ClusterExecutionException&gt;(&amp;translateClusterExecutionException);
  </code></pre>
  
  <p>Then the exception translation method:</p>
  
  <pre><code>static PyObject *clusterExceptionType = NULL;
  static void translateClusterException(ClusterException const &amp;exception) {
    assert(clusterExceptionType != NULL); 
    boost::python::object pythonExceptionInstance(exception);
    PyErr_SetObject(clusterExceptionType, pythonExceptionInstance.ptr());
  }
  
  static PyObject *clusterExecutionExceptionType = NULL;
  static void translateClusterExecutionException(ClusterExecutionException const &amp;exception) {
    assert(clusterExecutionExceptionType != NULL);
    boost::python::object pythonExceptionInstance(exception);
    PyErr_SetObject(clusterExecutionExceptionType, pythonExceptionInstance.ptr());
  }
  </code></pre>
  
  <p>I created the following test C++ function that throws the exceptions:</p>
  
  <pre><code>static void boomTest(int exCase) {
    switch (exCase) {
      case 0:  throw ClusterException("Connection to server failed");
               break;
      case 1:  throw ClusterExecutionException("Error X while executing in the cluster");
               break;
      default: throw std::runtime_error("Unknown exception type");
    }
  }
  </code></pre>
  
  <p>Finally the Python test code that calls my C++ <code>boomTest</code>:</p>
  
  <pre><code>import cluster
  reload(cluster)
  from cluster import ClusterException, ClusterExecutionException
  
  def test_exception(exCase):
      try:
          cluster.boomTest(exCase)
  
      except ClusterException as ex:
          print 'Success! ClusterException gracefully handled:' \
              '\n message="%s"' % ex.message
      except ClusterExecutionException as ex:
          print 'Success! ClusterExecutionException gracefully handled:' \
              '\n message="%s"' \
              '\n errorType="%s"' \
              '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)
      except:
          print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])
  
  def main():
      print '\n************************ throwing ClusterException ***********************************************************************'
      test_exception(0)
      print '\n************************ throwing ClusterExecutionException **************************************************************'
      test_exception(1)
      print '\n************************ throwing std::runtime_error *********************************************************************'
      test_exception(2)
  
  if __name__ == "__main__":
      main()
  </code></pre>
  
  <p>Up to here it all works. However if I remove the <code>ClusterExecutionException</code> catch handler from Python then this exception will be caught and fallback to an unknown exception instead of being caught as its base <code>ClusterException</code>.</p>
  
  <p>I have tried in Boost-Python while registering the exception translation of <code>ClusterExecutionException</code> to register it as its base <code>ClusterException</code> and then it gets caught "polymorphically" but then it won't be caught as a <code>ClusterExecutionException</code>. How can make it so that <code>ClusterExecutionException</code> gets caught as both <code>ClusterException</code> and <code>ClusterExecutionException</code>? I have tried of course registering this <code>ClusterExecutionException</code> exception as both <code>ClusterException</code> and <code>ClusterExecutionException</code> but it follows a last wins strategy, and only one works not both.</p>
  
  <p>Is there any other way to solve this? </p>
  
  <p><strong>UPDATE 1:</strong> The wholy grail of this problem is to find out on the C++ side the type of the <code>except</code> Python statement e.g. <code>except ClusterException as ex:</code> which is unknown once inside the C++ side. The exception translate by Boost.Python will call the translate function that corresponds to the dynamic type of the exception and the Python catch static type is not known.</p>
  
  <p><strong>UPDATE 2:</strong> As suggested changing the Python code to the following i.e. adding <code>print(type(ex).__bases__)</code> gives:</p>
  
  <pre><code>def test_exception(exCase):
      try:
          cluster.boomTest(exCase)
  
      except ClusterException as ex:
          print(type(ex).__bases__)
          print 'Success! ClusterException gracefully handled:' \
              '\n message="%s"' % ex.message
      except ClusterExecutionException as ex:
          print(type(ex).__bases__)
          print 'Success! ClusterExecutionException gracefully handled:' \
              '\n message="%s"' \
              '\n errorType="%s"' \
              '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)
      except:
          print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])
  </code></pre>
  
  <p>and the output:</p>
  
  <pre><code>************************ throwing ClusterException ***********************************************************************
  (&lt;type 'Boost.Python.instance'&gt;,)
  Success! ClusterException gracefully handled:
   message="Connection to server failed"
  
  ************************ throwing ClusterExecutionException **************************************************************
  (&lt;class 'cluster.ClusterException'&gt;,)
  Success! ClusterExecutionException gracefully handled:
   message="Error X while executing in the cluster"
   errorType="LifeCycleException"
   clusterResponse="{ "resultStatus": "Error", "errorType": "LifeCycleException", "errorMessage": "Error X while executing in the cluster" }"
  </code></pre>
  
  <p>Meaning that the inheritance relationship is "seen" from Python. But the polymorphic handling still doesn't work.</p>
  
  <p><strong>UPDATE 3</strong> This is the output of running VS <code>dumpbin.exe</code>:</p>
  
  <p>The command I used is:</p>
  
  <pre><code>dumpbin.exe /EXPORTS /SYMBOLS C:\ClusterDK\x64\Debug\ClusterDK.dll &gt; c:\temp\dumpbin.out
  </code></pre>
  
  <p>and the relevant parts of the output:</p>
  
  <pre><code>Microsoft (R) COFF/PE Dumper Version 11.00.50727.1
  Copyright (C) Microsoft Corporation.  All rights reserved.
  
  Dump of file C:\ClusterDK\x64\Debug\ClusterDK.dll
  
  File Type: DLL
  
  Section contains the following exports for ClusterDK.dll
  
  00000000 characteristics
  5A1689DA time date stamp Thu Nov 23 09:42:02 2017
      0.00 version
         1 ordinal base
        78 number of functions
        78 number of names
  
  ordinal hint RVA      name
  
        8    7 00004485 ??0ClusterException@cluster@@QEAA@AEBV01@@Z = @ILT+13440(??0ClusterException@cluster@@QEAA@AEBV01@@Z)
        9    8 00001659 ??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+1620(??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
       10    9 00001F1E ??0ClusterException@cluster@@QEAA@XZ = @ILT+3865(??0ClusterException@cluster@@QEAA@XZ)
       11    A 00004D4F ??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z = @ILT+15690(??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z)
       12    B 000010AA ??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+165(??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
       27   1A 000035D0 ??1ClusterException@cluster@@UEAA@XZ = @ILT+9675(??1ClusterException@cluster@@UEAA@XZ)
       28   1B 00003C7E ??1ClusterExecutionException@cluster@@UEAA@XZ = @ILT+11385(??1ClusterExecutionException@cluster@@UEAA@XZ)
       37   24 00002BD5 ??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+7120(??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z)
       38   25 000034D1 ??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+9420(??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z)
       46   2D 000D2220 ??_7ClusterException@cluster@@6B@ = ??_7ClusterException@cluster@@6B@ (const cluster::ClusterException::`vftable')
       47   2E 000D2248 ??_7ClusterExecutionException@cluster@@6B@ = ??_7ClusterExecutionException@cluster@@6B@ (const cluster::ClusterExecutionException::`vftable')
       52   33 00004BB5 ?clone@ClusterException@cluster@@UEAAPEAV12@XZ = @ILT+15280(?clone@ClusterException@cluster@@UEAAPEAV12@XZ)
       53   34 00004D31 ?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ = @ILT+15660(?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ)
       61   3C 00001D43 ?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ = @ILT+3390(?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)
       69   44 0000480E ?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+14345(?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
       78   4D 000032FB ?what@ClusterException@cluster@@UEBAPEBDXZ = @ILT+8950(?what@ClusterException@cluster@@UEBAPEBDXZ)
  
  Summary
  
      4000 .data
      5000 .idata
     12000 .pdata
     54000 .rdata
      2000 .reloc
      1000 .rsrc
     C9000 .text
      1000 .tls
  </code></pre>