Skip to content

Commit e190864

Browse files
wrapper: Robustify check, don't require that alias classes inherit, allow alias to be wrapped directly.
1 parent aceeebd commit e190864

File tree

5 files changed

+94
-73
lines changed

5 files changed

+94
-73
lines changed

docs/advanced/classes.rst

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ helper class that is defined as follows:
6565

6666
.. code-block:: cpp
6767
68-
class PyAnimal : public py::wrapper<Animal> {
68+
class PyAnimal : public Animal {
6969
public:
7070
/* Inherit the constructors */
71-
using py::wrapper<Animal>::wrapper;
71+
using Animal::Animal;
7272
7373
/* Trampoline (need one for each virtual function) */
7474
std::string go(int n_times) override {
@@ -90,15 +90,13 @@ function* slots, which defines the name of function in Python. This is required
9090
when the C++ and Python versions of the
9191
function have different names, e.g. ``operator()`` vs ``__call__``.
9292

93-
The base class ``py::wrapper<>`` is optional, but is recommended as it allows us to attach the lifetime of Python objects directly to C++ objects, explained in :ref:`virtual_inheritance_lifetime`.
94-
9593
The binding code also needs a few minor adaptations (highlighted):
9694

9795
.. code-block:: cpp
9896
:emphasize-lines: 2,4,5
9997
10098
PYBIND11_MODULE(example, m) {
101-
py::class_<Animal, PyAnimal /* <--- trampoline*/> animal(m, "Animal");
99+
py::class_<Animal, py::wrapper<PyAnimal> /* <--- trampoline*/> animal(m, "Animal");
102100
animal
103101
.def(py::init<>())
104102
.def("go", &Animal::go);
@@ -119,7 +117,7 @@ Bindings should be made against the actual class, not the trampoline helper clas
119117

120118
.. code-block:: cpp
121119
122-
py::class_<Animal, PyAnimal /* <--- trampoline*/> animal(m, "Animal");
120+
py::class_<Animal, py::wrapper<PyAnimal> /* <--- trampoline*/> animal(m, "Animal");
123121
animal
124122
.def(py::init<>())
125123
.def("go", &PyAnimal::go); /* <--- THIS IS WRONG, use &Animal::go */
@@ -129,6 +127,16 @@ extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the
129127
necessary steps required to providing proper overload support for inherited
130128
classes.
131129

130+
The wrapper ``py::wrapper<>`` is optional, but is recommended as it
131+
allows us to attach the lifetime of Python objects directly to C++ objects,
132+
explained in :ref:`virtual_inheritance_lifetime`.
133+
134+
.. note::
135+
136+
If you do use ``py::wrapper<>`` and you have defined factory
137+
``__init__`` methods, you *must* ensure that the alias variants return new
138+
instances of ``py::wrapper<Alias>``, rather than just ``Alias``.
139+
132140
The Python session below shows how to override ``Animal::go`` and invoke it via
133141
a virtual method call.
134142

@@ -234,15 +242,15 @@ override the ``name()`` method):
234242

235243
.. code-block:: cpp
236244
237-
class PyAnimal : public py::wrapper<Animal> {
245+
class PyAnimal : public Animal {
238246
public:
239-
using py::wrapper<Animal>::wrapper; // Inherit constructors
247+
using Animal::Animal; // Inherit constructors
240248
std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, Animal, go, n_times); }
241249
std::string name() override { PYBIND11_OVERLOAD(std::string, Animal, name, ); }
242250
};
243-
class PyDog : public py::wrapper<Dog> {
251+
class PyDog : public Dog {
244252
public:
245-
using py::wrapper<Dog>::wrapper; // Inherit constructors
253+
using Dog::Dog; // Inherit constructors
246254
std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, Dog, go, n_times); }
247255
std::string name() override { PYBIND11_OVERLOAD(std::string, Dog, name, ); }
248256
std::string bark() override { PYBIND11_OVERLOAD(std::string, Dog, bark, ); }
@@ -262,24 +270,24 @@ declare or override any virtual methods itself:
262270
.. code-block:: cpp
263271
264272
class Husky : public Dog {};
265-
class PyHusky : public py::wrapper<Husky> {
273+
class PyHusky : public Husky {
266274
public:
267-
using py::wrapper<Husky>::wrapper; // Inherit constructors
275+
using Husky::Husky; // Inherit constructors
268276
std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, Husky, go, n_times); }
269277
std::string name() override { PYBIND11_OVERLOAD(std::string, Husky, name, ); }
270278
std::string bark() override { PYBIND11_OVERLOAD(std::string, Husky, bark, ); }
271279
};
272280
273281
There is, however, a technique that can be used to avoid this duplication
274282
(which can be especially helpful for a base class with several virtual
275-
methods). The technique (the Curiously Recurring Template Pattern) involves using template trampoline classes, as
283+
methods). The technique involves using template trampoline classes, as
276284
follows:
277285

278286
.. code-block:: cpp
279287
280-
template <class AnimalBase = Animal> class PyAnimal : public py::wrapper<AnimalBase> {
288+
template <class AnimalBase = Animal> class PyAnimal : public AnimalBase {
281289
public:
282-
using py::wrapper<AnimalBase>::wrapper; // Inherit constructors
290+
using AnimalBase::AnimalBase; // Inherit constructors
283291
std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, AnimalBase, go, n_times); }
284292
std::string name() override { PYBIND11_OVERLOAD(std::string, AnimalBase, name, ); }
285293
};
@@ -301,9 +309,9 @@ The classes are then registered with pybind11 using:
301309

302310
.. code-block:: cpp
303311
304-
py::class_<Animal, PyAnimal<>> animal(m, "Animal");
305-
py::class_<Dog, PyDog<>> dog(m, "Dog");
306-
py::class_<Husky, PyDog<Husky>> husky(m, "Husky");
312+
py::class_<Animal, py::wrapper<PyAnimal<>>> animal(m, "Animal");
313+
py::class_<Dog, py::wrapper<PyDog<>>> dog(m, "Dog");
314+
py::class_<Husky, py::wrapper<PyDog<Husky>>> husky(m, "Husky");
307315
// ... add animal, dog, husky definitions
308316
309317
Note that ``Husky`` did not require a dedicated trampoline template class at
@@ -324,6 +332,12 @@ can now create a python class that inherits from ``Dog``:
324332
See the file :file:`tests/test_virtual_functions.cpp` for complete examples
325333
using both the duplication and templated trampoline approaches.
326334

335+
.. note::
336+
337+
If you do not want to specify ``py::wrapper<>`` for each type, you
338+
can have ``PyAnimal`` inherit from ``py::wrapper<>``. Pybind will
339+
detect if ``py::wrapper<>`` is present at any point in the chain.
340+
327341
.. _extended_aliases:
328342

329343
Extended trampoline class functionality
@@ -1051,10 +1065,10 @@ For this example, we will build upon the above code for ``Animal`` with alias ``
10511065
virtual std::string go(int n_times) = 0;
10521066
};
10531067
1054-
class PyAnimal : public py::wrapper<Animal> {
1068+
class PyAnimal : public Animal {
10551069
public:
10561070
/* Inherit the constructors */
1057-
using py::wrapper<Animal>::wrapper;
1071+
using Animal::Animal;
10581072
std::string go(int n_times) override {
10591073
PYBIND11_OVERLOAD_PURE(std::string, Animal, go, n_times);
10601074
}
@@ -1077,7 +1091,7 @@ And the following bindings:
10771091
.. code-block:: cpp
10781092
10791093
PYBIND11_MODULE(example, m) {
1080-
py::class_<Animal, PyAnimal, std::shared_ptr<Animal>> animal(m, "Animal");
1094+
py::class_<Animal, py::wrapper<PyAnimal>, std::shared_ptr<Animal>> animal(m, "Animal");
10811095
animal
10821096
.def(py::init<>())
10831097
.def("go", &Animal::go);

include/pybind11/pybind11.h

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,12 +1014,25 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
10141014
return pmf;
10151015
}
10161016

1017+
namespace detail {
1018+
1019+
template <template <typename...> class Tpl>
1020+
struct is_base_template_of_impl {
1021+
// Use pointers for robustness.
1022+
template <typename ... Base>
1023+
static std::true_type check(const Tpl<Base...>*);
1024+
static std::false_type check(void*);
1025+
};
10171026

1027+
template <template <typename...> class Tpl, typename Derived>
1028+
using is_base_template_of =
1029+
decltype(is_base_template_of_impl<Tpl>::check(
1030+
std::declval<Derived*>()));
10181031

1019-
template <typename type, bool compatible>
1032+
template <typename type, typename alias, bool compatible>
10201033
struct wrapper_interface_impl {
10211034
static void use_cpp_lifetime(type* cppobj, object&& obj, detail::HolderTypeId holder_type_id) {
1022-
auto* tr = dynamic_cast<wrapper<type>*>(cppobj);
1035+
auto* tr = dynamic_cast<alias*>(cppobj);
10231036
if (tr == nullptr) {
10241037
// This has been invoked at too high of a level; should use a
10251038
// downcast class's `release_to_cpp` mechanism (if it supports it).
@@ -1034,21 +1047,18 @@ struct wrapper_interface_impl {
10341047
}
10351048

10361049
static object release_cpp_lifetime(type* cppobj) {
1037-
auto* tr = dynamic_cast<wrapper<type>*>(cppobj);
1050+
auto* tr = dynamic_cast<alias*>(cppobj);
10381051
if (tr == nullptr) {
10391052
// This shouldn't happen here...
10401053
throw std::runtime_error("Internal error?");
10411054
}
10421055
// Return newly created object.
10431056
return tr->release_cpp_lifetime();
10441057
}
1045-
static wrapper<type>* run(type*, std::false_type) {
1046-
return nullptr;
1047-
}
10481058
};
10491059

1050-
template <typename type>
1051-
struct wrapper_interface_impl<type, false> {
1060+
template <typename type, typename alias>
1061+
struct wrapper_interface_impl<type, alias, false> {
10521062
static void use_cpp_lifetime(type*, object&&, detail::HolderTypeId) {
10531063
// This should be captured by runtime flag.
10541064
// TODO(eric.cousineau): Runtime flag may not be necessary.
@@ -1157,6 +1167,8 @@ struct holder_check_impl<detail::HolderTypeId::UniquePtr> : public holder_check_
11571167
}
11581168
};
11591169

1170+
} // namespace detail
1171+
11601172
template <typename type_, typename... options>
11611173
class class_ : public detail::generic_type {
11621174
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
@@ -1170,7 +1182,8 @@ class class_ : public detail::generic_type {
11701182
using type = type_;
11711183
using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
11721184
constexpr static bool has_alias = !std::is_void<type_alias>::value;
1173-
constexpr static bool has_wrapper = std::is_base_of<wrapper<type>, type_alias>::value;
1185+
constexpr static bool has_wrapper =
1186+
detail::is_base_template_of<wrapper, type_alias>::value;
11741187
using holder_type = detail::exactly_one_t<is_holder, std::unique_ptr<type>, options...>;
11751188
constexpr static detail::HolderTypeId holder_type_id = detail::get_holder_type_id<holder_type>::value;
11761189

@@ -1231,8 +1244,8 @@ class class_ : public detail::generic_type {
12311244
return detail::get_type_info(id);
12321245
}
12331246

1234-
typedef wrapper_interface_impl<type, has_wrapper> wrapper_interface;
1235-
using holder_check = holder_check_impl<holder_type_id>;
1247+
using wrapper_interface = detail::wrapper_interface_impl<type, type_alias, has_wrapper>;
1248+
using holder_check = detail::holder_check_impl<holder_type_id>;
12361249

12371250
static bool allow_destruct(detail::instance* inst, detail::holder_erased holder) {
12381251
// TODO(eric.cousineau): There should not be a case where shared_ptr<> lives in

include/pybind11/pytypes.h

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,23 +1301,21 @@ inline iterator iter(handle obj) {
13011301
}
13021302
/// @} python_builtins
13031303

1304-
/// Trampoline class to permit attaching a derived Python object's data
1305-
/// (namely __dict__) to an actual C++ class.
1306-
/// If the object lives purely in C++, then there should only be one reference to
1307-
/// this data.
1304+
/// Wrapper to permit lifetime of a Python instance which is derived from a C++
1305+
/// pybind type to be managed by C++. Useful when adding virtual classes to
1306+
/// containers, where Python instance being added may be collected by Python
1307+
/// gc / refcounting.
1308+
/// @note Do NOT use the methods in this class.
13081309
template <typename Base>
13091310
class wrapper : public Base {
1310-
protected:
1311-
using Base::Base;
1312-
13131311
public:
1314-
// TODO(eric.cousineau): Complain if this is not virtual? (and remove `virtual` specifier in dtor?)
1312+
using Base::Base;
13151313

13161314
virtual ~wrapper() {
13171315
delete_py_if_in_cpp();
13181316
}
13191317

1320-
/// To be used by the holder casters, by means of `wrapper_interface<>`.
1318+
// To be used by the holder casters, by means of `wrapper_interface<>`.
13211319
// TODO(eric.cousineau): Make this private to ensure contract?
13221320
void use_cpp_lifetime(object&& patient, detail::HolderTypeId holder_type_id) {
13231321
if (lives_in_cpp()) {
@@ -1342,26 +1340,6 @@ class wrapper : public Base {
13421340
}
13431341

13441342
protected:
1345-
/// Call this if, for whatever reason, your C++ wrapper class `Base` has a non-trivial
1346-
/// destructor that needs to keep information available to the Python-extended class.
1347-
/// In this case, you want to delete the Python object *before* you do any work in your wrapper class.
1348-
///
1349-
/// As an example, say you have `Base`, and `PyBase` is your wrapper class which extends `wrapper<Base>`.
1350-
/// By default, if the instance is owned in C++ and deleted, then the destructor order will be:
1351-
/// ~PyBase()
1352-
/// do_stuff()
1353-
/// ~wrapper<Base>()
1354-
/// delete_py_if_in_cpp()
1355-
/// PyChild.__del__ - ERROR: Should have been called before `do_stuff()
1356-
/// ~Base()
1357-
/// If you explicitly call `delete_py_if_in_cpp()`, then you will get the desired order:
1358-
/// ~PyBase()
1359-
/// delete_py_if_in_cpp()
1360-
/// PyChild.__del__ - GOOD: Workzzz. Called before `do_stuff()`.
1361-
/// do_stuff()
1362-
/// ~wrapper<Base>()
1363-
/// delete_py_if_in_cpp() - No-op. Python object has been released.
1364-
/// ~Base()
13651343
// TODO(eric.cousineau): Verify this with an example workflow.
13661344
void delete_py_if_in_cpp() {
13671345
if (lives_in_cpp()) {
@@ -1398,7 +1376,7 @@ class wrapper : public Base {
13981376
}
13991377

14001378
private:
1401-
bool lives_in_cpp() const {
1379+
inline bool lives_in_cpp() const {
14021380
// NOTE: This is *false* if, for whatever reason, the wrapper class is
14031381
// constructed in C++... Meh. Not gonna worry about that situation.
14041382
return static_cast<bool>(patient_);
@@ -1408,7 +1386,6 @@ class wrapper : public Base {
14081386
detail::HolderTypeId holder_type_id_{detail::HolderTypeId::Unknown};
14091387
};
14101388

1411-
14121389
NAMESPACE_BEGIN(detail)
14131390
template <typename D> iterator object_api<D>::begin() const { return iter(derived()); }
14141391
template <typename D> iterator object_api<D>::end() const { return iterator::sentinel(); }

tests/test_ownership_transfer.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@ class DefineBaseUniqueContainer {
7575
};
7676

7777
template <int label>
78-
class DefinePyBase : public py::wrapper<DefineBase<label>> {
78+
class DefinePyBase : public DefineBase<label> {
79+
public:
80+
using BaseT = DefineBase<label>;
81+
using BaseT::BaseT;
82+
int value() const override {
83+
PYBIND11_OVERLOAD(int, BaseT, value);
84+
}
85+
};
86+
87+
template <int label>
88+
class DefinePyBaseWrapped : public py::wrapper<DefineBase<label>> {
7989
public:
8090
using BaseT = py::wrapper<DefineBase<label>>;
8191
using BaseT::BaseT;
@@ -89,7 +99,7 @@ typedef DefineBase<BaseBadLabel> BaseBad;
8999
typedef DefineBaseContainer<BaseBadLabel> BaseBadContainer;
90100
typedef Stats<ChildBadLabel> ChildBadStats;
91101

92-
// Base - with wrapper alias.
102+
// Base - wrapper alias used in pybind definition.
93103
typedef DefineBase<BaseLabel> Base;
94104
typedef DefinePyBase<BaseLabel> PyBase;
95105
typedef DefineBaseContainer<BaseLabel> BaseContainer;
@@ -101,9 +111,9 @@ typedef DefineBase<BaseBadUniqueLabel> BaseBadUnique;
101111
typedef DefineBaseUniqueContainer<BaseBadUniqueLabel> BaseBadUniqueContainer;
102112
typedef Stats<ChildBadUniqueLabel> ChildBadUniqueStats;
103113

104-
// Base - with wrapper alias.
114+
// Base - wrapper alias used directly.
105115
typedef DefineBase<BaseUniqueLabel> BaseUnique;
106-
typedef DefinePyBase<BaseUniqueLabel> PyBaseUnique;
116+
typedef DefinePyBaseWrapped<BaseUniqueLabel> PyBaseUnique;
107117
typedef DefineBaseUniqueContainer<BaseUniqueLabel> BaseUniqueContainer;
108118
typedef Stats<ChildUniqueLabel> ChildUniqueStats;
109119

@@ -135,6 +145,7 @@ template <typename... Args>
135145
using class_unique_ = py::class_<Args...>;
136146

137147
TEST_SUBMODULE(ownership_transfer, m) {
148+
// No alias - will not have lifetime extended.
138149
class_shared_<BaseBad>(m, "BaseBad")
139150
.def(py::init<int>())
140151
.def("value", &BaseBad::value);
@@ -144,8 +155,11 @@ TEST_SUBMODULE(ownership_transfer, m) {
144155
.def("release", &BaseBadContainer::release);
145156
class_shared_<ChildBadStats>(m, "ChildBadStats");
146157

147-
class_shared_<Base, PyBase>(m, "Base")
158+
// Has alias - will have lifetime extended.
159+
class_shared_<Base, py::wrapper<PyBase>>(m, "Base")
148160
.def(py::init<int>())
161+
// Factory method for alias.
162+
.def(py::init([]() { return new py::wrapper<PyBase>(10); }))
149163
.def("value", &Base::value);
150164
class_shared_<BaseContainer>(m, "BaseContainer")
151165
.def(py::init<std::shared_ptr<Base>>())
@@ -164,6 +178,8 @@ TEST_SUBMODULE(ownership_transfer, m) {
164178

165179
class_unique_<BaseUnique, PyBaseUnique>(m, "BaseUnique")
166180
.def(py::init<int>())
181+
// Factory method.
182+
.def(py::init([]() { return new PyBaseUnique(10); }))
167183
.def("value", &BaseUnique::value);
168184
class_unique_<BaseUniqueContainer>(m, "BaseUniqueContainer")
169185
.def(py::init<std::unique_ptr<BaseUnique>>())

0 commit comments

Comments
 (0)