Discussion:
[C++-sig] Boost python exposing abstract class , function returning boost::shared_ptr
warin
2015-03-27 13:27:11 UTC
Permalink
I'm not top posting.
I have an abstract class A2, with derived class B2.
I want to map a function returning a boost::shared_ptr of A.
This abstract class is wrapped :

struct A2{
virtual ~A2(){}
virtual int ret() =0;
};

struct B2: public A2{
virtual ~B2(){}
int ret() { return 1; }
};

// wrapper
struct A2Wrap : A2, wrapper<A2>
{
inline int ret()
{
return this->get_override("ret")();
}
};

// function to map
boost::shared_ptr<A2> f1()
{
return boost::shared_ptr<A2>(new B2());
}

// expose to python
BOOST_PYTHON_MODULE(grids)
{
class_<A2Wrap, boost::shared_ptr<A2Wrap> , boost::noncopyable >("A2")
.def("ret",pure_virtual(&A2::ret));

class_<B2,boost::shared_ptr<B2>,bases<A2>>("B2");
def("f1",f1);
}
import grids
a = grids.f1()
Traceback (most recent call last):

File "<stdin>", line 1, in <module>
TypeError: No to_python (by-value) converter found for C++ type:
boost::shared_ptr<A2>

Any idea for this problem ? I imagine to express the conversion from
boost::shared_ptr<A2> to boost::shared_ptr<A2Wrap>.
Holger Joukl
2015-03-27 15:45:00 UTC
Permalink
Hi,
Post by warin
[...]
// expose to python
BOOST_PYTHON_MODULE(grids)
{
class_<A2Wrap, boost::shared_ptr<A2Wrap> , boost::noncopyable >("A2")
.def("ret",pure_virtual(&A2::ret));
class_<B2,boost::shared_ptr<B2>,bases<A2>>("B2");
def("f1",f1);
}
import grids
a = grids.f1()
File "<stdin>", line 1, in <module>
boost::shared_ptr<A2>
What happens if you expose like this:

class_<A2, boost::shared_ptr<A2Wrap> , boost::noncopyable >("A2")
^^
|- original class here, not the callback-wrapper class

?

Holger


Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart
warin
2015-03-28 13:58:20 UTC
Permalink
I'm not top posting.
Hi Holger


Thank you for the reply.
It compiles but same error on execution.
Sincerely yours

Xavier
Holger Joukl
2015-03-30 09:09:13 UTC
Permalink
Hi,
Post by warin
Thank you for the reply.
It compiles but same error on execution.
If I change the A2 exposure to

bp::class_<A2, boost::shared_ptr<A2>, boost::noncopyable>("A2",
bp::no_init)
...

then f1() successfully returns a B2 object. But of course we then lose
the Python-callback abilities provided by the wrapper class.

Looking into the Boost.Python tests i stumbled across shared_ptr.cpp
which contains:
...
// This is the ugliness required to register a to-python converter
// for shared_ptr<A>.
objects::class_value_wrapper<
shared_ptr<A>
, objects::make_ptr_instance<A,
objects::pointer_holder<shared_ptr<A>,A> >
Post by warin
();
...


So, giving this a try - looks like the following seems to be a working
version for your sample
(note that I changed the extension module name to shared_ptr_hierarchy
in my code):


C++ (wrapper) code:
==================

#include <boost/python.hpp>

namespace bp = boost::python;


struct A2
{
virtual ~A2(){}
virtual int ret() =0;
};


struct B2: public A2
{
virtual ~B2(){}
int ret() { return 1; }
};


// wrapper
struct A2Wrap : A2, bp::wrapper<A2>
{
inline int ret()
{
return this->get_override("ret")();
}
};


// To the best of my knowledge we need to add callback support
// through a wrapper class at every level of the hierarchy, so
// a bit of code duplication going on. This is due to the fact
// that B2 has no inheritance relation whatsoever to A2Wrap.
// I haven't so far found an elegant/short solution for this
// (e.g. using templates or macros)
struct B2Wrap : B2, bp::wrapper<B2>
{
inline int ret()
{
if (bp::override py_override = this->get_override("ret")) {
return py_override();
}
return B2::ret();
}

inline int default_ret()
{
return B2::ret();
}
};


// function to map
boost::shared_ptr<A2> f1()
{
return boost::shared_ptr<A2>(new B2());
}


// invoke overridden virtual from C++ side (check callback-ability)
int invoke_ret(A2 & a2)
{
return a2.ret();
}


// expose to python
BOOST_PYTHON_MODULE(shared_ptr_hierarchy)
{

bp::class_<A2Wrap, boost::shared_ptr<A2Wrap>, boost::noncopyable>("A2")
.def("ret", bp::pure_virtual(&A2::ret))
;

// taken from boost.python libs/python/test/shared_ptr.cpp:
// This is the ugliness required to register a to-python converter
// for shared_ptr<A>.
bp::objects::class_value_wrapper<
boost::shared_ptr<A2>
, bp::objects::make_ptr_instance<A2,
bp::objects::pointer_holder<boost::shared_ptr<A2>,A2> >
Post by warin
();
bp::class_<B2Wrap, boost::shared_ptr<B2Wrap>, bp::bases<A2>,
boost::noncopyable >("B2")
.def("ret", &B2::ret, &B2Wrap::default_ret)
;

bp::def("f1", f1);

bp::def("invoke_ret", &invoke_ret, (bp::arg("a2")));
}


Python test code:
=================

import shared_ptr_hierarchy


class PyB2(shared_ptr_hierarchy.A2):
def ret(self):
print "PyB2 Python class", self
return 5

class PyC2(shared_ptr_hierarchy.B2):
def ret(self):
print "PyC2 Python class", self
return 6


def invoke(factory):
print "\nfactory:", factory
obj = factory()
print "obj =", obj
print "obj.ret() ->", obj.ret()
if hasattr(shared_ptr_hierarchy, 'invoke_ret'):
print "shared_ptr_hierarchy.invoke_ret(obj) ->", \
shared_ptr_hierarchy.invoke_ret(obj)

try:
invoke(shared_ptr_hierarchy.A2)
except Exception as e:
print "Exception:", e
invoke(shared_ptr_hierarchy.B2)
invoke(shared_ptr_hierarchy.f1)
invoke(PyB2)
invoke(PyC2)


Test code output:
=================

factory: <class 'shared_ptr_hierarchy.A2'>
obj = <shared_ptr_hierarchy.A2 object at 0x17cb5d0>
obj.ret() -> Exception: Pure virtual function called

factory: <class 'shared_ptr_hierarchy.B2'>
obj = <shared_ptr_hierarchy.B2 object at 0x17cb628>
obj.ret() -> 1
shared_ptr_hierarchy.invoke_ret(obj) -> 1

factory: <Boost.Python.function object at 0x177a460>
obj = <shared_ptr_hierarchy.B2 object at 0x17d0668>
obj.ret() -> 1
shared_ptr_hierarchy.invoke_ret(obj) -> 1

factory: <class '__main__.PyB2'>
obj = <__main__.PyB2 object at 0x17cb628>
obj.ret() -> PyB2 Python class <__main__.PyB2 object at 0x17cb628>
5
shared_ptr_hierarchy.invoke_ret(obj) -> PyB2 Python class <__main__.PyB2
object at 0x17cb628>
5

factory: <class '__main__.PyC2'>
obj = <__main__.PyC2 object at 0x17cb628>
obj.ret() -> PyC2 Python class <__main__.PyC2 object at 0x17cb628>
6
shared_ptr_hierarchy.invoke_ret(obj) -> PyC2 Python class <__main__.PyC2
object at 0x17cb628>
6


I can't really say that I understand why that's needed, but alas...

Open issues:
- Would be nice to have the abstract class A2 not being initializable from
Python,
but we can't use bp::no_init and inherit from A2 in Python because the
Python-derived
needs to call the (A2) base class constructor


Holger

Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart
warin
2015-03-30 13:08:16 UTC
Permalink
Hi Holger

Thank you very much.
A little bit ugly but it works.
Even using std::shared_ptr instead of boost::shared_ptr
Best regards

Loading...