Wrapping STL container return types using Pybind11

4.6k views Asked by At

I'm tying to wrap a C++ function (using Pybind11) that returns an STL container which is itself wrapped in a smart pointer. An example is shown below. The function is overloaded so I have to specify the signature.

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "my_class.h"

typedef std::array<std::complex<double>, 4> ArrayComplex4;
PYBIND11_MAKE_OPAQUE(ArrayComplex4);

namespace py = pybind11;
using namespace my_namespace;

PYBIND11_MODULE(my_module, m) {
    py::class_<MyClass>(m, "MyClass", py::dynamic_attr())
        .def("my_function", (std::unique_ptr<ArrayComplex4> (MyClass::*)(double)) &MyClass::my_function)
        .def("my_function", (std::unique_ptr<ArrayComplex4> (MyClass::*)(double, double)) &MyClass::my_function);
}

The module will compile but it will give an error when I try to use the function in Python:

TypeError: Unable to convert function return value to a Python type!

I'm sure I'm just setting something up wrong for Pybind11. Thanks for any help!

EDIT

The problem was definitely in my attempt to bind the std::Array data type. I ended up modifying the code to use std::Vector and then Pybind11 had no issues. See AS Mackey's answer below for how one might bind the std::Array containers.

1

There are 1 answers

0
AS Mackay On BEST ANSWER

The problem might be with the PYBIND11_MAKE_OPAQUE treatment of the ArrayComplex4 data structure, I couldn't get this to work, although I'll look at it some more when I get time.

The following code, which I did get to work, is my closest approximation so far to your design. I used an additional user-defined data structure to wrap the std:: elements:

#include <pybind11/pybind11.h>
#include <pybind11/complex.h>
#include <pybind11/stl.h>
#include <memory>
#include <complex>
#include <array>
#include <cmath>

namespace py = pybind11;


typedef std::array<std::complex<double>, 4> ArrayComplex4;

struct ArrayComplex4Holder
{
    ArrayComplex4 data;
    ArrayComplex4 getData() { return data; }
};

class MyClass {
public:
    MyClass() { }
    std::unique_ptr<ArrayComplex4Holder> my_function(double x)
    {
        std::unique_ptr<ArrayComplex4Holder> ph( new ArrayComplex4Holder());
        ph->data[0] = std::complex<double>(x);
        return ph;
    }
    std::unique_ptr<ArrayComplex4Holder> my_function(double x, double y)
    {
        std::unique_ptr<ArrayComplex4Holder> ph( new ArrayComplex4Holder());
        ph->data[0] = std::complex<double>(x);
        ph->data[1] = std::complex<double>(y);
        return ph;
    }
};

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example"; // optional module docstring

    py::class_<ArrayComplex4Holder>(m, "ArrayComplex4Holder")
            .def(py::init<>())
            .def("getData", &ArrayComplex4Holder::getData);

    py::class_<MyClass>(m, "MyClass")
            .def(py::init<>())
            .def("my_function", (std::unique_ptr<ArrayComplex4Holder> (MyClass::*)(double))         &MyClass::my_function)
            .def("my_function", (std::unique_ptr<ArrayComplex4Holder> (MyClass::*)(double, double)) &MyClass::my_function);
}

NB I found that the additional pybind #includes were needed to make the python casting work.

The simple python code is:

import sys
sys.path.append('/Volumes/RAID 1/Projects/workspace/Project CPP 1')
import example

p = example.MyClass()
print (p.my_function(1.2345).getData())
print (p.my_function(1.2345, 6.7890).getData())

This produces the following output, which looks correct:

[(1.2345+0j), 0j, 0j, 0j]
[(1.2345+0j), (6.789+0j), 0j, 0j]

I hope this gives you a starting point to work from. I'd be interested to know if you find a better alternative... Regards, AS