How Runtime Polymorphism is expressed in Boost.Python: ----------------------------------------------------- struct A { virtual std::string f(); virtual ~A(); }; std::string call_f(A& x) { return x.f(); } struct B { virtual std::string f() { return "B"; } }; struct Bcb : B { Bcb(PyObject* self) : m_self(self) {} virtual std::string f() { return call_method(m_sef, "f"); } static std::string f_default(B& b) { return b.B::f(); } PyObject* m_self; }; struct C : B { virtual std::string f() { return "C"; } }; >>> class D(B): ... def f(): ... return 'D' ... >>> class E(B): pass ... When we write, "invokes B::f non-virtually", we mean: void g(B& x) { x.B::f(); } This will call B::f() regardless of the dynamic type of x. Any other way of invoking B::f, including through a function pointer, is a "virtual invocation", and will call the most-derived override of f(). Case studies C++\Python class \___A_____B_____C_____D____E___ | A | 1 | B | 2 3 | Bcb | 4 5 6 | C | 7 8 | 1. Simple case 2. Python A holds a B*. Probably won't happen once we have forced downcasting. Requires: x.f() -> 'B' call_f(x) -> 'B' Implies: A.f invokes A::f() (virtually or otherwise) 3. Python B holds a B*. Requires: x.f() -> 'B' call_f(x) -> 'B' Implies: B.f invokes B::f (virtually or otherwise) 4. B constructed from Python Requires: x.f() -> 'B' call_f(x) -> 'B' Implies: B.f invokes B::f non-virtually. Bcb::f invokes B::f non-virtually. Question: Does it help if we arrange for Python B construction to build a true B object? Then this case doesn't arise. 5. D is a Python class derived from B Requires: x.f() -> 'D' call_f(x) -> 'D' Implies: Bcb::f must invoke call_method to look up the Python method override, otherwise call_f wouldn't work. 6. E is like D, but doesn't override f Requires: x.f() -> 'B' call_f(x) -> 'B' Implies: B.f invokes B::f non-virtually. If it were virtual, x.f() would cause infinite recursion, because we've already determined that Bcb::f must invoke call_method to look up the Python method override. 7. Python B object holds a C* Requires: x.f() -> 'C' call_f(x) -> 'C' Implies: B.f invokes B::f virtually. 8. C object constructed from Python Requires: x.f() -> 'C' call_f(x) -> 'C' Implies: nothing new. ------ Total implications: 2: A.f invokes A::f() (virtually or otherwise) 3: B.f invokes B::f (virtually or otherwise) 4: B.f invokes B::f non-virtually. Bcb::f invokes B::f non-virtually 6: B.f invokes B::f non-virtually. 7: B.f invokes B::f virtually. 5: Bcb::f invokes call_method to look up the Python method Though (4) is avoidable, clearly 6 and 7 are not, and they conflict. The implication is that B.f must choose its behavior according to the type of the contained C++ object. If it is Bcb, a non-virtual call to B::f must occur. Otherwise, a virtual call to B::f must occur. This is essentially the same scheme we had with Boost.Python v1. Note: in early versions of Boost.Python v1, we solved this problem by introducing a new Python class in the hierarchy, so that D and E actually derive from a B', and B'.f invokes B::f non-virtually, while B.f invokes B::f virtually. However, people complained about the artificial class in the hierarchy, which was revealed when they tried to do normal kinds of Python introspection. ------- Assumption: we will have a function which builds a virtual function dispatch callable Python object. make_virtual_function(pvmf, default_impl, call_policies, dispatch_type) Pseudocode: Get first argument from Python arg tuple if it contains dispatch_type call default_impl else call through pvmf Open questions: 1. What about Python multiple inheritance? Do we have the right check in the if clause above? A: Not quite. The correct test looks like: Deduce target type of pvmf, i.e. T in R(T::*)(A1...AN). Find holder in first argument which holds T if it holds dispatch_type... 2. Can we make this more efficient? The current "returning" mechanism will look up a holder for T again. I don't know if we know how to avoid that.