I am exposing my C++ classes via Boost.Python. My intention is to expose member variables that are of a user-defined class type with an internal reference. This worked fine until I decided to introduce a member variable of type boost::optional<T>.
There are several great posts that show how to expose boost::optional<T> as return by value. Specifically, I have implemented this converter. The other relevant pieces of my code look like this:
struct Bar {}
struct Foo {
boost::optional<Bar> bar;
}
BOOST_PYTHON_MODULE(mymodule) {
using namespace boost::python;
python_optional<Bar>(); //registering the converter
class_<Foo>("Foo")
.add_property ( "bar", make_getter(&Foo::bar, return_value_policy<return_by_value>()), make_setter(&Foo::bar) )
;
}
I tried to replace return_value_policy<return_by_value>()
with either return_value_policy<reference_existing_object>()
or return_internal_reference<>()
. Both produced a Python TypeError:
>> import mymodule
>> foo = mymodule.Foo()
>> bar = foo.bar
TypeError: No Python class registered for C++ class boost::optional<Bar>
My understanding is that I now I am getting a reference to the boost::optional<T> object. The converter I registered is, however, not called because it expects a boost::optional<T> object and not a reference to such an object. I thought about changing the converter but I am new to this and I really do not know how. Any suggestions?
I found a workaround by adding a getter to struct Foo
that either returns a pointer to the object held by boost::optional or a nullptr in case of boost::none. As &*bar
returns const Bar*
I had to use const_cast
.
struct Bar {}
struct Foo {
Bar* getBar() { return (bar ? const_cast<Bar*>(&*bar) : nullptr); };
boost::optional<Bar> bar;
}
BOOST_PYTHON_MODULE(mymodule) {
using namespace boost::python;
python_optional<Bar>(); //registering the converter
class_<Foo>("Foo")
.add_property ( "bar", make_function(static_cast< Bar*(Foo::*)() >(&Foo::getBar), return_internal_reference<>() ), make_setter(&Foo::bar) )
;
}
In case bar
belongs to a base class of Foo
Python will produce an ArgumentError like this:
>> import mymodule
>> foo = mymodule.Foo()
>> foo.bar = mymodule.Bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument tpyes in
None.None(Foo, Bar)
did not match C++ signature:
None(FooBase {lvalue}, boost::optional<Bar>)
To solve the problem define a getter and a setter for bar
in class Foo
.
struct Bar {}
struct FooBase {
boost::optional<Bar> bar;
}
struct Foo : public FooBase {
Bar* getBar() { return (bar ? const_cast<Bar*>(&*bar) : nullptr); };
void setBar(const Bar* bar) { bar ? this->bar = *bar : this->bar = boost::none; }
}
void (Foo::*foo_bar_set)(const Bar*) = &Foo::setBar;
BOOST_PYTHON_MODULE(mymodule) {
using namespace boost::python;
python_optional<Bar>(); //registering the converter
class_<Foo>("Foo")
.add_property ( "bar", make_function(static_cast< Bar*(Foo::*)() >(&Foo::getBar), return_internal_reference<>() ), foo_bar_set )
;
}