• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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