I have hybrid system (c++, boost python). In my c++ code there is very simple hierarchy
class Base{...}
class A : public Base{...}
class B : public Base{...}
2 more business (on c++) methods
smart_ptr<Base> factory() //this produce instances of A and B
void consumer(smart_ptr<A>& a) //this consumes instance of A
In python code I create instance of A with the factory and try to call consumer method:
v = factory() #I'm pretty sure that it is A instance
consumer(v)
Absolutely reasonable I've got exception:
Python argument types in consumer(Base) did not match to C++ signature: consumer(class A{lvalue})
It happens because no way how to tell Boost that some conversion efforts should be there.
Is there some way how to specify dynamic casting behavior? Thank you in advance.
When it comes to boost::shared_ptr, Boost.Python generally provides the desired functionality. In this particular case, there is no need to explicitly provide custom to_python converters as long as the module declaration defines that Base is held by boost::shared_ptr<Base>, and Boost.Python is told that A inherits from Base.
BOOST_PYTHON_MODULE(example) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>,
boost::noncopyable>("Base", no_init);
class_<A, bases<Base>,
boost::noncopyable>("A", no_init);
def("factory", &factory);
def("consumer", &consumer);
}
Boost.Python does not currently support custom lvalue converters, as it requires changes to the core library. Therefore, the consumer function either needs accept the boost:shared_ptr<A> by value or by const-reference. Either of the following signatures should work:
void consumer(boost::shared_ptr<A> a)
void consumer(const boost::shared_ptr<A>& a)
Here is a complete example:
#include <boost/python.hpp>
#include <boost/make_shared.hpp>
class Base
{
public:
virtual ~Base() {}
};
class A
: public Base
{
public:
A(int value) : value_(value) {}
int value() { return value_; };
private:
int value_;
};
boost::shared_ptr<Base> factory()
{
return boost::make_shared<A>(42);
}
void consumer(const boost::shared_ptr<A>& a)
{
std::cout << "The value of object is " << a->value() << std::endl;
}
BOOST_PYTHON_MODULE(example) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>,
boost::noncopyable>("Base", no_init);
class_<A, bases<Base>,
boost::noncopyable>("A", no_init);
def("factory", &factory);
def("consumer", &consumer);
}
And the usage:
>>> from example import *
>>> x = factory()
>>> type(x)
<class 'example.A'>
>>> consumer(x)
The value of object is 42
>>>
Since the module declaration specified that Base was a base-class for A, Boost.Python was able to resolves the type returned from factory() to example.A.
Yes, there is. You have to declare your own "from-python" converter. It is vaguely explained on the boost.python documentation (look at this answer in the FAQ), but you will find tutorials around the net, like this one. Here is a complete example, based on your initial input that does what you want:
#include <boost/python.hpp>
#include <boost/make_shared.hpp>
class Base {
public:
virtual ~Base() {}
};
class A: public Base {
public:
A(int v): _value(v) {}
int _value;
};
boost::shared_ptr<Base> factory() {
return boost::make_shared<A>(27);
}
void consumer(boost::shared_ptr<A> a) {
std::cout << "The value of object is " << a->_value << std::endl;
}
struct a_from_base {
typedef typename boost::shared_ptr<A> container_type;
// Registers the converter for boost.python
a_from_base() {
boost::python::converter::registry::push_back(&convertible, &construct,
boost::python::type_id<container_type>());
}
// Determines convertibility: checks if a random
// object is convertible to boost::shared_ptr<A>
static void* convertible(PyObject* ptr) {
boost::python::object object(boost::python::handle<>(boost::python::borrowed(ptr)));
boost::python::extract<boost::shared_ptr<Base> > checker(object);
if (checker.check()) { //is Base
boost::shared_ptr<Base> base = checker();
if (boost::dynamic_pointer_cast<A>(base)) return ptr; //is A
}
return 0; //is not A
}
// Runs the conversion (here we know the input object *is* convertible)
static void construct(PyObject* ptr, boost::python::converter::rvalue_from_python_stage1_data* data) {
// This is some memory allocation black-magic that is necessary for bp
void* storage = ((boost::python::converter::rvalue_from_python_storage<container_type>*)data)->storage.bytes;
new (storage) container_type();
data->convertible = storage;
container_type& result = *((container_type*)storage); //< your object
// The same as above, but this time we set the provided memory
boost::python::object object(boost::python::handle<>(boost::python::borrowed(ptr)));
boost::shared_ptr<Base> base = boost::python::extract<boost::shared_ptr<Base> >(object);
result = boost::dynamic_pointer_cast<A>(base);
}
};
// Your boost python module: compile it and run your test
// You should get "The value of object is 27".
BOOST_PYTHON_MODULE(convertible) {
using namespace boost::python;
class_<Base, boost::shared_ptr<Base>, boost::noncopyable>("Base", no_init);
class_<A, boost::shared_ptr<A>, bases<Base>, boost::noncopyable>("A", no_init);
a_from_base();
def("factory", &factory);
def("consumer", &consumer);
}
You can extend this example by writing another from-python converter for your class B or, just template that structure above to accommodate all children of Base.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With