简体   繁体   中英

Exposing boost::optional<T> via Boost.Python as internal reference or None

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 )
  ;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM