Boost.Python did not match signature lvalue

5.7k views Asked by At

I have a class Foo with a member x that I want to expose, but via a getter function rather than a property. I just discovered make_getter, so I thought I'd give that a go:

#include <boost/python.hpp>    
namespace py = boost::python;

struct Base {
    int x;
};

struct Foo : Base {
    Foo(int i): Base{i} { }
};

BOOST_PYTHON_MODULE(Foo)
{
    py::class_<Foo>("Foo", py::init<int>())
        .def_readonly("x", &Foo::x)
        .def("getX", py::make_getter(&Foo::x))
        ;
}

This, however, fails:

>>> import Foo
>>> f = Foo.Foo(42)
>>> f.x
42
>>> f.getX()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Foo.getX(Foo)
did not match C++ signature:
    getX(Base {lvalue})
>>> 

What does that error even mean? Clearly the signatures match! How can I fix this?

1

There are 1 answers

0
Barry On

The issue, if you carefully examine the ArgumentError exception is that, you are calling getX() with a Foo:

>>> f.getX()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Foo.getX(Foo)

whereas the data member you are trying to access is really a member of Base:

did not match C++ signature:
    getX(Base {lvalue})

Boost.Python needs to perform a conversion from a lvalue Foo to an lvalue Base, and you haven't actually told Boost that it can do that. The problem ultimately stems from the fact that &Base::x is an int Base::* instead of an int Foo::*, so the template deduction in boost::python::make_getter makes a function that takes a Base instead of a Foo.

The simplest solution is to ensure that you pass in the correct pointer-to-member to make_getter:

BOOST_PYTHON_MODULE(Foo)
{
    py::class_<Foo>("Foo", py::init<int>())
        .def_readonly("x", &Foo::x)
        .def("getX", py::make_getter(static_cast<int Foo::*>(&Foo::x)))
        ;
}

With that cast, everything works:

>>> from Foo import Foo
>>> Foo(4).getX()
4

That's a little tedious though, so you could instead write a quick method/macro to do it for you:

template <typename Derived, typename Base, typename T>
T Derived::* as_derived(T Base::*member) {
    return static_cast<T Derived::*>(member);
}

#define AS_DERIVED(CLS, FIELD) as_derived<CLS>(&CLS::FIELD)

With which you can do:

BOOST_PYTHON_MODULE(Foo)
{
    py::class_<Foo>("Foo", py::init<int>())
        .def_readonly("x", &Foo::x)
        .def("getX", py::make_getter(AS_DERIVED(Foo, x)))
        ;
}

Alternatively, you could tell Boost.Python about the hierarchy directly:

BOOST_PYTHON_MODULE(Foo)
{
    py::class_<Base>("Base", py::no_init)
        .def("getX", py::make_getter(&Base::x))
        ;

    py::class_<Foo, py::bases<Base>>("Foo", py::init<int>())
        .def_readonly("x", &Foo::x)
        ;
}

This way, Foo inherits getX() and all is well.