pybind / pybind/pybind11

[BUG]: PYBIND11_OVERRIDE_IMPL issue with multiple inheritance due to static_cast

Open
#3,902 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
C++
Stars
18k
Forks
2.3k
Avg merge
5d 17h
Merged PRs (30d)
10

Description

Required prerequisites
Problem description

When using PYBIND11_OVERRIDE_PURE inside of class with multiple inheritance, e.g.

class A { virtual ~A() {} };
class B { virtual std::string fn() const = 0; virtual ~B() {} }

class PyClass : public A, public B {
public:
    std::string fn() const override {
        PYBIND11_OVERRIDE_PURE(QString, B, fn, );
    }
};

// A is not exposed in Python

py::class_<B,  PyB>(m, "B", py::multiple_inheritance())
    .def(py::init<>()) // should this be init_alias?
    .def("function", &B::fn);

This functions throws a "Tried to call pure virtual function ..." exception when constructing a new B in Python.

Removing this static_cast:

https://github.com/pybind/pybind11/blob/master/include/pybind11/pybind11.h#L2729

...solves the issue.

Is there any reason to have this static_cast here? Is there something I'm missing?

Reproducible example code
#include <iostream>

#include <pybind11/embed.h>
#include <pybind11/pybind11.h>

class A {
public:
    virtual ~A() {}
};

class B {
public:
    virtual std::string fn() const = 0;

    virtual ~B() {}
};

constexpr const char* fmt_s = "{:20}: {}\n";

// switch A and B here fixes the issue since the function is from B
class PyB : public A, public B {
public:
    std::string fn() const override {
        std::cout << "fn(), this           : " << (void*)this << "\n";
        std::cout << "fn(), static_cast<>  : " << (void*)static_cast<const B*>(this)
                  << "\n";
        std::cout << "fn(), dynamic_cast<> : " << (void*)dynamic_cast<const B*>(this)
                  << "\n";

        PYBIND11_OVERRIDE_PURE(std::string, B, fn, );
    }
};

namespace py = pybind11;

PYBIND11_EMBEDDED_MODULE(cpp_module, m) {
    py::class_<B, PyB>(m, "B", py::multiple_inheritance())
        .def(py::init<>())
        .def("fn", &B::fn);
}

int main() {
    py::scoped_interpreter guard{};
    py::exec(R"(
        from cpp_module import B

        class MyB(B):
            def fn(self): return "MyB"

        b = MyB()
    )");
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with include/pybind11/pybind11.h around line 2729 and inspect how PYBIND11_OVERRIDE_PURE handles the static_cast. Build and run the embedded C++ reproducer from the issue, including the multiple-inheritance and switched-base cases. Done means the MyB example calls the Python override without the pure-virtual exception while the existing behavior remains correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.