diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7ff6b3be..4060ed713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,8 @@ jobs: - 3.5 - 3.6 - 3.9 - - 3.10-dev + # Broken b/o https://github.com/pytest-dev/pytest/issues/8539 + # - 3.10-dev - pypy2 - pypy3 diff --git a/README_smart_holder.rst b/README_smart_holder.rst index 4dc769e76..ba2c97748 100644 --- a/README_smart_holder.rst +++ b/README_smart_holder.rst @@ -101,28 +101,48 @@ holder: py::classh(m, "Foo"); } -There are three small differences compared to classic pybind11: +There are three small differences compared to Classic pybind11: - ``#include `` is used instead of ``#include ``. -- ``py::classh`` is used instead of ``py::class_``. - - The ``PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo)`` macro is needed. -To the 2nd bullet point, ``py::classh`` is simply a shortcut for -``py::class_``. The shortcut makes it possible to -switch to using ``py::smart_holder`` without messing up the indentation of -existing code. However, when migrating code that uses ``py::class_>``, currently ``std::shared_ptr`` needs to be -removed manually when switching to ``py::classh`` (#HelpAppreciated this -could probably be avoided with a little bit of template metaprogramming). +- ``py::classh`` is used instead of ``py::class_``. -To the 3rd bullet point, the macro also needs to appear in other translation -units with pybind11 bindings that involve Python⇄C++ conversions for -`Foo`. This is the biggest inconvenience of the Conservative mode. Practically, -at a larger scale it is best to work with a pair of `.h` and `.cpp` files -for the bindings code, with the macros in the `.h` files. +To the 2nd bullet point, the ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro +needs to appear in all translation units with pybind11 bindings that involve +Python⇄C++ conversions for `Foo`. This is the biggest inconvenience of the +Conservative mode. Practically, at a larger scale it is best to work with a +pair of `.h` and `.cpp` files for the bindings code, with the macros in the +`.h` files. + +To the 3rd bullet point, ``py::classh`` is simply a shortcut for +``py::class_``. The shortcut makes it possible to +switch to using ``py::smart_holder`` without disturbing the indentation of +existing code. + +When migrating code that uses ``py::class_>`` +there are two alternatives. The first one is to use ``py::classh``: + +.. code-block:: diff + + - py::class_>(m, "Bar"); + + py::classh(m, "Bar"); + +This is clean and simple, but makes it difficult to fall back to Classic +mode if needed. The second alternative is to replace ``std::shared_ptr`` +with ``PYBIND11_SH_AVL(Bar)``: + +.. code-block:: diff + + - py::class_>(m, "Bar"); + + py::class_(m, "Bar"); + +The ``PYBIND11_SH_AVL`` macro substitutes ``py::smart_holder`` +in Conservative mode, or ``std::shared_ptr`` in Classic mode. +See tests/test_classh_mock.cpp for an example. Note that the macro is also +designed to not disturb the indentation of existing code. Progressive mode @@ -132,47 +152,63 @@ To work in Progressive mode: - Add ``-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` to the compilation commands. -- Remove any ``std::shared_ptr<...>`` holders from existing ``py::class_`` - instantiations. +- Remove or replace (see below) ``std::shared_ptr<...>`` holders. - Only if custom smart-pointers are used: the - `PYBIND11_TYPE_CASTER_BASE_HOLDER` macro is needed [`example - `_]. + `PYBIND11_TYPE_CASTER_BASE_HOLDER` macro is needed (see + tests/test_smart_ptr.cpp for examples). Overall this is probably easier to work with than the Conservative mode, but - the macro inconvenience is shifted from ``py::smart_holder`` to custom - smart-pointers (but probably much more rarely needed). + smart-pointer holders (which are probably much more rare). - it will not interoperate with other extensions built against master or stable, or extensions built in Conservative mode (see the cross-module compatibility section below). +When migrating code that uses ``py::class_>`` there +are the same alternatives as for the Conservative mode (see previous section). +An additional alternative is to use the ``PYBIND11_SH_DEF(...)`` macro: -Transition from Conservative to Progressive mode ------------------------------------------------- +.. code-block:: diff + + - py::class_>(m, "Bar"); + + py::class_(m, "Bar"); + +The ``PYBIND11_SH_DEF`` macro substitutes ``py::smart_holder`` only in +Progressive mode, or ``std::shared_ptr`` in Classic or Conservative +mode. See tests/test_classh_mock.cpp for an example. Note that the +``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro is never needed in combination +with the ``PYBIND11_SH_DEF`` macro, which is an advantage compared to the +``PYBIND11_SH_AVL`` macro. Please review tests/test_classh_mock.cpp for a +concise overview of all available options. + + +Transition from Classic to Progressive mode +------------------------------------------- This still has to be tried out more in practice, but in small-scale situations it may be feasible to switch directly to Progressive mode in a break-fix fashion. In large-scale situations it seems more likely that an incremental approach is needed, which could mean incrementally converting ``py::class_`` -to ``py::classh`` including addition of the macros, then flip the switch, -and convert ``py::classh`` back to ``py:class_`` combined with removal of the -macros if desired (at that point it will work equivalently either way). It -may be smart to delay the final cleanup step until all third-party projects -of interest have made the switch, because then the code will continue to -work in either mode. +to ``py::classh`` and using the family of related macros, then flip the switch +to Progressive mode, and convert ``py::classh`` back to ``py:class_`` combined +with removal of the macros if desired (at that point it will work equivalently +either way). It may be smart to delay the final cleanup step until all +third-party projects of interest have made the switch, because then the code +will continue to work in all modes. -Using py::classh but with fallback to classic pybind11 ------------------------------------------------------- +Using py::smart_holder but with fallback to Classic pybind11 +------------------------------------------------------------ -This could be viewed as super-conservative mode, for situations in which -compatibility with classic pybind11 (without smart_holder) is needed for -some period of time. The main idea is to enable use of ``py::classh`` -and the associated ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro while -still being able to build the same code with classic pybind11. Please see -tests/test_classh_mock.cpp for an example. +For situations in which compatibility with Classic pybind11 +(without smart_holder) is needed for some period of time, fallback +to Classic mode can be enabled by copying the ``BOILERPLATE`` code +block from tests/test_classh_mock.cpp. This code block provides mock +implementations of ``py::classh`` and the family of related macros +(e.g. ``PYBIND11_SMART_HOLDER_TYPE_CASTERS``). Classic / Conservative / Progressive cross-module compatibility @@ -268,7 +304,7 @@ inherit from ``py::trampoline_self_life_support``, for example: ... }; -This is the only difference compared to classic pybind11. A fairly +This is the only difference compared to Classic pybind11. A fairly minimal but complete example is tests/test_class_sh_trampoline_unique_ptr.cpp. @@ -277,7 +313,7 @@ Ideas for the long-term The macros are clearly an inconvenience in many situations. Highly speculative: to avoid the need for the macros, a potential approach would -be to combine the classic implementation (``type_caster_base``) with +be to combine the Classic implementation (``type_caster_base``) with the ``smart_holder_type_caster``, but this will probably be very messy and not great as a long-term solution. The ``type_caster_base`` code is very complex already. A more maintainable approach long-term could be to work diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 2c1c77ae9..0aea1f52c 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -179,7 +179,7 @@ template < void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, bool need_alias) { auto *ptr = unq_ptr.get(); no_nullptr(ptr); - if (Class::has_alias && need_alias) + if (Class::has_alias && need_alias && !is_alias(ptr)) throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee " "is not an alias instance"); auto smhldr @@ -209,7 +209,7 @@ template < void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, bool need_alias) { auto *ptr = shd_ptr.get(); no_nullptr(ptr); - if (Class::has_alias && need_alias) + if (Class::has_alias && need_alias && !is_alias(ptr)) throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee " "is not an alias instance"); auto smhldr = type_caster>::template smart_holder_from_shared_ptr(shd_ptr); diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 12f79d42e..ed21b8fd4 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1217,12 +1217,30 @@ template using default_holder_type = std::unique_ptr; +# ifndef PYBIND11_SH_AVL +# define PYBIND11_SH_AVL(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if AVaiLable" +// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation +// of existing code. +# endif + +# define PYBIND11_SH_DEF(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if DEFault" +// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation +// of existing code. + # define PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...) #else using default_holder_type = smart_holder; +# ifndef PYBIND11_SH_AVL +# define PYBIND11_SH_AVL(...) ::pybind11::smart_holder // "Smart_Holder if AVaiLable" +// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation +// of existing code. +# endif + +# define PYBIND11_SH_DEF(...) ::pybind11::smart_holder // "Smart_Holder if DEFault" + // This define could be hidden away inside detail/smart_holder_type_casters.h, but is kept here // for clarity. # define PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...) \ diff --git a/include/pybind11/smart_holder.h b/include/pybind11/smart_holder.h index bf7cfb9dd..f852f77e2 100644 --- a/include/pybind11/smart_holder.h +++ b/include/pybind11/smart_holder.h @@ -8,6 +8,12 @@ #include "detail/smart_holder_type_casters.h" #include "pybind11.h" +#undef PYBIND11_SH_AVL // Undoing #define in pybind11.h + +#define PYBIND11_SH_AVL(...) ::pybind11::smart_holder // "Smart_Holder if AVaiLable" +// ---- std::shared_ptr(...) -- same length by design, to not disturb the indentation +// of existing code. + PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) // Supports easier switching between py::class_ and py::class_: diff --git a/tests/test_classh_mock.cpp b/tests/test_classh_mock.cpp index af016c7f8..38e765fb0 100644 --- a/tests/test_classh_mock.cpp +++ b/tests/test_classh_mock.cpp @@ -14,6 +14,12 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) template using classh = class_; PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +# ifndef PYBIND11_SH_AVL +# define PYBIND11_SH_AVL(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if AVaiLable" +# endif +# ifndef PYBIND11_SH_DEF +# define PYBIND11_SH_DEF(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if DEFault" +# endif # ifndef PYBIND11_SMART_HOLDER_TYPE_CASTERS # define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) # endif @@ -24,23 +30,42 @@ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) // BOILERPLATE END namespace { -struct Foo0 {}; -struct Foo1 {}; -struct Foo2 {}; +struct FooUc {}; +struct FooUp {}; +struct FooSa {}; +struct FooSc {}; +struct FooSp {}; } // namespace -PYBIND11_TYPE_CASTER_BASE_HOLDER(Foo1, std::shared_ptr) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo2) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooUp) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooSp) + +PYBIND11_TYPE_CASTER_BASE_HOLDER(FooSa, std::shared_ptr) TEST_SUBMODULE(classh_mock, m) { - // Uses std::unique_ptr as holder in conservative mode, py::smart_holder in progressive - // mode (if available). - py::class_(m, "Foo0").def(py::init<>()); + // Please see README_smart_holder.rst, in particular section + // Classic / Conservative / Progressive cross-module compatibility - // Always uses std::shared_ptr as holder. - py::class_>(m, "Foo1").def(py::init<>()); + // Uses std::unique_ptr as holder in Classic or Conservative mode, py::smart_holder in + // Progressive mode. + py::class_(m, "FooUc").def(py::init<>()); - // Uses py::smart_holder if available, or std::unique_ptr if only pybind11 classic is - // available. - py::classh(m, "Foo2").def(py::init<>()); + // Uses std::unique_ptr as holder in Classic mode, py::smart_holder in Conservative or + // Progressive mode. + py::classh(m, "FooUp").def(py::init<>()); + + // Always uses std::shared_ptr as holder. + py::class_>(m, "FooSa").def(py::init<>()); + + // Uses std::shared_ptr as holder in Classic or Conservative mode, py::smart_holder in + // Progressive mode. + py::class_(m, "FooSc").def(py::init<>()); + // -------------- std::shared_ptr -- same length by design, to not disturb the + // indentation of existing code. + + // Uses std::shared_ptr as holder in Classic mode, py::smart_holder in Conservative or + // Progressive mode. + py::class_(m, "FooSp").def(py::init<>()); + // -------------- std::shared_ptr -- same length by design, to not disturb the + // indentation of existing code. } diff --git a/tests/test_classh_mock.py b/tests/test_classh_mock.py index 2c1d48754..03d323eae 100644 --- a/tests/test_classh_mock.py +++ b/tests/test_classh_mock.py @@ -6,6 +6,8 @@ from pybind11_tests import classh_mock as m def test_foobar(): # Not really testing anything in particular. The main purpose of this test is to ensure the # suggested BOILERPLATE code block in test_classh_mock.cpp is correct. - assert m.Foo0() - assert m.Foo1() - assert m.Foo2() + assert m.FooUc() + assert m.FooUp() + assert m.FooSa() + assert m.FooSc() + assert m.FooSp() diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index a4e4829e5..db1a1b18a 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -139,11 +139,6 @@ public: static std::shared_ptr construct3(int a) { return std::shared_ptr(new TestFactory3(a)); } }; -PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory3, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory4, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory5, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(TestFactory7, std::shared_ptr) - TEST_SUBMODULE(factory_constructors, m) { // Define various trivial types to allow simpler overload resolution: @@ -188,7 +183,7 @@ TEST_SUBMODULE(factory_constructors, m) { auto c4a = [c](pointer_tag, TF4_tag, int a) { (void) c; return new TestFactory4(a);}; // test_init_factory_basic, test_init_factory_casting - py::class_> pyTestFactory3(m, "TestFactory3"); + py::class_ pyTestFactory3(m, "TestFactory3"); pyTestFactory3 .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); })) .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); })); @@ -212,12 +207,12 @@ TEST_SUBMODULE(factory_constructors, m) { ; // test_init_factory_casting - py::class_>(m, "TestFactory4") + py::class_(m, "TestFactory4") .def(py::init(c4a)) // pointer ; // Doesn't need to be registered, but registering makes getting ConstructorStats easier: - py::class_>(m, "TestFactory5"); + py::class_(m, "TestFactory5"); // test_init_factory_alias // Alias testing @@ -238,7 +233,7 @@ TEST_SUBMODULE(factory_constructors, m) { // test_init_factory_dual // Separate alias constructor testing - py::class_>(m, "TestFactory7") + py::class_(m, "TestFactory7") .def(py::init( [](int i) { return TestFactory7(i); }, [](int i) { return PyTF7(i); })) diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 8c6ca173a..ed8e6d52c 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -280,10 +280,11 @@ def test_init_factory_dual(): assert not g1.has_alias() with pytest.raises(TypeError) as excinfo: PythFactory7(tag.shared_ptr, tag.invalid_base, 14) - assert ( - str(excinfo.value) - == "pybind11::init(): construction failed: returned holder-wrapped instance is not an " - "alias instance" + assert str(excinfo.value) in ( + "pybind11::init(): construction failed: returned holder-wrapped instance is not an " + "alias instance", + "pybind11::init(): construction failed: returned std::shared_ptr pointee is not an " + "alias instance", ) assert [i.alive() for i in cstats] == [13, 7] diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp index d4c2c1466..ddf3b4526 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -113,7 +113,7 @@ class NoneTester { public: int answer = 42; }; int none1(const NoneTester &obj) { return obj.answer; } int none2(NoneTester *obj) { return obj ? obj->answer : -1; } int none3(const std::shared_ptr &obj) { return obj ? obj->answer : -1; } -int none4(const std::shared_ptr *obj) { return obj && *obj ? (*obj)->answer : -1; } +int none4(std::shared_ptr *obj) { return obj && *obj ? (*obj)->answer : -1; } int none5(std::shared_ptr obj) { return obj ? obj->answer : -1; } struct StrIssue { @@ -148,8 +148,6 @@ struct RefQualified { int constRefQualified(int other) const & { return value + other; } }; -PYBIND11_TYPE_CASTER_BASE_HOLDER(NoneTester, std::shared_ptr) - TEST_SUBMODULE(methods_and_attributes, m) { // test_methods_and_attributes py::class_ emna(m, "ExampleMandA"); @@ -327,19 +325,23 @@ TEST_SUBMODULE(methods_and_attributes, m) { // [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works. // test_accepts_none - py::class_>(m, "NoneTester") + py::class_(m, "NoneTester") .def(py::init<>()); m.def("no_none1", &none1, py::arg{}.none(false)); m.def("no_none2", &none2, py::arg{}.none(false)); m.def("no_none3", &none3, py::arg{}.none(false)); - m.def("no_none4", &none4, py::arg{}.none(false)); m.def("no_none5", &none5, py::arg{}.none(false)); m.def("ok_none1", &none1); m.def("ok_none2", &none2, py::arg{}.none(true)); m.def("ok_none3", &none3); - m.def("ok_none4", &none4, py::arg{}.none(true)); m.def("ok_none5", &none5); +#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT + // smart_holder_type_caster does not support conversion to `const shared_ptr *`. + m.def("no_none4", &none4, py::arg{}.none(false)); + m.def("ok_none4", &none4, py::arg{}.none(true)); +#endif + m.def("no_none_kwarg", &none2, "a"_a.none(false)); m.def("no_none_kwarg_kw_only", &none2, py::kw_only(), "a"_a.none(false)); diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 2aaf9331f..8328dcd4f 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -374,12 +374,10 @@ def test_accepts_none(msg): assert m.no_none1(a) == 42 assert m.no_none2(a) == 42 assert m.no_none3(a) == 42 - assert m.no_none4(a) == 42 assert m.no_none5(a) == 42 assert m.ok_none1(a) == 42 assert m.ok_none2(a) == 42 assert m.ok_none3(a) == 42 - assert m.ok_none4(a) == 42 assert m.ok_none5(a) == 42 with pytest.raises(TypeError) as excinfo: @@ -391,9 +389,6 @@ def test_accepts_none(msg): with pytest.raises(TypeError) as excinfo: m.no_none3(None) assert "incompatible function arguments" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - m.no_none4(None) - assert "incompatible function arguments" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: m.no_none5(None) assert "incompatible function arguments" in str(excinfo.value) @@ -414,7 +409,6 @@ def test_accepts_none(msg): # The rest take the argument as pointer or holder, and accept None: assert m.ok_none2(None) == -1 assert m.ok_none3(None) == -1 - assert m.ok_none4(None) == -1 assert m.ok_none5(None) == -1 with pytest.raises(TypeError) as excinfo: @@ -430,6 +424,14 @@ def test_accepts_none(msg): m.no_none_kwarg_kw_only(a=None) assert "incompatible function arguments" in str(excinfo.value) + if hasattr(m, "no_none4"): + assert m.no_none4(a) == 42 + assert m.ok_none4(a) == 42 + with pytest.raises(TypeError) as excinfo: + m.no_none4(None) + assert "incompatible function arguments" in str(excinfo.value) + assert m.ok_none4(None) == -1 + def test_str_issue(msg): """#283: __str__ called on uninitialized instance when constructor arguments invalid""" diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 23e6341dd..6243deab5 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -69,14 +69,6 @@ struct I801D : I801C {}; // Indirect MI } // namespace -PYBIND11_TYPE_CASTER_BASE_HOLDER(Base1a, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(Base2a, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(Base12a, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(I801B1, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(I801B2, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(I801C, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(I801D, std::shared_ptr) - TEST_SUBMODULE(multiple_inheritance, m) { // Please do not interleave `struct` and `class` definitions with bindings code, // but implement `struct`s and `class`es in the anonymous namespace above. @@ -136,16 +128,16 @@ TEST_SUBMODULE(multiple_inheritance, m) { // test_multiple_inheritance_virtbase // Test the case where not all base classes are specified, and where pybind11 requires the // py::multiple_inheritance flag to perform proper casting between types. - py::class_>(m, "Base1a") + py::class_(m, "Base1a") .def(py::init()) .def("foo", &Base1a::foo); - py::class_>(m, "Base2a") + py::class_(m, "Base2a") .def(py::init()) .def("bar", &Base2a::bar); py::class_>(m, "Base12a", py::multiple_inheritance()) + PYBIND11_SH_DEF(Base12a)>(m, "Base12a", py::multiple_inheritance()) .def(py::init()); m.def("bar_base2a", [](Base2a *b) { return b->bar(); }); @@ -158,10 +150,10 @@ TEST_SUBMODULE(multiple_inheritance, m) { struct I801B3 { int c = 3; virtual ~I801B3() = default; }; struct I801E : I801B3, I801D {}; - py::class_>(m, "I801B1").def(py::init<>()).def_readonly("a", &I801B1::a); - py::class_>(m, "I801B2").def(py::init<>()).def_readonly("b", &I801B2::b); - py::class_>(m, "I801C").def(py::init<>()); - py::class_>(m, "I801D").def(py::init<>()); + py::class_(m, "I801B1").def(py::init<>()).def_readonly("a", &I801B1::a); + py::class_(m, "I801B2").def(py::init<>()).def_readonly("b", &I801B2::b); + py::class_(m, "I801C").def(py::init<>()); + py::class_(m, "I801D").def(py::init<>()); // Two separate issues here: first, we want to recognize a pointer to a base type as being a // known instance even when the pointer value is unequal (i.e. due to a non-first