mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
Test and document binding protected member functions
This commit is contained in:
parent
9f6a636e54
commit
234f7c39a0
@ -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
|
||||
: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
|
||||
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
|
||||
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
|
||||
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``.
|
||||
|
@ -229,6 +229,57 @@ TEST_SUBMODULE(class_, m) {
|
||||
// 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:
|
||||
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; };
|
||||
|
@ -176,3 +176,22 @@ def test_operator_new_delete(capture):
|
||||
"C delete " + sz_noalias + "\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
|
||||
|
Loading…
Reference in New Issue
Block a user