Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost python explicit typecast needed

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.

like image 629
Dewfy Avatar asked Feb 10 '26 01:02

Dewfy


2 Answers

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.

like image 127
Tanner Sansbury Avatar answered Feb 12 '26 14:02

Tanner Sansbury


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.

like image 23
André Anjos Avatar answered Feb 12 '26 15:02

André Anjos