pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.09k stars 2.05k forks source link

[BUG]: Overloaded (pure) virtual function not inherited #4996

Open MatthijsBurgh opened 6 months ago

MatthijsBurgh commented 6 months ago

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

2.11.1

Problem description

Similar to https://github.com/pybind/pybind11/issues/2351. Though I have overloaded functions defined in child classes, instead of the base class.

I have a base class with multiple child classes. The base class does has a (pure) virtual function (with 2 doubles as arguments). The child classes overload this function with more doubles as arguments, the amount depends on the child class. The issue is that the (pure) virtual function func is not inherited into the child classes. Only the overloaded version of the function is available in the child classes. The original function (2 doubles as arguments) is only available in the child classes when defining the function for each child. It doesn't matter whether I reference the base class or the child class in the definition. (See the commented lines in the child classes.) It also doesn't matter whether I use the py::overload_cast in the base class or not. (See commented line in the base class.)

The __copy__ and __deepcopy__ functions are inherited from the base class to the child classes without any issue.

The bug: The overloaded (pure) virtual function is not inherited into the child classes unless defined in the child classes.

edit: I think the issue is not related to the virtual functions, but the overloading of base functions in the child classes. As a fully implemented non-virtual function (func2) shows the same issue of not being inherited. Again only the overloaded version of the function is available.

Reproducible example code

mylib.hpp

class Base {
 public:
  Base()
  virtual ~Base() = default;

  virtual void func(double, double) = 0;
  void func2(double);
};

class Child1: public Base {
 public:
  Child1();
  void func(double, double, double, double);
  void func2(double, double);
};

class Child2: public Base {
 public:
  Child2();
  void func(double, double, double, double, double, double);
  void func2(double, double, double);
};

mylib_pybind.cpp

template <class BaseType = Base>
class PyBase : public BaseType
{
  using BaseType::BaseType;  // Inherit constructors
  BaseType* clone() const override
  {
    PYBIND11_OVERRIDE_PURE_NAME(BaseType*, BaseType, "__copy__", clone);
  };
  void func(double arg1, double arg2) override
  {
    PYBIND11_OVERRIDE_PURE_NAME(void, BaseType, "func", func, arg1, arg2);
  };
};

template <class ChildType>
class PyChild : public PyBase<ChildType>
{
public:
  using PyBase<ChildType>::PyBase;  // Inherit constructors
  ChildType* clone() const override
  {
    PYBIND11_OVERRIDE_NAME(ChildType*, ChildType, "__copy__", clone);
  }
  void func(double arg1, double arg2) override
  {
    PYBIND11_OVERRIDE_NAME(void, ChildType, "func", func, arg1, arg2);
  };
};

PYBIND11_MODULE(mypkg, m) {
  py::class_<Base, PyBase>(m, "Base")
      .def(py::init<>())
      .def("__copy__", &Base::clone)
      .def("__deepcopy__", [](const Base& self, py::dict&) { return self.clone(); }, py::arg("memo"))
      .def("func", &Base::func)
//      .def("func", py::overload_cast<double, double>(&Base::func))
      .def("func2", &Base::func2);

    py::class_<Child1, Base, PyChild<Child1>>(m, "Child1")
      .def(py::init<>())
//      .def("func", py::overload_cast<double, double>(&Child1::func))
//      .def("func", py::overload_cast<double, double>(&Base::func))
      .def("func", py::overload_cast<double, double, double, double>(&Child1::func))
//      .def("func2", py::overload_cast<double>(&Child1::func2))
//      .def("func2", py::overload_cast<double>(&Base::func2))
      .def("func2", py::overload_cast<double, double>(&Child1::func2));

    py::class_<Child2, Base, PyChild<Child2>>(m, "Child2")
      .def(py::init<>())
//      .def("func", py::overload_cast<double, double>(&Child2::func))
//      .def("func", py::overload_cast<double, double>(&Base::func))
      .def("func", py::overload_cast<double, double, double, double, double, double>(&Child2::func))
//      .def("func2", py::overload_cast<double>(&Child2::func2))
//      .def("func2", py::overload_cast<double>(&Base::func2))
      .def("func2", py::overload_cast<double, double, double>(&Child2::func2));

}

Is this a regression? Put the last known working version here if it is.

Not a regression