1

I have two different classes that I want to expose using boost-python, but the constructor of the second one takes and array of the first one as argument and I can't figure out how to do it.

This is the definition of the classes:

class INT96{
public:
    uint64_t value[3];

    INT96(){};
    INT96(uint64_t x0, uint64_t x1, uint64_t x2);
    ...
};

template <unsigned k>
class Xi_CW{
    protected:
        INT96 A[k];

    public:
        Xi_CW(INT96 (&A)[k]);
        ...
};

And my attempt to expose them using boost-python:

using namespace boost::python;    
typedef Xi_CW<4> Xi_CW4;

BOOST_PYTHON_MODULE(xis)
{
    class_<INT96>("INT96", init<double,double,double>())
        [...]
    ;
    class_<Xi_CW4>("Xi_CW4", init<INT96[4]>())
        [...]
    ;
}

Which results in a "no known conversion error". I've tried several other possibilities but so far no luck...

Any idea how should I do it? Thanks

Ester
  • 33
  • 2
  • Yes, as with many bindings in boost python, you create a proxy and expose that instead. – CashCow Jan 28 '14 at 15:06
  • Sorry, I'm a newbie and I'm having a hard time understanding what you mean... could you elaborate a little bit more? Thanks! – Ester Jan 29 '14 at 13:32
  • Well for a start your class constructs from a fixed sized array which doesn't exist in Python. Think of what Python type you do really want to construct from and adapt it through a proxy class, i.e. you write a new class with an appropriate constructor which acts as a wrapper for the one you really want to construct. – CashCow Jan 29 '14 at 15:16

2 Answers2

0

I believe you could use boost::python::list for your constructor to receive an array of int96 and then convert it to a regular c++ array if you want.

Here's an example I found to convert python iterables to a c++ vector: https://stackoverflow.com/a/19092051/996197

Community
  • 1
  • 1
Steve
  • 194
  • 11
0

One solution is to suppress the Boost.Python default initializer and register a custon initializer that can construct a Xi_CW<4> object from an iterable Python object that contains INT96 objects.

  • The default initializer can be suppressed by providing boost::python::no_init as the init_spec when exposing the class via boost::python::class_.
  • An auxiliary function wrapped by boost::python::make_constructor() can be exposed as the __init__ method on a class.

Here is a complete example based on the types presented in the original question:

#include <iostream>
#include <boost/foreach.hpp>
#include <boost/python.hpp>

/// @brief Mockup spam class.
struct spam
{
  int value[3];
  spam() {};
  spam(int x, int y, int z)
  {
    value[0] = x;
    value[1] = y;
    value[2] = z;
  }

};

/// @brief Mockup egg class.
template <std::size_t N>
class egg
{
public:

  explicit egg(spam (&spams)[N]) : spams_(spams) {}

  /// @brief Return string reprenstation of the egg class.
  std::string to_string()
  {
    std::stringstream stream;
    stream << "[";
    BOOST_FOREACH(spam& s, spams_)
    {
      stream << "[" << s.value[0] << ", " 
                    << s.value[1] << ", "
                    << s.value[2]
             << "]";
    }
    stream << "]";
    return stream.str();
  }

private:
  spam spams_[N];
};

/// @brief Auxiliary function that will attempt to create an egg<N> type
///        from an iterable Python object that contains spam objects.
template <std::size_t N>
egg<N>* make_egg(boost::python::object object)
{
  spam spams[N];
  // Iterate over the python object, extracting and copying spam objects.
  // Boost.Python will handle throwing exceptions for the appropriate cases:
  // - object does not support indexing (__getitem__)
  // - object[N-1] is out of range
  // - object[i] does not contain or cannot be converted to a spam&
  for (long i = 0; i < N; ++i)
    spams[i] = boost::python::extract<spam&>(object[i]);

  // Construct egg from the local spam array.  Ownership of the object is
  // passed to Boost.Python
  return new egg<N>(spams);
}

/// @brief Expose an egg<N> type to Python.
///
/// @param name The name that will be exposed to Python.
template <std::size_t N>
boost::python::class_<egg<N> > expose_egg(const char* name)
{
  namespace python = boost::python;

  // Explicitly suppress the Boost.Python default constructor.
  typedef egg<N> egg_type;
  python::class_<egg_type> egg_class(name, python::no_init);

  // Register a custom factory function as the constructor method.
  egg_class
    .def("__init__", python::make_constructor(&make_egg<N>))
    .def("__str__", &egg_type::to_string)
    ;

  return egg_class;
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose spam.
  python::class_<spam>("Spam", python::init<int, int, int>());

  // Expose different egg types.
  expose_egg<2>("Egg2");
  expose_egg<4>("Egg4");
}

Interactive usage:

>>> import example
>>> spams = [
...   example.Spam(0, 1, 2),
...   example.Spam(1, 2, 3),
...   example.Spam(2, 3, 4),
...   example.Spam(3, 4, 5)
... ]
>>> print example.Egg2(spams)
[[0, 1, 2][1, 2, 3]]
>>> print example.Egg4(tuple(spams)) # different type
[[0, 1, 2][1, 2, 3][2, 3, 4][3, 4, 5]]
>>> print example.Egg4(spams[:1]) # expect IndexError
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> print example.Egg4(42) # expect TypeError (42[0] is invalid)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object has no attribute '__getitem__'
>>> print example.Egg4([42]) # expect TypeError (conversion)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: No registered converter was able to extract a C++ reference to
           type spam from this Python object of type int
Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169