user2209008
user2209008

Reputation:

Boost.Python failing to resolve derived type passed to function

I've run into yet another snag in my Boost.Python adventure.

I have the following Python module defined:

#include <Python.h>
#include <iostream>
#include <boost/python.hpp>

using namespace boost;
using namespace boost::python;

struct Base { };

void foo(boost::shared_ptr<Base>)
{
    std::cout << "yay\n";
}

BOOST_PYTHON_MODULE(Test)
{
    class_<Base, shared_ptr<Base>, noncopyable>("Base", init<>());

    def("foo", foo);
}

Running the following script:

from Test import *

class Bar(Base):
    def __init__(self):
        pass

foo(Base()) #works
foo(Bar()) #error

The last line yields an error to the effect of this:

Python argument types in
    foo(Bar)
did not match C++ signature:
    foo(class boost::shared_ptr<struct Base>)

Now my question is why doesn't this work? Surely the type system should be able to work out the Bar is a Base instance?

http://coliru.stacked-crooked.com/a/43f111fb3032a20a

Any help is appreciated!

Upvotes: 0

Views: 454

Answers (1)

Tanner Sansbury
Tanner Sansbury

Reputation: 51881

In this particular case, the error message is misleading. The function receives an argument with the right type; however, the argument has an inappropriate value. The Bar initializer is not initializing the Python Base part of its hierarchy. The Python instance does not contain a boost::shared_ptr<Base> instance, resulting in Boost.Python failing to dispatch to the C++ function:

class Bar(Base):
    def __init__(self):
        pass # Base is not initialized.

fun(Base()) # No boost::shared_ptr<Base> instance.

To resolve the issue, explicitly invoke Base.__init__() within Bar.__init__():

class Bar(Base):
    def __init__(self):
        Base.__init__(self) # Instantiates boost::shared_ptr<Base>.

fun(Bar()) # Boost.Python will extract boost::shared_ptr<Base> from Bar().

For details, in Python if a derived class defines an __init__() method, then it should explicitly invoke the parent class' __init__() method. The Python documentation states:

If a base class has an __init__() method, the derived class’s __init__() method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example: BaseClass.__init__(self, [args...]).

In Boost.Python, C++ class wrappers have an instance_holder. These objects hold C++ instances within their Python object wrapper, and instantiation of the C++ object occurs within the Python object's __init__ function:

When an __init__ function for a wrapped C++ class is invoked, a new instance_holder instance is created and installed in the Python object [...]

Therefore, if the Python object's __init__() method is not invoked, the internal C++ object will not be instantiated. When an exposed C++ function is invoked from Python, Boost.Python will examine the invocation arguments, attempting to identify a matching C++ function within a set of exposed functions. When no match is found, it will raise a Boost.Python.ArgumentError exception, listing the argument types and the set of C++ functions for which it failed to match.


Here is a complete example demonstrating having two different Python types inherit from an exposed C++ type, where one hierarchy is initialized properly and the other is not:

#include <boost/python.hpp>

struct base {};

void foo(boost::shared_ptr<base>) {}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<base, boost::shared_ptr<base>, boost::noncopyable>(
      "Base", python::init<>())
    ;

  python::def("foo", &foo);
}

Interactive usage:

>>> import example
>>> class GoodDerived(example.Base):
...     def __init__(self):
...         example.Base.__init__(self)
...
>>> class BadDerived(example.Base):
...     def __init__(self):
...         pass
...
>>> assert(isinstance(GoodDerived(), example.Base))
>>> assert(isinstance(BadDerived(), example.Base))
>>> try:
...     example.foo(GoodDerived())
...     got_exception = False
... except:
...     got_exception = True
... finally:
...     assert(not got_exception)
...
>>> try:
...     example.foo(BadDerived())
...     got_exception = False
... except:
...     got_exception = True
... finally:
...     assert(got_exception)

Note that while the type hierarchy is correct and verifiable via isinstance((), the types do not indicate if an instance has an appropriate value.

Upvotes: 2

Related Questions