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.