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
|
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``.
|
||||||
|
@ -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; };
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user