Skip to content

Commit 99de498

Browse files
authored
Bug fix: adding back !is_alias<Class>(ptr) that were accidentally omitted. (#2958)
* Bug fix: adding back `!is_alias<Class>(ptr)` that were accidentally omitted. * Introducing PYBIND11_SH_AVL, PYBIND11_SH_DEF macros. Applying PYBIND11_SH_DEF to test_factory_constructors.py to complete test coverage. * Using PYBIND11_SH_DEF in test_methods_and_attributes.cpp, for more complete test coverage. * Using PYBIND11_SH_DEF in test_multiple_inheritance.cpp, for more complete test coverage. * Cleaning up test_classh_mock.cpp. * Better explanations for PYBIND11_SH_AVL, PYBIND11_SH_DEF. * Disabling 3.10-dev builds.
1 parent 0032083 commit 99de498

12 files changed

+175
-95
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ jobs:
2323
- 3.5
2424
- 3.6
2525
- 3.9
26-
- 3.10-dev
26+
# Broken b/o https://github.com/pytest-dev/pytest/issues/8539
27+
# - 3.10-dev
2728
- pypy2
2829
- pypy3
2930

README_smart_holder.rst

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -101,28 +101,48 @@ holder:
101101
py::classh<Foo>(m, "Foo");
102102
}
103103
104-
There are three small differences compared to classic pybind11:
104+
There are three small differences compared to Classic pybind11:
105105

106106
- ``#include <pybind11/smart_holder.h>`` is used instead of
107107
``#include <pybind11/pybind11.h>``.
108108

109+
- The ``PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo)`` macro is needed.
110+
109111
- ``py::classh`` is used instead of ``py::class_``.
110112

111-
- The ``PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo)`` macro is needed.
113+
To the 2nd bullet point, the ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro
114+
needs to appear in all translation units with pybind11 bindings that involve
115+
Python⇄C++ conversions for `Foo`. This is the biggest inconvenience of the
116+
Conservative mode. Practically, at a larger scale it is best to work with a
117+
pair of `.h` and `.cpp` files for the bindings code, with the macros in the
118+
`.h` files.
112119

113-
To the 2nd bullet point, ``py::classh<Foo>`` is simply a shortcut for
120+
To the 3rd bullet point, ``py::classh<Foo>`` is simply a shortcut for
114121
``py::class_<Foo, py::smart_holder>``. The shortcut makes it possible to
115-
switch to using ``py::smart_holder`` without messing up the indentation of
116-
existing code. However, when migrating code that uses ``py::class_<Foo,
117-
std::shared_ptr<Foo>>``, currently ``std::shared_ptr<Foo>`` needs to be
118-
removed manually when switching to ``py::classh`` (#HelpAppreciated this
119-
could probably be avoided with a little bit of template metaprogramming).
122+
switch to using ``py::smart_holder`` without disturbing the indentation of
123+
existing code.
124+
125+
When migrating code that uses ``py::class_<Bar, std::shared_ptr<Bar>>``
126+
there are two alternatives. The first one is to use ``py::classh<Bar>``:
127+
128+
.. code-block:: diff
129+
130+
- py::class_<Bar, std::shared_ptr<Bar>>(m, "Bar");
131+
+ py::classh<Bar>(m, "Bar");
132+
133+
This is clean and simple, but makes it difficult to fall back to Classic
134+
mode if needed. The second alternative is to replace ``std::shared_ptr<Bar>``
135+
with ``PYBIND11_SH_AVL(Bar)``:
120136

121-
To the 3rd bullet point, the macro also needs to appear in other translation
122-
units with pybind11 bindings that involve Python⇄C++ conversions for
123-
`Foo`. This is the biggest inconvenience of the Conservative mode. Practically,
124-
at a larger scale it is best to work with a pair of `.h` and `.cpp` files
125-
for the bindings code, with the macros in the `.h` files.
137+
.. code-block:: diff
138+
139+
- py::class_<Bar, std::shared_ptr<Bar>>(m, "Bar");
140+
+ py::class_<Bar, PYBIND11_SH_AVL(Bar)>(m, "Bar");
141+
142+
The ``PYBIND11_SH_AVL`` macro substitutes ``py::smart_holder``
143+
in Conservative mode, or ``std::shared_ptr<Bar>`` in Classic mode.
144+
See tests/test_classh_mock.cpp for an example. Note that the macro is also
145+
designed to not disturb the indentation of existing code.
126146

127147

128148
Progressive mode
@@ -132,47 +152,63 @@ To work in Progressive mode:
132152

133153
- Add ``-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` to the compilation commands.
134154

135-
- Remove any ``std::shared_ptr<...>`` holders from existing ``py::class_``
136-
instantiations.
155+
- Remove or replace (see below) ``std::shared_ptr<...>`` holders.
137156

138157
- Only if custom smart-pointers are used: the
139-
`PYBIND11_TYPE_CASTER_BASE_HOLDER` macro is needed [`example
140-
<https://github.com/pybind/pybind11/blob/2f624af1ac8571d603df2d70cb54fc7e2e3a356a/tests/test_multiple_inheritance.cpp#L72>`_].
158+
`PYBIND11_TYPE_CASTER_BASE_HOLDER` macro is needed (see
159+
tests/test_smart_ptr.cpp for examples).
141160

142161
Overall this is probably easier to work with than the Conservative mode, but
143162

144163
- the macro inconvenience is shifted from ``py::smart_holder`` to custom
145-
smart-pointers (but probably much more rarely needed).
164+
smart-pointer holders (which are probably much more rare).
146165

147166
- it will not interoperate with other extensions built against master or
148167
stable, or extensions built in Conservative mode (see the cross-module
149168
compatibility section below).
150169

170+
When migrating code that uses ``py::class_<Bar, std::shared_ptr<Bar>>`` there
171+
are the same alternatives as for the Conservative mode (see previous section).
172+
An additional alternative is to use the ``PYBIND11_SH_DEF(...)`` macro:
173+
174+
.. code-block:: diff
175+
176+
- py::class_<Bar, std::shared_ptr<Bar>>(m, "Bar");
177+
+ py::class_<Bar, PYBIND11_SH_DEF(Bar)>(m, "Bar");
178+
179+
The ``PYBIND11_SH_DEF`` macro substitutes ``py::smart_holder`` only in
180+
Progressive mode, or ``std::shared_ptr<Bar>`` in Classic or Conservative
181+
mode. See tests/test_classh_mock.cpp for an example. Note that the
182+
``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro is never needed in combination
183+
with the ``PYBIND11_SH_DEF`` macro, which is an advantage compared to the
184+
``PYBIND11_SH_AVL`` macro. Please review tests/test_classh_mock.cpp for a
185+
concise overview of all available options.
186+
151187

152-
Transition from Conservative to Progressive mode
153-
------------------------------------------------
188+
Transition from Classic to Progressive mode
189+
-------------------------------------------
154190

155191
This still has to be tried out more in practice, but in small-scale situations
156192
it may be feasible to switch directly to Progressive mode in a break-fix
157193
fashion. In large-scale situations it seems more likely that an incremental
158194
approach is needed, which could mean incrementally converting ``py::class_``
159-
to ``py::classh`` including addition of the macros, then flip the switch,
160-
and convert ``py::classh`` back to ``py:class_`` combined with removal of the
161-
macros if desired (at that point it will work equivalently either way). It
162-
may be smart to delay the final cleanup step until all third-party projects
163-
of interest have made the switch, because then the code will continue to
164-
work in either mode.
195+
to ``py::classh`` and using the family of related macros, then flip the switch
196+
to Progressive mode, and convert ``py::classh`` back to ``py:class_`` combined
197+
with removal of the macros if desired (at that point it will work equivalently
198+
either way). It may be smart to delay the final cleanup step until all
199+
third-party projects of interest have made the switch, because then the code
200+
will continue to work in all modes.
165201

166202

167-
Using py::classh but with fallback to classic pybind11
168-
------------------------------------------------------
203+
Using py::smart_holder but with fallback to Classic pybind11
204+
------------------------------------------------------------
169205

170-
This could be viewed as super-conservative mode, for situations in which
171-
compatibility with classic pybind11 (without smart_holder) is needed for
172-
some period of time. The main idea is to enable use of ``py::classh``
173-
and the associated ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro while
174-
still being able to build the same code with classic pybind11. Please see
175-
tests/test_classh_mock.cpp for an example.
206+
For situations in which compatibility with Classic pybind11
207+
(without smart_holder) is needed for some period of time, fallback
208+
to Classic mode can be enabled by copying the ``BOILERPLATE`` code
209+
block from tests/test_classh_mock.cpp. This code block provides mock
210+
implementations of ``py::classh`` and the family of related macros
211+
(e.g. ``PYBIND11_SMART_HOLDER_TYPE_CASTERS``).
176212

177213

178214
Classic / Conservative / Progressive cross-module compatibility
@@ -268,7 +304,7 @@ inherit from ``py::trampoline_self_life_support``, for example:
268304
...
269305
};
270306
271-
This is the only difference compared to classic pybind11. A fairly
307+
This is the only difference compared to Classic pybind11. A fairly
272308
minimal but complete example is tests/test_class_sh_trampoline_unique_ptr.cpp.
273309

274310

@@ -277,7 +313,7 @@ Ideas for the long-term
277313

278314
The macros are clearly an inconvenience in many situations. Highly
279315
speculative: to avoid the need for the macros, a potential approach would
280-
be to combine the classic implementation (``type_caster_base``) with
316+
be to combine the Classic implementation (``type_caster_base``) with
281317
the ``smart_holder_type_caster``, but this will probably be very messy and
282318
not great as a long-term solution. The ``type_caster_base`` code is very
283319
complex already. A more maintainable approach long-term could be to work

include/pybind11/detail/init.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ template <
179179
void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, D> &&unq_ptr, bool need_alias) {
180180
auto *ptr = unq_ptr.get();
181181
no_nullptr(ptr);
182-
if (Class::has_alias && need_alias)
182+
if (Class::has_alias && need_alias && !is_alias<Class>(ptr))
183183
throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee "
184184
"is not an alias instance");
185185
auto smhldr
@@ -209,7 +209,7 @@ template <
209209
void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&shd_ptr, bool need_alias) {
210210
auto *ptr = shd_ptr.get();
211211
no_nullptr(ptr);
212-
if (Class::has_alias && need_alias)
212+
if (Class::has_alias && need_alias && !is_alias<Class>(ptr))
213213
throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee "
214214
"is not an alias instance");
215215
auto smhldr = type_caster<Cpp<Class>>::template smart_holder_from_shared_ptr(shd_ptr);

include/pybind11/pybind11.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,12 +1217,30 @@ template <typename T>
12171217

12181218
using default_holder_type = std::unique_ptr<T>;
12191219

1220+
# ifndef PYBIND11_SH_AVL
1221+
# define PYBIND11_SH_AVL(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if AVaiLable"
1222+
// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation
1223+
// of existing code.
1224+
# endif
1225+
1226+
# define PYBIND11_SH_DEF(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if DEFault"
1227+
// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation
1228+
// of existing code.
1229+
12201230
# define PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...)
12211231

12221232
#else
12231233

12241234
using default_holder_type = smart_holder;
12251235

1236+
# ifndef PYBIND11_SH_AVL
1237+
# define PYBIND11_SH_AVL(...) ::pybind11::smart_holder // "Smart_Holder if AVaiLable"
1238+
// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation
1239+
// of existing code.
1240+
# endif
1241+
1242+
# define PYBIND11_SH_DEF(...) ::pybind11::smart_holder // "Smart_Holder if DEFault"
1243+
12261244
// This define could be hidden away inside detail/smart_holder_type_casters.h, but is kept here
12271245
// for clarity.
12281246
# define PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...) \

include/pybind11/smart_holder.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
#include "detail/smart_holder_type_casters.h"
99
#include "pybind11.h"
1010

11+
#undef PYBIND11_SH_AVL // Undoing #define in pybind11.h
12+
13+
#define PYBIND11_SH_AVL(...) ::pybind11::smart_holder // "Smart_Holder if AVaiLable"
14+
// ---- std::shared_ptr(...) -- same length by design, to not disturb the indentation
15+
// of existing code.
16+
1117
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
1218

1319
// Supports easier switching between py::class_<T> and py::class_<T, py::smart_holder>:

tests/test_classh_mock.cpp

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
1414
template <typename type_, typename... options>
1515
using classh = class_<type_, options...>;
1616
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
17+
# ifndef PYBIND11_SH_AVL
18+
# define PYBIND11_SH_AVL(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if AVaiLable"
19+
# endif
20+
# ifndef PYBIND11_SH_DEF
21+
# define PYBIND11_SH_DEF(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if DEFault"
22+
# endif
1723
# ifndef PYBIND11_SMART_HOLDER_TYPE_CASTERS
1824
# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...)
1925
# endif
@@ -24,23 +30,42 @@ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
2430
// BOILERPLATE END
2531

2632
namespace {
27-
struct Foo0 {};
28-
struct Foo1 {};
29-
struct Foo2 {};
33+
struct FooUc {};
34+
struct FooUp {};
35+
struct FooSa {};
36+
struct FooSc {};
37+
struct FooSp {};
3038
} // namespace
3139

32-
PYBIND11_TYPE_CASTER_BASE_HOLDER(Foo1, std::shared_ptr<Foo1>)
33-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo2)
40+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooUp)
41+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooSp)
42+
43+
PYBIND11_TYPE_CASTER_BASE_HOLDER(FooSa, std::shared_ptr<FooSa>)
3444

3545
TEST_SUBMODULE(classh_mock, m) {
36-
// Uses std::unique_ptr<Foo0> as holder in conservative mode, py::smart_holder in progressive
37-
// mode (if available).
38-
py::class_<Foo0>(m, "Foo0").def(py::init<>());
46+
// Please see README_smart_holder.rst, in particular section
47+
// Classic / Conservative / Progressive cross-module compatibility
48+
49+
// Uses std::unique_ptr<FooUc> as holder in Classic or Conservative mode, py::smart_holder in
50+
// Progressive mode.
51+
py::class_<FooUc>(m, "FooUc").def(py::init<>());
52+
53+
// Uses std::unique_ptr<FooUp> as holder in Classic mode, py::smart_holder in Conservative or
54+
// Progressive mode.
55+
py::classh<FooUp>(m, "FooUp").def(py::init<>());
56+
57+
// Always uses std::shared_ptr<FooSa> as holder.
58+
py::class_<FooSa, std::shared_ptr<FooSa>>(m, "FooSa").def(py::init<>());
3959

40-
// Always uses std::shared_ptr<Foo1> as holder.
41-
py::class_<Foo1, std::shared_ptr<Foo1>>(m, "Foo1").def(py::init<>());
60+
// Uses std::shared_ptr<FooSc> as holder in Classic or Conservative mode, py::smart_holder in
61+
// Progressive mode.
62+
py::class_<FooSc, PYBIND11_SH_DEF(FooSc)>(m, "FooSc").def(py::init<>());
63+
// -------------- std::shared_ptr<FooSc> -- same length by design, to not disturb the
64+
// indentation of existing code.
4265

43-
// Uses py::smart_holder if available, or std::unique_ptr<Foo2> if only pybind11 classic is
44-
// available.
45-
py::classh<Foo2>(m, "Foo2").def(py::init<>());
66+
// Uses std::shared_ptr<FooSp> as holder in Classic mode, py::smart_holder in Conservative or
67+
// Progressive mode.
68+
py::class_<FooSp, PYBIND11_SH_AVL(FooSp)>(m, "FooSp").def(py::init<>());
69+
// -------------- std::shared_ptr<FooSp> -- same length by design, to not disturb the
70+
// indentation of existing code.
4671
}

tests/test_classh_mock.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
def test_foobar():
77
# Not really testing anything in particular. The main purpose of this test is to ensure the
88
# suggested BOILERPLATE code block in test_classh_mock.cpp is correct.
9-
assert m.Foo0()
10-
assert m.Foo1()
11-
assert m.Foo2()
9+
assert m.FooUc()
10+
assert m.FooUp()
11+
assert m.FooSa()
12+
assert m.FooSc()
13+
assert m.FooSp()

tests/test_factory_constructors.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,6 @@ class TestFactoryHelper {
139139
static std::shared_ptr<TestFactory3> construct3(int a) { return std::shared_ptr<TestFactory3>(new TestFactory3(a)); }
140140
};
141141

142-
PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory3, std::shared_ptr<TestFactory3>)
143-
PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory4, std::shared_ptr<TestFactory4>)
144-
PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory5, std::shared_ptr<TestFactory5>)
145-
PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory7, std::shared_ptr<TestFactory7>)
146-
147142
TEST_SUBMODULE(factory_constructors, m) {
148143

149144
// Define various trivial types to allow simpler overload resolution:
@@ -188,7 +183,7 @@ TEST_SUBMODULE(factory_constructors, m) {
188183
auto c4a = [c](pointer_tag, TF4_tag, int a) { (void) c; return new TestFactory4(a);};
189184

190185
// test_init_factory_basic, test_init_factory_casting
191-
py::class_<TestFactory3, std::shared_ptr<TestFactory3>> pyTestFactory3(m, "TestFactory3");
186+
py::class_<TestFactory3, PYBIND11_SH_DEF(TestFactory3)> pyTestFactory3(m, "TestFactory3");
192187
pyTestFactory3
193188
.def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); }))
194189
.def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); }));
@@ -212,12 +207,12 @@ TEST_SUBMODULE(factory_constructors, m) {
212207
;
213208

214209
// test_init_factory_casting
215-
py::class_<TestFactory4, TestFactory3, std::shared_ptr<TestFactory4>>(m, "TestFactory4")
210+
py::class_<TestFactory4, TestFactory3, PYBIND11_SH_DEF(TestFactory4)>(m, "TestFactory4")
216211
.def(py::init(c4a)) // pointer
217212
;
218213

219214
// Doesn't need to be registered, but registering makes getting ConstructorStats easier:
220-
py::class_<TestFactory5, TestFactory3, std::shared_ptr<TestFactory5>>(m, "TestFactory5");
215+
py::class_<TestFactory5, TestFactory3, PYBIND11_SH_DEF(TestFactory5)>(m, "TestFactory5");
221216

222217
// test_init_factory_alias
223218
// Alias testing
@@ -238,7 +233,7 @@ TEST_SUBMODULE(factory_constructors, m) {
238233

239234
// test_init_factory_dual
240235
// Separate alias constructor testing
241-
py::class_<TestFactory7, PyTF7, std::shared_ptr<TestFactory7>>(m, "TestFactory7")
236+
py::class_<TestFactory7, PyTF7, PYBIND11_SH_DEF(TestFactory7)>(m, "TestFactory7")
242237
.def(py::init(
243238
[](int i) { return TestFactory7(i); },
244239
[](int i) { return PyTF7(i); }))

tests/test_factory_constructors.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,11 @@ def get(self):
280280
assert not g1.has_alias()
281281
with pytest.raises(TypeError) as excinfo:
282282
PythFactory7(tag.shared_ptr, tag.invalid_base, 14)
283-
assert (
284-
str(excinfo.value)
285-
== "pybind11::init(): construction failed: returned holder-wrapped instance is not an "
286-
"alias instance"
283+
assert str(excinfo.value) in (
284+
"pybind11::init(): construction failed: returned holder-wrapped instance is not an "
285+
"alias instance",
286+
"pybind11::init(): construction failed: returned std::shared_ptr pointee is not an "
287+
"alias instance",
287288
)
288289

289290
assert [i.alive() for i in cstats] == [13, 7]

0 commit comments

Comments
 (0)