wlav / cppyy

Other
400 stars 41 forks source link

Indirectly inherited member function is silently not overridden by Python subclass when called from C++ #93

Closed torokati44 closed 1 year ago

torokati44 commented 1 year ago

This was a rather convoluted title, so let me illustrate it with code instead:

import cppyy

cppyy.cppdef("""
#include <iostream>

class Base {
public:

    // all functions look the same here
    virtual void f1() { std::cout << "C++: Base::f1()" << std::endl; }
    virtual void f2() { std::cout << "C++: Base::f2()" << std::endl; }
    virtual void f3() { std::cout << "C++: Base::f3()" << std::endl; }

    virtual ~Base() { }
};

class Intermediate: public Base {
public:

    using Base::f2;

    virtual ~Intermediate() { }
};

class Sub: public Intermediate {
public:

    using Intermediate::f3; // `using Base::f3;` would also work

    virtual ~Sub() { }
};

class CppSub: public Sub {
public:

    void f1() { std::cout << "C++: CppSub::f1()" << std::endl; }
    void f2() { std::cout << "C++: CppSub::f2()" << std::endl; }
    void f3() { std::cout << "C++: CppSub::f3()" << std::endl; }

    virtual ~CppSub() { }
};

void call_fs(Base *b) {
    b->f1();
    b->f2();
    b->f3();
}
""")

# all of these work as expected
cppsub = cppyy.gbl.CppSub()
cppsub.f1()
cppsub.f2()
cppsub.f3()
cppyy.gbl.call_fs(cppsub)

class PySub(cppyy.gbl.Sub):
    def f1(self):
        print("Python: PySub::f1()")

    def f2(self):
        print("Python: PySub::f2()")

    def f3(self):
        print("Python: PySub::f3()")

# these also work as expected
pysub = PySub()
pysub.f1()
pysub.f2()
pysub.f3()
# the issue is here:
cppyy.gbl.call_fs(pysub)

I would expect the output of this to be:

C++: CppSub::f1()
C++: CppSub::f2()
C++: CppSub::f3()
C++: CppSub::f1()
C++: CppSub::f2()
C++: CppSub::f3()
Python: PySub::f1()
Python: PySub::f2()
Python: PySub::f3()
Python: PySub::f1()
Python: PySub::f2()
Python: PySub::f3()

But actually it is this:

C++: CppSub::f1()
C++: CppSub::f2()
C++: CppSub::f3()
C++: CppSub::f1()
C++: CppSub::f2()
C++: CppSub::f3()
Python: PySub::f1()
Python: PySub::f2()
Python: PySub::f3()
C++: Base::f1()
Python: PySub::f2()
Python: PySub::f3()

For whatever reason, and only when called from C++, f1 (and only f1) is silently not being overridden by the Python subclass.

torokati44 commented 1 year ago

Do you think you'll be able to look into this sometime, @wlav? My guess is that the generation of the dispatcher class doesn't traverse all the way up on the inheritance tree when collecting methods to dispatch, so perhaps not all available methods (declared in place or inherited from anywhere) get a dispatcher into Python...?

wlav commented 1 year ago

Fixed in repo.

Before, only the immediate bases (i.e. 2 layers up into the inheritance tree) were search. Now the search is done recursively.

torokati44 commented 1 year ago

Ah, so my guess was right. Thank you so much!

torokati44 commented 1 year ago

I can confirm the fix worked on the example snippet above. Now waiting for a new release on PyPI soon. :pray: