mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
Override deduced Base class when defining Derived methods
When defining method from a member function pointer (e.g. `.def("f",
&Derived::f)`) we run into a problem if `&Derived::f` is actually
implemented in some base class `Base` when `Base` isn't
pybind-registered.
This happens because the class type is deduced from the member function
pointer, which then becomes a lambda with first argument this deduced
type. For a base class implementation, the deduced type is `Base`, not
`Derived`, and so we generate and registered an overload which takes a
`Base *` as first argument. Trying to call this fails if `Base` isn't
registered (e.g. because it's an implementation detail class that isn't
intended to be exposed to Python) because the type caster for an
unregistered type always fails.
This commit adds a `method_adaptor` function that rebinds a member
function to a derived type member function and otherwise (i.e. regular
functions/lambda) leaves the argument as-is. This is now used for class
definitions so that they are bound with type being registered rather
than a potential base type.
A closely related fix in this commit is to similarly update the lambdas
used for `def_readwrite` (and related) to bind to the class type being
registered rather than the deduced type so that registering a property
that resolves to a base class member similarly generates a usable
function.
Fixes #854, #910.
Co-Authored-By: Dean Moldovan <dean0x7d@gmail.com>
This commit is contained in:
@@ -159,7 +159,7 @@ public:
|
||||
};
|
||||
}}
|
||||
|
||||
/// Issue/PR #648: bad arg default debugging output
|
||||
// Issue/PR #648: bad arg default debugging output
|
||||
class NotRegistered {};
|
||||
|
||||
// Test None-allowed py::arg argument policy
|
||||
@@ -177,6 +177,23 @@ struct StrIssue {
|
||||
StrIssue(int i) : val{i} {}
|
||||
};
|
||||
|
||||
// Issues #854, #910: incompatible function args when member function/pointer is in unregistered base class
|
||||
class UnregisteredBase {
|
||||
public:
|
||||
void do_nothing() const {}
|
||||
void increase_value() { rw_value++; ro_value += 0.25; }
|
||||
void set_int(int v) { rw_value = v; }
|
||||
int get_int() const { return rw_value; }
|
||||
double get_double() const { return ro_value; }
|
||||
int rw_value = 42;
|
||||
double ro_value = 1.25;
|
||||
};
|
||||
class RegisteredDerived : public UnregisteredBase {
|
||||
public:
|
||||
using UnregisteredBase::UnregisteredBase;
|
||||
double sum() const { return rw_value + ro_value; }
|
||||
};
|
||||
|
||||
test_initializer methods_and_attributes([](py::module &m) {
|
||||
py::class_<ExampleMandA> emna(m, "ExampleMandA");
|
||||
emna.def(py::init<>())
|
||||
@@ -325,7 +342,7 @@ test_initializer methods_and_attributes([](py::module &m) {
|
||||
m.def("ints_preferred", [](int i) { return i / 2; }, py::arg("i"));
|
||||
m.def("ints_only", [](int i) { return i / 2; }, py::arg("i").noconvert());
|
||||
|
||||
/// Issue/PR #648: bad arg default debugging output
|
||||
// Issue/PR #648: bad arg default debugging output
|
||||
#if !defined(NDEBUG)
|
||||
m.attr("debug_enabled") = true;
|
||||
#else
|
||||
@@ -360,4 +377,26 @@ test_initializer methods_and_attributes([](py::module &m) {
|
||||
.def("__str__", [](const StrIssue &si) {
|
||||
return "StrIssue[" + std::to_string(si.val) + "]"; }
|
||||
);
|
||||
|
||||
// Issues #854/910: incompatible function args when member function/pointer is in unregistered
|
||||
// base class The methods and member pointers below actually resolve to members/pointers in
|
||||
// UnregisteredBase; before this test/fix they would be registered via lambda with a first
|
||||
// argument of an unregistered type, and thus uncallable.
|
||||
py::class_<RegisteredDerived>(m, "RegisteredDerived")
|
||||
.def(py::init<>())
|
||||
.def("do_nothing", &RegisteredDerived::do_nothing)
|
||||
.def("increase_value", &RegisteredDerived::increase_value)
|
||||
.def_readwrite("rw_value", &RegisteredDerived::rw_value)
|
||||
.def_readonly("ro_value", &RegisteredDerived::ro_value)
|
||||
// These should trigger a static_assert if uncommented
|
||||
//.def_readwrite("fails", &SimpleValue::value) // should trigger a static_assert if uncommented
|
||||
//.def_readonly("fails", &SimpleValue::value) // should trigger a static_assert if uncommented
|
||||
.def_property("rw_value_prop", &RegisteredDerived::get_int, &RegisteredDerived::set_int)
|
||||
.def_property_readonly("ro_value_prop", &RegisteredDerived::get_double)
|
||||
// This one is in the registered class:
|
||||
.def("sum", &RegisteredDerived::sum)
|
||||
;
|
||||
|
||||
using Adapted = decltype(py::method_adaptor<RegisteredDerived>(&RegisteredDerived::do_nothing));
|
||||
static_assert(std::is_same<Adapted, void (RegisteredDerived::*)() const>::value, "");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user