1.. Copyright David Abrahams 2006. Distributed under the Boost 2.. Software License, Version 1.0. (See accompanying 3.. file LICENSE_1_0.txt or copy at 4.. http://www.boost.org/LICENSE_1_0.txt) 5 6How Runtime Polymorphism is expressed in Boost.Python: 7----------------------------------------------------- 8 9 struct A { virtual std::string f(); virtual ~A(); }; 10 11 std::string call_f(A& x) { return x.f(); } 12 13 struct B { virtual std::string f() { return "B"; } }; 14 15 struct Bcb : B 16 { 17 Bcb(PyObject* self) : m_self(self) {} 18 19 virtual std::string f() { return call_method<std::string>(m_sef, "f"); } 20 static std::string f_default(B& b) { return b.B::f(); } 21 22 PyObject* m_self; 23 }; 24 25 struct C : B 26 { 27 virtual std::string f() { return "C"; } 28 }; 29 30 >>> class D(B): 31 ... def f(): 32 ... return 'D' 33 ... 34 >>> class E(B): pass 35 ... 36 37 38When we write, "invokes B::f non-virtually", we mean: 39 40 void g(B& x) { x.B::f(); } 41 42This will call B::f() regardless of the dynamic type of x. Any other 43way of invoking B::f, including through a function pointer, is a 44"virtual invocation", and will call the most-derived override of f(). 45 46Case studies 47 48 C++\Python class 49 \___A_____B_____C_____D____E___ 50 | 51 A | 1 52 | 53 B | 2 3 54 | 55 Bcb | 4 5 6 56 | 57 C | 7 8 58 | 59 60 611. Simple case 62 632. Python A holds a B*. Probably won't happen once we have forced 64 downcasting. 65 66 Requires: 67 x.f() -> 'B' 68 call_f(x) -> 'B' 69 70 Implies: A.f invokes A::f() (virtually or otherwise) 71 723. Python B holds a B*. 73 74 Requires: 75 x.f() -> 'B' 76 call_f(x) -> 'B' 77 78 Implies: B.f invokes B::f (virtually or otherwise) 79 80 814. B constructed from Python 82 83 Requires: 84 85 x.f() -> 'B' 86 call_f(x) -> 'B' 87 88 Implies: B.f invokes B::f non-virtually. Bcb::f invokes B::f 89 non-virtually. 90 91 Question: Does it help if we arrange for Python B construction to 92 build a true B object? Then this case doesn't arise. 93 94 955. D is a Python class derived from B 96 97 Requires: 98 99 x.f() -> 'D' 100 call_f(x) -> 'D' 101 102 Implies: Bcb::f must invoke call_method to look up the Python 103 method override, otherwise call_f wouldn't work. 104 1056. E is like D, but doesn't override f 106 107 Requires: 108 109 x.f() -> 'B' 110 call_f(x) -> 'B' 111 112 Implies: B.f invokes B::f non-virtually. If it were virtual, x.f() 113 would cause infinite recursion, because we've already 114 determined that Bcb::f must invoke call_method to look up 115 the Python method override. 116 1177. Python B object holds a C* 118 119 Requires: 120 121 x.f() -> 'C' 122 call_f(x) -> 'C' 123 124 Implies: B.f invokes B::f virtually. 125 1268. C object constructed from Python 127 128 Requires: 129 130 x.f() -> 'C' 131 call_f(x) -> 'C' 132 133 Implies: nothing new. 134 135------ 136 137Total implications: 138 1392: A.f invokes A::f() (virtually or otherwise) 1403: B.f invokes B::f (virtually or otherwise) 1414: B.f invokes B::f non-virtually. Bcb::f invokes B::f non-virtually 1426: B.f invokes B::f non-virtually. 1437: B.f invokes B::f virtually. 144 1455: Bcb::f invokes call_method to look up the Python method 146 147Though (4) is avoidable, clearly 6 and 7 are not, and they 148conflict. The implication is that B.f must choose its behavior 149according to the type of the contained C++ object. If it is Bcb, a 150non-virtual call to B::f must occur. Otherwise, a virtual call to B::f 151must occur. This is essentially the same scheme we had with 152Boost.Python v1. 153 154Note: in early versions of Boost.Python v1, we solved this problem by 155introducing a new Python class in the hierarchy, so that D and E 156actually derive from a B', and B'.f invokes B::f non-virtually, while 157B.f invokes B::f virtually. However, people complained about the 158artificial class in the hierarchy, which was revealed when they tried 159to do normal kinds of Python introspection. 160 161------- 162 163Assumption: we will have a function which builds a virtual function 164dispatch callable Python object. 165 166 make_virtual_function(pvmf, default_impl, call_policies, dispatch_type) 167 168Pseudocode: 169 170 Get first argument from Python arg tuple 171 if it contains dispatch_type 172 call default_impl 173 else 174 call through pvmf 175 176 177Open questions: 178 179 1. What about Python multiple inheritance? Do we have the right 180 check in the if clause above? 181 182 A: Not quite. The correct test looks like: 183 184 Deduce target type of pvmf, i.e. T in R(T::*)(A1...AN). 185 Find holder in first argument which holds T 186 if it holds dispatch_type... 187 188 2. Can we make this more efficient? 189 190 The current "returning" mechanism will look up a holder for T 191 again. I don't know if we know how to avoid that. 192 193 194 OK, the solution involves reworking the call mechanism. This is 195 neccesary anyway in order to enable wrapping of function objects. 196 197 It can result in a reduction in the overall amount of source code, 198 because returning<> won't need to be specialized for every 199 combination of function and member function... though it will still 200 need a void specialization. We will still need a way to dispatch to 201 member functions through a regular function interface. mem_fn is 202 almost the right tool, but it only goes up to 8 203 arguments. Forwarding is tricky if you don't want to incur copies. 204 I think the trick is to use arg_from_python<T>::result_type for each 205 argument to the forwarder. 206 207 Another option would be to use separate function, function object, 208 and member function dispatchers. Once you know you have a member 209 function, you don't need cv-qualified overloads to call it. 210 211 Hmm, while we're at this, maybe we should solve the write-back 212 converter problem. Can we do it? Maybe not. Ralf doesn't want to 213 write special write-back functions here, does he? He wants the 214 converter to do the work automatically. We could add 215 cleanup/destructor registration. That would relieve the client from 216 having accessible destructors for types which are being converted by 217 rvalue. I'm not sure that this will really save any code, 218 however. It rather depends on the linker, doesn't it? I wonder if 219 this can be done in a backwards-compatible fashion by generating the 220 delete function when it's not supplied? 221 222 223