summaryrefslogtreecommitdiff
path: root/libs/python/doc/polymorphism.txt
diff options
context:
space:
mode:
Diffstat (limited to 'libs/python/doc/polymorphism.txt')
-rw-r--r--libs/python/doc/polymorphism.txt222
1 files changed, 222 insertions, 0 deletions
diff --git a/libs/python/doc/polymorphism.txt b/libs/python/doc/polymorphism.txt
new file mode 100644
index 000000000..38e2f6914
--- /dev/null
+++ b/libs/python/doc/polymorphism.txt
@@ -0,0 +1,222 @@
+.. Copyright David Abrahams 2006. Distributed under the Boost
+.. Software License, Version 1.0. (See accompanying
+.. file LICENSE_1_0.txt or copy at
+.. http://www.boost.org/LICENSE_1_0.txt)
+
+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<std::string>(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.
+
+
+ OK, the solution involves reworking the call mechanism. This is
+ neccesary anyway in order to enable wrapping of function objects.
+
+ It can result in a reduction in the overall amount of source code,
+ because returning<> won't need to be specialized for every
+ combination of function and member function... though it will still
+ need a void specialization. We will still need a way to dispatch to
+ member functions through a regular function interface. mem_fn is
+ almost the right tool, but it only goes up to 8
+ arguments. Forwarding is tricky if you don't want to incur copies.
+ I think the trick is to use arg_from_python<T>::result_type for each
+ argument to the forwarder.
+
+ Another option would be to use separate function, function object,
+ and member function dispatchers. Once you know you have a member
+ function, you don't need cv-qualified overloads to call it.
+
+ Hmm, while we're at this, maybe we should solve the write-back
+ converter problem. Can we do it? Maybe not. Ralf doesn't want to
+ write special write-back functions here, does he? He wants the
+ converter to do the work automatically. We could add
+ cleanup/destructor registration. That would relieve the client from
+ having accessible destructors for types which are being converted by
+ rvalue. I'm not sure that this will really save any code,
+ however. It rather depends on the linker, doesn't it? I wonder if
+ this can be done in a backwards-compatible fashion by generating the
+ delete function when it's not supplied?
+
+