Test and document binding protected member functions

This commit is contained in:
Dean Moldovan 2017-08-17 17:03:46 +02:00
parent 9f6a636e54
commit 234f7c39a0
3 changed files with 146 additions and 1 deletions

View File

@ -86,7 +86,7 @@ functions, and :func:`PYBIND11_OVERLOAD` should be used for functions which have
a default implementation. There are also two alternate macros a default implementation. There are also two alternate macros
:func:`PYBIND11_OVERLOAD_PURE_NAME` and :func:`PYBIND11_OVERLOAD_NAME` which :func:`PYBIND11_OVERLOAD_PURE_NAME` and :func:`PYBIND11_OVERLOAD_NAME` which
take a string-valued name argument between the *Parent class* and *Name of the take a string-valued name argument between the *Parent class* and *Name of the
function* slots, which defines the name of function in Python. This is required function* slots, which defines the name of function in Python. This is required
when the C++ and Python versions of the when the C++ and Python versions of the
function have different names, e.g. ``operator()`` vs ``__call__``. function have different names, e.g. ``operator()`` vs ``__call__``.
@ -916,3 +916,78 @@ Python type created elsewhere.
The file :file:`tests/test_local_bindings.cpp` contains additional examples The file :file:`tests/test_local_bindings.cpp` contains additional examples
that demonstrate how ``py::module_local()`` works. that demonstrate how ``py::module_local()`` works.
Binding protected member functions
==================================
It's normally not possible to expose ``protected`` member functions to Python:
.. code-block:: cpp
class A {
protected:
int foo() const { return 42; }
};
py::class_<A>(m, "A")
.def("foo", &A::foo); // error: 'foo' is a protected member of 'A'
On one hand, this is good because non-``public`` members aren't meant to be
accessed from the outside. But we may want to make use of ``protected``
functions in derived Python classes.
The following pattern makes this possible:
.. code-block:: cpp
class A {
protected:
int foo() const { return 42; }
};
class Publicist : public A { // helper type for exposing protected functions
public:
using A::foo; // inherited with different access modifier
};
py::class_<A>(m, "A") // bind the primary class
.def("foo", &Publicist::foo); // expose protected methods via the publicist
This works because ``&Publicist::foo`` is exactly the same function as
``&A::foo`` (same signature and address), just with a different access
modifier. The only purpose of the ``Publicist`` helper class is to make
the function name ``public``.
If the intent is to expose ``protected`` ``virtual`` functions which can be
overridden in Python, the publicist pattern can be combined with the previously
described trampoline:
.. code-block:: cpp
class A {
public:
virtual ~A() = default;
protected:
virtual int foo() const { return 42; }
};
class Trampoline : public A {
public:
int foo() const override { PYBIND11_OVERLOAD(int, A, foo, ); }
};
class Publicist : public A {
public:
using A::foo;
};
py::class_<A, Trampoline>(m, "A") // <-- `Trampoline` here
.def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`!
.. note::
MSVC 2015 has a compiler bug (fixed in version 2017) which
requires a more explicit function binding in the form of
``.def("foo", static_cast<int (A::*)() const>(&Publicist::foo));``
where ``int (A::*)() const`` is the type of ``A::foo``.

View File

@ -229,6 +229,57 @@ TEST_SUBMODULE(class_, m) {
// This test is actually part of test_local_bindings (test_duplicate_local), but we need a // This test is actually part of test_local_bindings (test_duplicate_local), but we need a
// definition in a different compilation unit within the same module: // definition in a different compilation unit within the same module:
bind_local<LocalExternal, 17>(m, "LocalExternal", py::module_local()); bind_local<LocalExternal, 17>(m, "LocalExternal", py::module_local());
// test_bind_protected_functions
class ProtectedA {
protected:
int foo() const { return value; }
private:
int value = 42;
};
class PublicistA : public ProtectedA {
public:
using ProtectedA::foo;
};
py::class_<ProtectedA>(m, "ProtectedA")
.def(py::init<>())
#if !defined(_MSC_VER) || _MSC_VER >= 1910
.def("foo", &PublicistA::foo);
#else
.def("foo", static_cast<int (ProtectedA::*)() const>(&PublicistA::foo));
#endif
class ProtectedB {
public:
virtual ~ProtectedB() = default;
protected:
virtual int foo() const { return value; }
private:
int value = 42;
};
class TrampolineB : public ProtectedB {
public:
int foo() const override { PYBIND11_OVERLOAD(int, ProtectedB, foo, ); }
};
class PublicistB : public ProtectedB {
public:
using ProtectedB::foo;
};
py::class_<ProtectedB, TrampolineB>(m, "ProtectedB")
.def(py::init<>())
#if !defined(_MSC_VER) || _MSC_VER >= 1910
.def("foo", &PublicistB::foo);
#else
.def("foo", static_cast<int (ProtectedB::*)() const>(&PublicistB::foo));
#endif
} }
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; }; template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };

View File

@ -176,3 +176,22 @@ def test_operator_new_delete(capture):
"C delete " + sz_noalias + "\n" + "C delete " + sz_noalias + "\n" +
"C delete " + sz_alias + "\n" "C delete " + sz_alias + "\n"
) )
def test_bind_protected_functions():
"""Expose protected member functions to Python using a helper class"""
a = m.ProtectedA()
assert a.foo() == 42
b = m.ProtectedB()
assert b.foo() == 42
class C(m.ProtectedB):
def __init__(self):
m.ProtectedB.__init__(self)
def foo(self):
return 0
c = C()
assert c.foo() == 0