From 48f25275c44d52d0ceade122e328dc1f2e48ef44 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Wed, 31 Jul 2024 20:17:31 +0700 Subject: [PATCH] [smart_holder] Bake smart_holder functionality into `class_` and `type_caster_base` (#5257) * Put bakein branch @ 18b72c0ffa6ff2747ed6c4b869a80adfb8e762c9 on top of smart_holder branch: Commands used: ``` git checkout bakein git diff smart_holder > ~/zd git checkout smart_holder git checkout -b bakein_sh patch -p 1 < ~/zd git checkout smart_holder \ MANIFEST.in \ README.rst \ README_smart_holder.rst \ docs/advanced/smart_ptrs.rst \ ubench/holder_comparison.cpp \ ubench/holder_comparison.py \ ubench/holder_comparison_extract_sheet_data.py \ ubench/number_bucket.h \ ubench/python/number_bucket.clif git add -A ``` * Add back README_smart_holder.rst in tests/extra_python_package/test_files.py * Restore smart_holder_poc.h as-is on smart_holder branch (i.e. undo `PYBIND11_SMART_HOLDER_PADDING`, which was meant for stress-testing only). * Insert `std::move()` as suggested by @laramiel * `property_cpp_function_sh_*` named specializations, as suggested by @laramiel (https://github.com/pybind/pybind11/pull/5257#discussion_r1688346807) * Call `property_cpp_function_classic` member functions, rather than inlining the implementations. * Use `PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT` in holder_comparison.cpp (holder_comparison.py is NOT changed accordingly in this commit, i.e. can still only be run if the smart_holder functionality is available). * Systematically rename `loaded_as` to `load_as` (`shared_ptr`, `unique_ptr`) as suggested by @laramiel * Make change as suggested by @laramiel. This makes it much more obvious that the latest implementation of `smart_holder_from_unique_ptr()` accepts all existing `return_value_policy` enum values except `copy`. * Resolve `BAKEIN_WIP: Rewrite comment.` for `property_cpp_function_*` specializations. * Resolve `BAKEIN_WIP: Add comment to explain: This is meant for stress-testing only.` * Resolve all remaining BAKEIN_WIP (in pybind11/cast.h). Leave only two pairs of SMART_HOLDER_BAKEIN_FOLLOW_ON comments: refactoring of copyable_holder_caster, move_only_holder_caster. This is best left until after the smart_holder branch is merged into the master branch. * Remove obsolete `using holder_type = smart_holder;` in `load_helper` * Add SMART_HOLDER_BAKEIN_FOLLOW_ON comment for `internals::default_holder` * README_smart_holder.rst update (line count reduced from 356 to 123). --- CMakeLists.txt | 3 +- README_smart_holder.rst | 341 +----- include/pybind11/attr.h | 4 + include/pybind11/cast.h | 281 ++++- include/pybind11/detail/common.h | 6 +- include/pybind11/detail/init.h | 41 +- include/pybind11/detail/internals.h | 42 +- .../detail/smart_holder_sfinae_hooks_only.h | 46 - .../detail/smart_holder_type_casters.h | 1046 ----------------- include/pybind11/detail/type_caster_base.h | 368 ++++++ include/pybind11/detail/using_smart_holder.h | 33 + include/pybind11/pybind11.h | 409 ++++--- include/pybind11/smart_holder.h | 29 +- include/pybind11/stl.h | 3 - .../pybind11/trampoline_self_life_support.h | 14 +- pybind11/_version.py | 2 +- tests/class_sh_module_local_0.cpp | 7 + tests/class_sh_module_local_1.cpp | 7 + tests/class_sh_module_local_2.cpp | 7 + tests/extra_python_package/test_files.py | 3 +- tests/test_class.cpp | 44 +- tests/test_class.py | 12 + tests/test_class_sh_basic.cpp | 9 + tests/test_class_sh_basic.py | 17 +- tests/test_class_sh_disowning.cpp | 7 + tests/test_class_sh_disowning.py | 3 + tests/test_class_sh_disowning_mi.cpp | 7 + tests/test_class_sh_disowning_mi.py | 3 + tests/test_class_sh_factory_constructors.cpp | 7 + tests/test_class_sh_factory_constructors.py | 3 + tests/test_class_sh_inheritance.cpp | 7 + tests/test_class_sh_inheritance.py | 5 + tests/test_class_sh_mi_thunks.cpp | 7 + tests/test_class_sh_mi_thunks.py | 6 +- tests/test_class_sh_module_local.py | 3 + tests/test_class_sh_property.cpp | 7 + tests/test_class_sh_property.py | 5 +- tests/test_class_sh_property_non_owning.cpp | 7 + tests/test_class_sh_property_non_owning.py | 3 + tests/test_class_sh_shared_ptr_copy_move.cpp | 9 +- tests/test_class_sh_shared_ptr_copy_move.py | 5 + tests/test_class_sh_trampoline_basic.cpp | 11 + tests/test_class_sh_trampoline_basic.py | 3 + ..._class_sh_trampoline_self_life_support.cpp | 9 + ...t_class_sh_trampoline_self_life_support.py | 3 + ...t_class_sh_trampoline_shared_from_this.cpp | 9 + ...st_class_sh_trampoline_shared_from_this.py | 5 +- ...class_sh_trampoline_shared_ptr_cpp_arg.cpp | 7 + ..._class_sh_trampoline_shared_ptr_cpp_arg.py | 3 + tests/test_class_sh_trampoline_unique_ptr.cpp | 9 + tests/test_class_sh_trampoline_unique_ptr.py | 5 + ...est_class_sh_unique_ptr_custom_deleter.cpp | 7 + ...test_class_sh_unique_ptr_custom_deleter.py | 5 + tests/test_class_sh_unique_ptr_member.cpp | 7 + tests/test_class_sh_unique_ptr_member.py | 3 + tests/test_class_sh_virtual_py_cpp_mix.cpp | 11 + tests/test_class_sh_virtual_py_cpp_mix.py | 3 + tests/test_classh_mock.cpp | 14 +- tests/test_embed/test_interpreter.cpp | 2 +- tests/test_factory_constructors.cpp | 8 +- tests/test_factory_constructors.py | 9 +- tests/test_methods_and_attributes.cpp | 12 +- tests/test_methods_and_attributes.py | 14 +- tests/test_multiple_inheritance.cpp | 14 +- tests/test_smart_ptr.cpp | 36 - tests/test_smart_ptr.py | 6 +- tests/test_virtual_functions.cpp | 2 +- ubench/holder_comparison.cpp | 17 +- 68 files changed, 1334 insertions(+), 1768 deletions(-) delete mode 100644 include/pybind11/detail/smart_holder_sfinae_hooks_only.h delete mode 100644 include/pybind11/detail/smart_holder_type_casters.h create mode 100644 include/pybind11/detail/using_smart_holder.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b82f45706a..0964e2eef8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,10 +154,9 @@ set(PYBIND11_HEADERS include/pybind11/detail/init.h include/pybind11/detail/internals.h include/pybind11/detail/smart_holder_poc.h - include/pybind11/detail/smart_holder_sfinae_hooks_only.h - include/pybind11/detail/smart_holder_type_casters.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h + include/pybind11/detail/using_smart_holder.h include/pybind11/detail/value_and_holder.h include/pybind11/attr.h include/pybind11/buffer_info.h diff --git a/README_smart_holder.rst b/README_smart_holder.rst index 7943d5c6ca..05b1ab56fa 100644 --- a/README_smart_holder.rst +++ b/README_smart_holder.rst @@ -6,58 +6,71 @@ pybind11 — smart_holder branch Overview ======== -- The smart_holder git branch is a strict superset of the master - branch. Everything that works on master is expected to work exactly the same - with the smart_holder branch. - -- **Smart-pointer interoperability** (``std::unique_ptr``, ``std::shared_ptr``) - is implemented as an **add-on**. - -- The add-on also supports - * passing a Python object back to C++ via ``std::unique_ptr``, safely - **disowning** the Python object. - * safely passing `"trampoline" - `_ - objects (objects with C++ virtual function overrides implemented in - Python) via ``std::unique_ptr`` or ``std::shared_ptr`` back to C++: - associated Python objects are automatically kept alive for the lifetime - of the smart-pointer. - -- The smart_holder branch can be used in two modes: - * **Conservative mode**: ``py::class_`` works exactly as on master. - ``py::classh`` uses ``py::smart_holder``. - * **Progressive mode**: ``py::class_`` uses ``py::smart_holder`` - (i.e. ``py::smart_holder`` is the default holder). +- The smart_holder branch is a strict superset of the pybind11 master branch. + Everything that works with the master branch is expected to work exactly the + same with the smart_holder branch. + +- Activating the smart_holder functionality for a given C++ type ``T`` is as + easy as changing ``py::class_`` to ``py::classh`` in client code. + +- The ``py::classh`` functionality includes + + * support for **two-way** Python/C++ conversions for both + ``std::unique_ptr`` and ``std::shared_ptr`` **simultaneously**. + — In contrast, ``py::class_`` only supports one-way C++-to-Python + conversions for ``std::unique_ptr``, or alternatively two-way + Python/C++ conversions for ``std::shared_ptr``, which then excludes + the one-way C++-to-Python ``std::unique_ptr`` conversions (this + manifests itself through undefined runtime behavior). + + * passing a Python object back to C++ via ``std::unique_ptr``, safely + **disowning** the Python object. + + * safely passing `"trampoline" + `_ + objects (objects with C++ virtual function overrides implemented in + Python) via ``std::unique_ptr`` or ``std::shared_ptr`` back to C++: + associated Python objects are automatically kept alive for the lifetime + of the smart-pointer. + +Note: As of `PR #5257 `_ +the smart_holder functionality is fully baked into pybind11. +Prior to PR #5257 the smart_holder implementation was an "add-on", which made +it necessary to use a ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro. This macro +still exists for backward compatibility, but is now a no-op. The trade-off +for this convenience is that the ``PYBIND11_INTERNALS_VERSION`` needed to be +changed. Consequently, Python extension modules built with the smart_holder +branch no longer interoperate with extension modules built with the pybind11 +master branch. If cross-extension-module interoperability is required, all +extension modules involved must be built with the smart_holder branch. +— Probably, most extension modules do not require cross-extension-module +interoperability, but exceptions to this are quite common. What is fundamentally different? -------------------------------- - Classic pybind11 has the concept of "smart-pointer is holder". - Interoperability between smart-pointers is completely missing. For - example, when using ``std::shared_ptr`` as holder, ``return``-ing - a ``std::unique_ptr`` leads to undefined runtime behavior - (`#1138 `_). A - `systematic analysis is here `_. + Interoperability between smart-pointers is completely missing. For example, + with ``py::class_>``, ``return``-ing a + ``std::unique_ptr`` leads to undefined runtime behavior + (`#1138 `_). + A `systematic analysis can be found here + `_. - ``py::smart_holder`` has a richer concept in comparison, with well-defined - runtime behavior. The holder "knows" about both ``std::unique_ptr`` and - ``std::shared_ptr`` and how they interoperate. - -- Caveat (#HelpAppreciated): currently the ``smart_holder`` branch does - not have a well-lit path for including interoperability with custom - smart-pointers. It is expected to be a fairly obvious extension of the - ``smart_holder`` implementation, but will depend on the exact specifications - of each custom smart-pointer type (generalizations are very likely possible). + runtime behavior in all situations. ``py::smart_holder`` "knows" about both + ``std::unique_ptr`` and ``std::shared_ptr``, and how they interoperate. What motivated the development of the smart_holder code? -------------------------------------------------------- -- Necessity is the mother. The bigger context is the ongoing retooling of - `PyCLIF `_, to use pybind11 underneath - instead of directly targeting the Python C API. Essentially, the smart_holder - branch is porting established PyCLIF functionality into pybind11. +- The original context was retooling of `PyCLIF + `_, to use pybind11 underneath, + instead of directly targeting the Python C API. Essentially the smart_holder + branch is porting established PyCLIF functionality into pybind11. (However, + this work also led to bug fixes in PyCLIF.) Installation @@ -72,225 +85,6 @@ Currently ``git clone`` is the only option. We do not have released packages. Everything else is exactly identical to using the default (master) branch. -Conservative or Progressive mode? -================================= - -It depends. To a first approximation, for a stand-alone, new project, the -Progressive mode will be easiest to use. For larger projects or projects -that integrate with third-party pybind11-based projects, the Conservative -mode may be more practical, at least initially, although it comes with the -disadvantage of having to use the ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro. - - -Conservative mode ------------------ - -Here is a minimal example for wrapping a C++ type with ``py::smart_holder`` as -holder: - -.. code-block:: cpp - - #include - - struct Foo {}; - - PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo) - - PYBIND11_MODULE(example_bindings, m) { - namespace py = pybind11; - py::classh(m, "Foo"); - } - -There are three small differences compared to Classic pybind11: - -- ``#include `` is used instead of - ``#include ``. - -- The ``PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo)`` macro is needed. - — NOTE: This macro needs to be in the global namespace. - -- ``py::classh`` is used instead of ``py::class_``. - -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 ----------------- - -To work in Progressive mode: - -- Add ``-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` to the compilation commands. - -- 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 (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-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: - -.. 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`` 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::smart_holder but with fallback to Classic pybind11 ------------------------------------------------------------- - -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 ---------------------------------------------------------------- - -Currently there are essentially three modes for building a pybind11 extension -module: - -- Classic: pybind11 stable (e.g. v2.6.2) or current master branch. - -- Conservative: pybind11 smart_holder branch. - -- Progressive: pybind11 smart_holder branch with - ``-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT``. - -In environments that mix extension modules built with different modes, -this is the compatibility matrix for ``py::class_``-wrapped types: - -.. list-table:: Compatibility matrix - :widths: auto - :header-rows: 2 - - * - - - - - - - Module 2 - - - * - - - - - Classic - - Conservative - - Progressive - * - - - **Classic** - - full - - one-and-a-half-way - - isolated - * - **Module 1** - - **Conservative** - - one-and-a-half-way - - full - - isolated - * - - - **Progressive** - - isolated - - isolated - - full - -Mixing Classic+Progressive or Conservative+Progressive is very easy to -understand: the extension modules are essentially completely isolated from -each other. This is in fact just the same as using pybind11 versions with -differing `"internals version" -`_ -in the past. While this is easy to understand, there is also no incremental -transition path between Classic and Progressive. - -The Conservative mode enables incremental transitions, but at the cost of -more complexity. Types wrapped in a Classic module are fully compatible with -a Conservative module. However, a type wrapped in a Conservative module is -compatible with a Classic module only if ``py::smart_holder`` is **not** used -(for that type). A type wrapped with ``py::smart_holder`` is incompatible with -a Classic module. This is an important pitfall to keep in mind: attempts to use -``py::smart_holder``-wrapped types in a Classic module will lead to undefined -runtime behavior, such as a SEGFAULT. This is a more general flavor of the -long-standing issue `#1138 `_, -often referred to as "holder mismatch". It is important to note that the -pybind11 smart_holder branch solves the smart-pointer interoperability issue, -but not the more general holder mismatch issue. — Unfortunately the existing -pybind11 internals do not track holder runtime type information, therefore -the holder mismatch issue cannot be solved in a fashion that would allow -an incremental transition, which is the whole point of the Conservative -mode. Please proceed with caution. (See `PR #2644 -`_ for background, which is -labeled with "abi break".) - -Another pitfall worth pointing out specifically, although it follows -from the previous: mixing base and derived classes between Classic and -Conservative modules means that neither the base nor the derived class can -use ``py::smart_holder``. - - Trampolines and std::unique_ptr ------------------------------- @@ -307,37 +101,10 @@ 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. -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 -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 -out and document a smart_holder-based solution for custom smart-pointers -in pybind11 version ``N``, then purge ``type_caster_base`` in version -``N+1``. #HelpAppreciated. - - -Testing of PRs against the smart_holder branch ----------------------------------------------- - -In the pybind11 GitHub Actions, PRs against the smart_holder branch are -automatically tested in both modes (Conservative, Progressive), with the -only difference that ``PYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` is defined -for Progressive mode testing. - -For interactive testing, the ``PYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` -define needs to be manually added to the cmake command. See -.github/workflows/ci_sh.yml for examples. - - Related links ============= @@ -353,4 +120,4 @@ Related links * Small `slide deck `_ presented in meeting with pybind11 maintainers on Feb 22, 2021. Slides 5 - and 6 show performance comparisons. + and 6 show performance comparisons. (These are outdated but probably not far off.) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 1044db94d9..74dc361e3e 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -331,6 +331,10 @@ struct type_record { /// Is the class inheritable from python classes? bool is_final : 1; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + holder_enum_t holder_enum_v = holder_enum_t::undefined; +#endif + PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) { auto *base_info = detail::get_type_info(base, false); if (!base_info) { diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 06dcfc37df..656b7666d3 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -12,7 +12,6 @@ #include "detail/common.h" #include "detail/descr.h" -#include "detail/smart_holder_sfinae_hooks_only.h" #include "detail/type_caster_base.h" #include "detail/typeid.h" #include "pytypes.h" @@ -29,33 +28,17 @@ #include #include -#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -# include "detail/smart_holder_type_casters.h" -#endif - PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -template -class type_caster_for_class_ : public type_caster_base {}; -#endif - template -class type_caster : public type_caster_for_class_ {}; - +class type_caster : public type_caster_base {}; template using make_caster = type_caster>; -template -struct type_uses_smart_holder_type_caster { - static constexpr bool value - = std::is_base_of>::value; -}; - // Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T template typename make_caster::template cast_op_type cast_op(make_caster &caster) { @@ -771,6 +754,7 @@ struct holder_helper { static auto get(const T &p) -> decltype(p.get()) { return p.get(); } }; +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to shared_ptr specialization. /// Type caster for holder types like std::shared_ptr, etc. /// The SFINAE hook is provided to help work around the current lack of support /// for smart-pointer interoperability. Please consider it an implementation @@ -852,12 +836,140 @@ struct copyable_holder_caster : public type_caster_base { holder_type holder; }; -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +template +struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled : std::true_type {}; + +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor copyable_holder_caster to reduce code duplication. +template +struct copyable_holder_caster< + type, + std::shared_ptr, + enable_if_t::value>> + : public type_caster_base { +public: + using base = type_caster_base; + static_assert(std::is_base_of>::value, + "Holder classes are only supported for custom types"); + using base::base; + using base::cast; + using base::typeinfo; + using base::value; + + bool load(handle src, bool convert) { + return base::template load_impl>>( + src, convert); + } + + explicit operator std::shared_ptr *() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + pybind11_fail("Passing `std::shared_ptr *` from Python to C++ is not supported " + "(inherently unsafe)."); + } + return std::addressof(shared_ptr_holder); + } + + explicit operator std::shared_ptr &() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + shared_ptr_holder = sh_load_helper.load_as_shared_ptr(value); + } + return shared_ptr_holder; + } + + static handle + cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { + const auto *ptr = src.get(); + auto st = type_caster_base::src_and_type(ptr); + if (st.second == nullptr) { + return handle(); // no type info: error will be set already + } + if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) { + return smart_holder_type_caster_support::smart_holder_from_shared_ptr( + src, policy, parent, st); + } + return type_caster_base::cast_holder(ptr, &src); + } + + // This function will succeed even if the `responsible_parent` does not own the + // wrapped C++ object directly. + // It is the responsibility of the caller to ensure that the `responsible_parent` + // has a `keep_alive` relationship with the owner of the wrapped C++ object, or + // that the wrapped C++ object lives for the duration of the process. + static std::shared_ptr shared_ptr_with_responsible_parent(handle responsible_parent) { + copyable_holder_caster loader; + loader.load(responsible_parent, /*convert=*/false); + assert(loader.typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder); + return loader.sh_load_helper.load_as_shared_ptr(loader.value, responsible_parent); + } + +protected: + friend class type_caster_generic; + void check_holder_compat() { + if (typeinfo->default_holder) { + throw cast_error("Unable to load a custom holder type from a default-holder instance"); + } + } + + void load_value(value_and_holder &&v_h) { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = v_h; + value = sh_load_helper.get_void_ptr_or_nullptr(); + return; + } + if (v_h.holder_constructed()) { + value = v_h.value_ptr(); + shared_ptr_holder = v_h.template holder>(); + return; + } + throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " +# if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " + "type information)"); +# else + "of type '" + + type_id>() + "''"); +# endif + } + + template , + detail::enable_if_t::value, int> = 0> + bool try_implicit_casts(handle, bool) { + return false; + } + + template , + detail::enable_if_t::value, int> = 0> + bool try_implicit_casts(handle src, bool convert) { + for (auto &cast : typeinfo->implicit_casts) { + copyable_holder_caster sub_caster(*cast.first); + if (sub_caster.load(src, convert)) { + value = cast.second(sub_caster.value); + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; + } else { + shared_ptr_holder + = std::shared_ptr(sub_caster.shared_ptr_holder, (type *) value); + } + return true; + } + } + return false; + } + + static bool try_direct_conversions(handle) { return false; } + + std::shared_ptr shared_ptr_holder; + smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl +}; + +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + /// Specialize for the common std::shared_ptr, so users don't need to template class type_caster> : public copyable_holder_caster> {}; -#endif +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to unique_ptr specialization. /// Type caster for holder types like std::unique_ptr. /// Please consider the SFINAE hook an implementation detail, as explained /// in the comment for the copyable_holder_caster. @@ -873,11 +985,115 @@ struct move_only_holder_caster { static constexpr auto name = type_caster_base::name; }; -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +template +struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled : std::true_type {}; + +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor move_only_holder_caster to reduce code duplication. +template +struct move_only_holder_caster< + type, + std::unique_ptr, + enable_if_t::value>> + : public type_caster_base { +public: + using base = type_caster_base; + static_assert(std::is_base_of>::value, + "Holder classes are only supported for custom types"); + using base::base; + using base::cast; + using base::typeinfo; + using base::value; + + static handle + cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { + auto *ptr = src.get(); + auto st = type_caster_base::src_and_type(ptr); + if (st.second == nullptr) { + return handle(); // no type info: error will be set already + } + if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) { + return smart_holder_type_caster_support::smart_holder_from_unique_ptr( + std::move(src), policy, parent, st); + } + return type_caster_generic::cast(st.first, + return_value_policy::take_ownership, + {}, + st.second, + nullptr, + nullptr, + std::addressof(src)); + } + + static handle + cast(const std::unique_ptr &src, return_value_policy policy, handle parent) { + if (!src) { + return none().release(); + } + if (policy == return_value_policy::automatic) { + policy = return_value_policy::reference_internal; + } + if (policy != return_value_policy::reference_internal) { + throw cast_error("Invalid return_value_policy for unique_ptr&"); + } + return type_caster_base::cast(src.get(), policy, parent); + } + + bool load(handle src, bool convert) { + return base::template load_impl< + move_only_holder_caster>>(src, convert); + } + + void load_value(value_and_holder &&v_h) { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = v_h; + sh_load_helper.loaded_v_h.type = typeinfo; + value = sh_load_helper.get_void_ptr_or_nullptr(); + return; + } + pybind11_fail( + "Passing `std::unique_ptr` from Python to C++ requires `py::classh` (with T = " + + clean_type_id(typeinfo->cpptype->name()) + ")"); + } + + template + using cast_op_type = std::unique_ptr; + + explicit operator std::unique_ptr() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + return sh_load_helper.template load_as_unique_ptr(value); + } + pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); + } + + bool try_implicit_casts(handle src, bool convert) { + for (auto &cast : typeinfo->implicit_casts) { + move_only_holder_caster sub_caster(*cast.first); + if (sub_caster.load(src, convert)) { + value = cast.second(sub_caster.value); + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; + } else { + pybind11_fail("Expected to be UNREACHABLE: " __FILE__ + ":" PYBIND11_TOSTRING(__LINE__)); + } + return true; + } + } + return false; + } + + static bool try_direct_conversions(handle) { return false; } + + smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl +}; + +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + template class type_caster> : public move_only_holder_caster> {}; -#endif template using type_caster_holder = conditional_t::value, @@ -906,10 +1122,16 @@ struct always_construct_holder { template struct is_holder_type : std::is_base_of, detail::type_caster> {}; -// Specialization for always-supported unique_ptr holders: + +// Specializations for always-supported holders: template struct is_holder_type> : std::true_type {}; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT +template +struct is_holder_type : std::true_type {}; +#endif + #ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888 // This leads to compilation errors if a specialization is missing. @@ -1141,7 +1363,6 @@ template using cast_is_temporary_value_reference = bool_constant<(std::is_reference::value || std::is_pointer::value) && !std::is_base_of>::value - && !type_uses_smart_holder_type_caster>::value && !std::is_same, void>::value>; // When a value returned from a C++ function is being cast back to Python, we almost always want to @@ -1155,9 +1376,7 @@ struct return_value_policy_override { template struct return_value_policy_override< Return, - detail::enable_if_t>::value - || type_uses_smart_holder_type_caster>::value, - void>> { + detail::enable_if_t>::value, void>> { static return_value_policy policy(return_value_policy p) { return !std::is_lvalue_reference::value && !std::is_pointer::value ? return_value_policy::move @@ -1857,10 +2076,8 @@ PYBIND11_NAMESPACE_END(detail) template handle type::handle_of() { - static_assert( - detail::any_of>, - detail::type_uses_smart_holder_type_caster>::value, - "py::type::of only supports the case where T is a registered C++ types."); + static_assert(std::is_base_of>::value, + "py::type::of only supports the case where T is a registered C++ types."); return detail::get_type_handle(typeid(T), true); } @@ -1869,7 +2086,7 @@ handle type::handle_of() { PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \ namespace detail { \ template <> \ - class type_caster<__VA_ARGS__> : public type_caster_for_class_<__VA_ARGS__> {}; \ + class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> {}; \ } \ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 71a98f064a..b18dc85012 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -9,13 +9,13 @@ #pragma once -#define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 14 +#define PYBIND11_VERSION_MAJOR 3 +#define PYBIND11_VERSION_MINOR 0 #define PYBIND11_VERSION_PATCH 0.dev1 // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Additional convention: 0xD = dev -#define PYBIND11_VERSION_HEX 0x020E00D1 +#define PYBIND11_VERSION_HEX 0x030000D1 // Define some generic pybind11 helper macros for warning management. // diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 72caf1c1f5..af8ec6dd46 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -10,7 +10,7 @@ #pragma once #include "class.h" -#include "smart_holder_sfinae_hooks_only.h" +#include "using_smart_holder.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -156,9 +156,7 @@ void construct(value_and_holder &v_h, Alias *alias_ptr, bool) { // holder. This also handles types like std::shared_ptr and std::unique_ptr where T is a // derived type (through those holder's implicit conversion from derived class holder // constructors). -template >::value, int> - = 0> +template >::value, int> = 0> void construct(value_and_holder &v_h, Holder holder, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = holder_helper>::get(holder); @@ -200,10 +198,18 @@ void construct(value_and_holder &v_h, Alias &&result, bool) { v_h.value_ptr() = new Alias(std::move(result)); } +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +template +smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr &&unq_ptr, + bool void_cast_raw_ptr) { + void *void_ptr = void_cast_raw_ptr ? static_cast(unq_ptr.get()) : nullptr; + return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr); +} + template >, - detail::enable_if_t>::value, int> - = 0> + detail::enable_if_t>::value, int> = 0> void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = unq_ptr.get(); @@ -217,7 +223,7 @@ void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, // trampoline Python object alive. For types that don't inherit from enable_shared_from_this // it does not matter if void_cast_raw_ptr is true or false, therefore it's not necessary // to also inspect the type. - auto smhldr = type_caster>::smart_holder_from_unique_ptr( + auto smhldr = init_smart_holder_from_unique_ptr( std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias(ptr)); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); @@ -225,22 +231,19 @@ void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, template >, - detail::enable_if_t>::value, int> - = 0> + detail::enable_if_t>::value, int> = 0> void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, bool /*need_alias*/) { auto *ptr = unq_ptr.get(); no_nullptr(ptr); - auto smhldr = type_caster>::smart_holder_from_unique_ptr( - std::move(unq_ptr), /*void_cast_raw_ptr*/ true); + auto smhldr + = init_smart_holder_from_unique_ptr(std::move(unq_ptr), /*void_cast_raw_ptr*/ true); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); } -template >::value, int> - = 0> +template >::value, int> = 0> void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = shd_ptr.get(); @@ -249,24 +252,24 @@ void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, boo throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee " "is not an alias instance"); } - auto smhldr = type_caster>::smart_holder_from_shared_ptr(shd_ptr); + auto smhldr = smart_holder::from_shared_ptr(shd_ptr); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); } -template >::value, int> - = 0> +template >::value, int> = 0> void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, bool /*need_alias*/) { auto *ptr = shd_ptr.get(); no_nullptr(ptr); - auto smhldr = type_caster>::smart_holder_from_shared_ptr(shd_ptr); + auto smhldr = smart_holder::from_shared_ptr(shd_ptr); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); } +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + // Implementing class for py::init<...>() template struct constructor { diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index a72e7c0e7a..d8b5c6b30e 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -16,7 +16,6 @@ #endif #include "../pytypes.h" -#include "smart_holder_sfinae_hooks_only.h" #include #include @@ -37,7 +36,9 @@ /// further ABI-incompatible changes may be made before the ABI is officially /// changed to the new version. #ifndef PYBIND11_INTERNALS_VERSION -# if PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER) +# if PYBIND11_VERSION_MAJOR >= 3 +# define PYBIND11_INTERNALS_VERSION 6 +# elif PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER) // Version bump for Python 3.12+, before first 3.12 beta release. // Version bump for MSVC piggy-backed on PR #4779. See comments there. # define PYBIND11_INTERNALS_VERSION 5 @@ -237,6 +238,20 @@ struct internals { } }; +#if PYBIND11_INTERNALS_VERSION >= 6 + +# define PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +enum class holder_enum_t : uint8_t { + undefined, + std_unique_ptr, // Default, lacking interop with std::shared_ptr. + std_shared_ptr, // Lacking interop with std::unique_ptr. + smart_holder, // Full std::unique_ptr / std::shared_ptr interop. + custom_holder, +}; + +#endif + /// Additional type information which does not fit into the PyTypeObject. /// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`. struct type_info { @@ -260,9 +275,14 @@ struct type_info { /* True if there is no multiple inheritance in this type's inheritance tree */ bool simple_ancestors : 1; /* for base vs derived holder_type checks */ + // SMART_HOLDER_BAKEIN_FOLLOW_ON: Remove default_holder member here and + // produce better error messages in the places where it is currently used. bool default_holder : 1; /* true if this is a type registered with py::module_local */ bool module_local : 1; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + holder_enum_t holder_enum_v = holder_enum_t::undefined; +#endif }; /// On MSVC, debug and release builds are not ABI-compatible! @@ -322,25 +342,15 @@ struct type_info { # define PYBIND11_INTERNALS_KIND "" #endif -/// See README_smart_holder.rst: -/// Classic / Conservative / Progressive cross-module compatibility -#ifndef PYBIND11_INTERNALS_SH_DEF -# if defined(PYBIND11_USE_SMART_HOLDER_AS_DEFAULT) -# define PYBIND11_INTERNALS_SH_DEF "_sh_def" -# else -# define PYBIND11_INTERNALS_SH_DEF "" -# endif -#endif - #define PYBIND11_INTERNALS_ID \ "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ - PYBIND11_BUILD_TYPE PYBIND11_INTERNALS_SH_DEF "__" + PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB \ + PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" #define PYBIND11_MODULE_LOCAL_ID \ "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ - PYBIND11_BUILD_TYPE PYBIND11_INTERNALS_SH_DEF "__" + PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB \ + PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. diff --git a/include/pybind11/detail/smart_holder_sfinae_hooks_only.h b/include/pybind11/detail/smart_holder_sfinae_hooks_only.h deleted file mode 100644 index ca57cb61cb..0000000000 --- a/include/pybind11/detail/smart_holder_sfinae_hooks_only.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2021 The Pybind Development Team. -// All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#pragma once - -#include "common.h" - -#include -#include - -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -// #define PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -// Currently the main purpose of this switch is to enable non-intrusive comprehensive testing. If -// and when `smart_holder` will actually become the released default is currently open. In the -// meantime, the full functionality is easily available by using `py::classh`, which is just a -// handy shortcut for `py::class_` (see `pybind11/smart_holder.h`). Classes -// wrapped in this way are fully compatible with everything existing. -#endif - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) -PYBIND11_NAMESPACE_BEGIN(detail) - -template -struct is_smart_holder_type : std::false_type {}; - -// Tag to be used as base class, inspected by type_uses_smart_holder_type_caster test. -struct smart_holder_type_caster_base_tag {}; - -template -struct type_uses_smart_holder_type_caster; - -// Simple helpers that may eventually be a better fit for another header file: - -template -struct is_std_unique_ptr : std::false_type {}; -template -struct is_std_unique_ptr> : std::true_type {}; - -template -struct is_std_shared_ptr : std::false_type {}; -template -struct is_std_shared_ptr> : std::true_type {}; - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h deleted file mode 100644 index 98eab58910..0000000000 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ /dev/null @@ -1,1046 +0,0 @@ -// Copyright (c) 2021 The Pybind Development Team. -// All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -#pragma once - -#include "../gil.h" -#include "../pytypes.h" -#include "../trampoline_self_life_support.h" -#include "common.h" -#include "descr.h" -#include "dynamic_raw_ptr_cast_if_possible.h" -#include "internals.h" -#include "smart_holder_poc.h" -#include "smart_holder_sfinae_hooks_only.h" -#include "type_caster_base.h" -#include "typeid.h" - -#include -#include -#include -#include -#include -#include -#include - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -using pybindit::memory::smart_holder; - -PYBIND11_NAMESPACE_BEGIN(detail) - -template <> -struct is_smart_holder_type : std::true_type {}; - -// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. -inline void register_instance(instance *self, void *valptr, const type_info *tinfo); -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); - -// The modified_type_caster_generic_load_impl could replace type_caster_generic::load_impl but not -// vice versa. The main difference is that the original code only propagates a reference to the -// held value, while the modified implementation propagates value_and_holder. -class modified_type_caster_generic_load_impl { -public: - PYBIND11_NOINLINE explicit modified_type_caster_generic_load_impl( - const std::type_info &type_info) - : typeinfo(get_type_info(type_info)), cpptype(&type_info) {} - - explicit modified_type_caster_generic_load_impl(const type_info *typeinfo = nullptr) - : typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) {} - - bool load(handle src, bool convert) { - return load_impl(src, convert); - } - - // Base methods for generic caster; there are overridden in copyable_holder_caster - void load_value_and_holder(value_and_holder &&v_h) { - if (!v_h.holder_constructed()) { - // This is needed for old-style __init__. - // type_caster_generic::load_value BEGIN - auto *&vptr = v_h.value_ptr(); - // Lazy allocation for unallocated values: - if (vptr == nullptr) { - // Lazy allocation for unallocated values: - const auto *type = v_h.type ? v_h.type : typeinfo; - if (type->operator_new) { - vptr = type->operator_new(type->type_size); - } else { -#if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912) - if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - vptr = ::operator new(type->type_size, std::align_val_t(type->type_align)); - } else { - vptr = ::operator new(type->type_size); - } -#else - vptr = ::operator new(type->type_size); -#endif - } - } - // type_caster_generic::load_value END - } - loaded_v_h = v_h; - loaded_v_h.type = typeinfo; - } - - bool try_implicit_casts(handle src, bool convert) { - for (const auto &cast : typeinfo->implicit_casts) { - modified_type_caster_generic_load_impl sub_caster(*cast.first); - if (sub_caster.load(src, convert)) { - if (loaded_v_h_cpptype != nullptr) { - pybind11_fail("smart_holder_type_casters: try_implicit_casts failure."); - } - loaded_v_h = sub_caster.loaded_v_h; - loaded_v_h_cpptype = cast.first; - // the sub_caster is being discarded, so steal its vector - implicit_casts = std::move(sub_caster.implicit_casts); - implicit_casts.emplace_back(cast.second); - return true; - } - } - return false; - } - - bool try_direct_conversions(handle src) { - for (auto &converter : *typeinfo->direct_conversions) { - if (converter(src.ptr(), unowned_void_ptr_from_direct_conversion)) { - return true; - } - } - return false; - } - - PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) { - std::unique_ptr loader( - new modified_type_caster_generic_load_impl(ti)); - if (loader->load(src, false)) { - // Trick to work with the existing pybind11 internals. - // The void pointer is immediately captured in a new unique_ptr in - // try_load_foreign_module_local. If this assumption is violated sanitizers - // will most likely flag a leak (verified to be the case with ASAN). - return static_cast(loader.release()); - } - return nullptr; - } - - /// Try to load with foreign typeinfo, if available. Used when there is no - /// native typeinfo, or when the native one wasn't able to produce a value. - PYBIND11_NOINLINE bool try_load_foreign_module_local(handle src) { - constexpr auto *local_key = PYBIND11_MODULE_LOCAL_ID; - const auto pytype = type::handle_of(src); - if (!hasattr(pytype, local_key)) { - return false; - } - - type_info *foreign_typeinfo = reinterpret_borrow(getattr(pytype, local_key)); - // Only consider this foreign loader if actually foreign and is a loader of the correct cpp - // type - if (foreign_typeinfo->module_local_load == &local_load - || (cpptype && !same_type(*cpptype, *foreign_typeinfo->cpptype))) { - return false; - } - - void *foreign_loader_void_ptr - = foreign_typeinfo->module_local_load(src.ptr(), foreign_typeinfo); - if (foreign_loader_void_ptr != nullptr) { - auto foreign_loader = std::unique_ptr( - static_cast(foreign_loader_void_ptr)); - // Magic number intentionally hard-coded for simplicity and maximum robustness. - if (foreign_loader->local_load_safety_guard != 1887406645) { - pybind11_fail("smart_holder_type_casters: Unexpected local_load_safety_guard," - " possibly due to py::class_ holder mixup."); - } - if (loaded_v_h_cpptype != nullptr) { - pybind11_fail("smart_holder_type_casters: try_load_foreign_module_local failure."); - } - loaded_v_h = foreign_loader->loaded_v_h; - loaded_v_h_cpptype = foreign_loader->loaded_v_h_cpptype; - // SMART_HOLDER_WIP: should this be a copy or move? - implicit_casts = foreign_loader->implicit_casts; - return true; - } - return false; - } - - // Implementation of `load`; this takes the type of `this` so that it can dispatch the relevant - // bits of code between here and copyable_holder_caster where the two classes need different - // logic (without having to resort to virtual inheritance). - template - PYBIND11_NOINLINE bool load_impl(handle src, bool convert) { - if (!src) { - return false; - } - if (!typeinfo) { - return try_load_foreign_module_local(src); - } - - auto &this_ = static_cast(*this); - - PyTypeObject *srctype = Py_TYPE(src.ptr()); - - // Case 1: If src is an exact type match for the target type then we can reinterpret_cast - // the instance's value pointer to the target type: - if (srctype == typeinfo->type) { - this_.load_value_and_holder( - reinterpret_cast(src.ptr())->get_value_and_holder()); - return true; - } - // Case 2: We have a derived class - if (PyType_IsSubtype(srctype, typeinfo->type)) { - const auto &bases = all_type_info(srctype); // subtype bases - bool no_cpp_mi = typeinfo->simple_type; - - // Case 2a: the python type is a Python-inherited derived class that inherits from just - // one simple (no MI) pybind11 class, or is an exact match, so the C++ instance is of - // the right type and we can use reinterpret_cast. - // (This is essentially the same as case 2b, but because not using multiple inheritance - // is extremely common, we handle it specially to avoid the loop iterator and type - // pointer lookup overhead) - if (bases.size() == 1 && (no_cpp_mi || bases.front()->type == typeinfo->type)) { - this_.load_value_and_holder( - reinterpret_cast(src.ptr())->get_value_and_holder()); - loaded_v_h_cpptype = bases.front()->cpptype; - reinterpret_cast_deemed_ok = true; - return true; - } - // Case 2b: the python type inherits from multiple C++ bases. Check the bases to see - // if we can find an exact match (or, for a simple C++ type, an inherited match); if - // so, we can safely reinterpret_cast to the relevant pointer. - if (bases.size() > 1) { - for (auto *base : bases) { - if (no_cpp_mi ? PyType_IsSubtype(base->type, typeinfo->type) - : base->type == typeinfo->type) { - this_.load_value_and_holder( - reinterpret_cast(src.ptr())->get_value_and_holder(base)); - loaded_v_h_cpptype = base->cpptype; - reinterpret_cast_deemed_ok = true; - return true; - } - } - } - - // Case 2c: C++ multiple inheritance is involved and we couldn't find an exact type - // match in the registered bases, above, so try implicit casting (needed for proper C++ - // casting when MI is involved). - if (this_.try_implicit_casts(src, convert)) { - return true; - } - } - - // Perform an implicit conversion - if (convert) { - for (const auto &converter : typeinfo->implicit_conversions) { - auto temp = reinterpret_steal(converter(src.ptr(), typeinfo->type)); - if (load_impl(temp, false)) { - loader_life_support::add_patient(temp); - return true; - } - } - if (this_.try_direct_conversions(src)) { - return true; - } - } - - // Failed to match local typeinfo. Try again with global. - if (typeinfo->module_local) { - if (auto *gtype = get_global_type_info(*typeinfo->cpptype)) { - typeinfo = gtype; - return load(src, false); - } - } - - // Global typeinfo has precedence over foreign module_local - if (try_load_foreign_module_local(src)) { - return true; - } - - if (src.is_none()) { - // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) { - return false; - } - loaded_v_h = value_and_holder(); - return true; - } - return false; - } - - const type_info *typeinfo = nullptr; - const std::type_info *cpptype = nullptr; - void *unowned_void_ptr_from_direct_conversion = nullptr; - const std::type_info *loaded_v_h_cpptype = nullptr; - std::vector implicit_casts; - value_and_holder loaded_v_h; - bool reinterpret_cast_deemed_ok = false; - // Magic number intentionally hard-coded, to guard against class_ holder mixups. - // Ideally type_caster_generic would have a similar guard, but this requires a change there. - // SMART_HOLDER_WIP: If it is decided that this guard is useful long term, potentially - // set/reset this value in ctor/dtor, mark volatile. - std::size_t local_load_safety_guard = 1887406645; // 32-bit compatible value for portability. -}; - -struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag { - static decltype(&modified_type_caster_generic_load_impl::local_load) - get_local_load_function_ptr() { - return &modified_type_caster_generic_load_impl::local_load; - } - - using holder_type = pybindit::memory::smart_holder; - - template - static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) { - return false; - } - - // Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy - // ownership semantics: if we detected via shared_from_this that a shared_ptr exists already, - // it is reused, irrespective of the return_value_policy in effect. - // "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a - // direct base of WrappedType. - template - static bool try_initialization_using_shared_from_this( - holder_type *uninitialized_location, - WrappedType *value_ptr_w_t, - const std::enable_shared_from_this *) { - auto shd_ptr = std::dynamic_pointer_cast( - detail::try_get_shared_from_this(value_ptr_w_t)); - if (!shd_ptr) { - return false; - } - // Note: inst->owned ignored. - new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr)); - return true; - } - - template - static void init_instance_for_type(detail::instance *inst, const void *holder_const_void_ptr) { - // Need for const_cast is a consequence of the type_info::init_instance type: - // void (*init_instance)(instance *, const void *); - auto *holder_void_ptr = const_cast(holder_const_void_ptr); - - auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(WrappedType))); - if (!v_h.instance_registered()) { - register_instance(inst, v_h.value_ptr(), v_h.type); - v_h.set_instance_registered(); - } - auto *uninitialized_location = std::addressof(v_h.holder()); - auto *value_ptr_w_t = v_h.value_ptr(); - bool pointee_depends_on_holder_owner - = dynamic_raw_ptr_cast_if_possible(value_ptr_w_t) != nullptr; - if (holder_void_ptr) { - // Note: inst->owned ignored. - auto *holder_ptr = static_cast(holder_void_ptr); - new (uninitialized_location) holder_type(std::move(*holder_ptr)); - } else if (!try_initialization_using_shared_from_this( - uninitialized_location, value_ptr_w_t, value_ptr_w_t)) { - if (inst->owned) { - new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership( - value_ptr_w_t, /*void_cast_raw_ptr*/ pointee_depends_on_holder_owner)); - } else { - new (uninitialized_location) - holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t)); - } - } - v_h.holder().pointee_depends_on_holder_owner - = pointee_depends_on_holder_owner; - v_h.set_holder_constructed(); - } - - template - static smart_holder smart_holder_from_unique_ptr(std::unique_ptr &&unq_ptr, - bool void_cast_raw_ptr) { - void *void_ptr = void_cast_raw_ptr ? static_cast(unq_ptr.get()) : nullptr; - return pybindit::memory::smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr); - } - - template - static smart_holder smart_holder_from_shared_ptr(std::shared_ptr shd_ptr) { - return pybindit::memory::smart_holder::from_shared_ptr(shd_ptr); - } -}; - -struct shared_ptr_parent_life_support { - PyObject *parent; - explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} { - Py_INCREF(parent); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void operator()(void *) { - gil_scoped_acquire gil; - Py_DECREF(parent); - } -}; - -struct shared_ptr_trampoline_self_life_support { - PyObject *self; - explicit shared_ptr_trampoline_self_life_support(instance *inst) - : self{reinterpret_cast(inst)} { - gil_scoped_acquire gil; - Py_INCREF(self); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void operator()(void *) { - gil_scoped_acquire gil; - Py_DECREF(self); - } -}; - -template ::value, int>::type = 0> -inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { - if (deleter == nullptr) { - return std::unique_ptr(raw_ptr); - } - return std::unique_ptr(raw_ptr, std::move(*deleter)); -} - -template ::value, int>::type = 0> -inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { - if (deleter == nullptr) { - pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no" - " instance available to return."); - } - return std::unique_ptr(raw_ptr, std::move(*deleter)); -} - -template -struct smart_holder_type_caster_load { - using holder_type = pybindit::memory::smart_holder; - - bool load(handle src, bool convert) { - static_assert(type_uses_smart_holder_type_caster::value, "Internal consistency error."); - load_impl = modified_type_caster_generic_load_impl(typeid(T)); - if (!load_impl.load(src, convert)) { - return false; - } - return true; - } - - T *loaded_as_raw_ptr_unowned() const { - void *void_ptr = load_impl.unowned_void_ptr_from_direct_conversion; - if (void_ptr == nullptr) { - if (have_holder()) { - throw_if_uninitialized_or_disowned_holder(typeid(T)); - void_ptr = holder().template as_raw_ptr_unowned(); - } else if (load_impl.loaded_v_h.vh != nullptr) { - void_ptr = load_impl.loaded_v_h.value_ptr(); - } - if (void_ptr == nullptr) { - return nullptr; - } - } - return convert_type(void_ptr); - } - - T &loaded_as_lvalue_ref() const { - T *raw_ptr = loaded_as_raw_ptr_unowned(); - if (raw_ptr == nullptr) { - throw reference_cast_error(); - } - return *raw_ptr; - } - - std::shared_ptr make_shared_ptr_with_responsible_parent(handle parent) const { - return std::shared_ptr(loaded_as_raw_ptr_unowned(), - shared_ptr_parent_life_support(parent.ptr())); - } - - std::shared_ptr loaded_as_shared_ptr(handle responsible_parent = nullptr) const { - if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr) { - if (responsible_parent) { - return make_shared_ptr_with_responsible_parent(responsible_parent); - } - throw cast_error("Unowned pointer from direct conversion cannot be converted to a" - " std::shared_ptr."); - } - if (!have_holder()) { - return nullptr; - } - throw_if_uninitialized_or_disowned_holder(typeid(T)); - holder_type &hld = holder(); - hld.ensure_is_not_disowned("loaded_as_shared_ptr"); - if (hld.vptr_is_using_noop_deleter) { - if (responsible_parent) { - return make_shared_ptr_with_responsible_parent(responsible_parent); - } - throw std::runtime_error("Non-owning holder (loaded_as_shared_ptr)."); - } - auto *void_raw_ptr = hld.template as_raw_ptr_unowned(); - auto *type_raw_ptr = convert_type(void_raw_ptr); - if (hld.pointee_depends_on_holder_owner) { - auto *vptr_gd_ptr = std::get_deleter(hld.vptr); - if (vptr_gd_ptr != nullptr) { - std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); - if (released_ptr) { - return std::shared_ptr(released_ptr, type_raw_ptr); - } - std::shared_ptr to_be_released( - type_raw_ptr, - shared_ptr_trampoline_self_life_support(load_impl.loaded_v_h.inst)); - vptr_gd_ptr->released_ptr = to_be_released; - return to_be_released; - } - auto *sptsls_ptr = std::get_deleter(hld.vptr); - if (sptsls_ptr != nullptr) { - // This code is reachable only if there are multiple registered_instances for the - // same pointee. - if (reinterpret_cast(load_impl.loaded_v_h.inst) == sptsls_ptr->self) { - pybind11_fail("smart_holder_type_casters loaded_as_shared_ptr failure: " - "load_impl.loaded_v_h.inst == sptsls_ptr->self"); - } - } - if (sptsls_ptr != nullptr - || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) { - return std::shared_ptr( - type_raw_ptr, - shared_ptr_trampoline_self_life_support(load_impl.loaded_v_h.inst)); - } - if (hld.vptr_is_external_shared_ptr) { - pybind11_fail("smart_holder_type_casters loaded_as_shared_ptr failure: not " - "implemented: trampoline-self-life-support for external shared_ptr " - "to type inheriting from std::enable_shared_from_this."); - } - pybind11_fail("smart_holder_type_casters: loaded_as_shared_ptr failure: internal " - "inconsistency."); - } - std::shared_ptr void_shd_ptr = hld.template as_shared_ptr(); - return std::shared_ptr(void_shd_ptr, type_raw_ptr); - } - - template - std::unique_ptr loaded_as_unique_ptr(const char *context = "loaded_as_unique_ptr") { - if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr) { - throw cast_error("Unowned pointer from direct conversion cannot be converted to a" - " std::unique_ptr."); - } - if (!have_holder()) { - return unique_with_deleter(nullptr, std::unique_ptr()); - } - throw_if_uninitialized_or_disowned_holder(typeid(T)); - throw_if_instance_is_currently_owned_by_shared_ptr(); - holder().ensure_is_not_disowned(context); - holder().template ensure_compatible_rtti_uqp_del(context); - holder().ensure_use_count_1(context); - auto raw_void_ptr = holder().template as_raw_ptr_unowned(); - - void *value_void_ptr = load_impl.loaded_v_h.value_ptr(); - if (value_void_ptr != raw_void_ptr) { - pybind11_fail("smart_holder_type_casters: loaded_as_unique_ptr failure:" - " value_void_ptr != raw_void_ptr"); - } - - // SMART_HOLDER_WIP: MISSING: Safety checks for type conversions - // (T must be polymorphic or meet certain other conditions). - T *raw_type_ptr = convert_type(raw_void_ptr); - - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); - if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) { - throw value_error("Alias class (also known as trampoline) does not inherit from " - "py::trampoline_self_life_support, therefore the ownership of this " - "instance cannot safely be transferred to C++."); - } - - // Temporary variable to store the extracted deleter in. - std::unique_ptr extracted_deleter; - - auto *gd = std::get_deleter(holder().vptr); - if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. - // In smart_holder_poc, a custom deleter is always stored in a guarded delete. - // The guarded delete's std::function actually points at the - // custom_deleter type, so we can verify it is of the custom deleter type and - // finally extract its deleter. - using custom_deleter_D = pybindit::memory::custom_deleter; - const auto &custom_deleter_ptr = gd->del_fun.template target(); - assert(custom_deleter_ptr != nullptr); - // Now that we have confirmed the type of the deleter matches the desired return - // value we can extract the function. - extracted_deleter = std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); - } - - // Critical transfer-of-ownership section. This must stay together. - if (self_life_support != nullptr) { - holder().disown(); - } else { - holder().release_ownership(); - } - auto result = unique_with_deleter(raw_type_ptr, std::move(extracted_deleter)); - if (self_life_support != nullptr) { - self_life_support->activate_life_support(load_impl.loaded_v_h); - } else { - load_impl.loaded_v_h.value_ptr() = nullptr; - deregister_instance( - load_impl.loaded_v_h.inst, value_void_ptr, load_impl.loaded_v_h.type); - } - // Critical section end. - - return result; - } - - // This function will succeed even if the `responsible_parent` does not own the - // wrapped C++ object directly. - // It is the responsibility of the caller to ensure that the `responsible_parent` - // has a `keep_alive` relationship with the owner of the wrapped C++ object, or - // that the wrapped C++ object lives for the duration of the process. - static std::shared_ptr shared_ptr_from_python(handle responsible_parent) { - smart_holder_type_caster_load loader; - loader.load(responsible_parent, false); - return loader.loaded_as_shared_ptr(responsible_parent); - } - -private: - modified_type_caster_generic_load_impl load_impl; - - bool have_holder() const { - return load_impl.loaded_v_h.vh != nullptr && load_impl.loaded_v_h.holder_constructed(); - } - - holder_type &holder() const { return load_impl.loaded_v_h.holder(); } - - // have_holder() must be true or this function will fail. - void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const { - static const std::string missing_value_msg = "Missing value for wrapped C++ type `"; - if (!holder().is_populated) { - throw value_error(missing_value_msg + clean_type_id(typeid_name) - + "`: Python instance is uninitialized."); - } - if (!holder().has_pointee()) { - throw value_error(missing_value_msg + clean_type_id(typeid_name) - + "`: Python instance was disowned."); - } - } - - void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const { - throw_if_uninitialized_or_disowned_holder(type_info.name()); - } - - // have_holder() must be true or this function will fail. - void throw_if_instance_is_currently_owned_by_shared_ptr() const { - auto vptr_gd_ptr = std::get_deleter(holder().vptr); - if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) { - throw value_error("Python instance is currently owned by a std::shared_ptr."); - } - } - - T *convert_type(void *void_ptr) const { - if (void_ptr != nullptr && load_impl.loaded_v_h_cpptype != nullptr - && !load_impl.reinterpret_cast_deemed_ok && !load_impl.implicit_casts.empty()) { - for (auto implicit_cast : load_impl.implicit_casts) { - void_ptr = implicit_cast(void_ptr); - } - } - return static_cast(void_ptr); - } -}; - -// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. -struct make_constructor : private type_caster_base { // Any type, nothing special about int. - using type_caster_base::Constructor; - using type_caster_base::make_copy_constructor; - using type_caster_base::make_move_constructor; -}; - -template -struct smart_holder_type_caster : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - // static handle cast(T, ...) - // is redundant (leads to ambiguous overloads). - - static handle cast(T &&src, return_value_policy /*policy*/, handle parent) { - // type_caster_base BEGIN - return cast(std::addressof(src), return_value_policy::move, parent); - // type_caster_base END - } - - static handle cast(T const &src, return_value_policy policy, handle parent) { - // type_caster_base BEGIN - if (policy == return_value_policy::automatic - || policy == return_value_policy::automatic_reference) { - policy = return_value_policy::copy; - } - return cast(std::addressof(src), policy, parent); - // type_caster_base END - } - - static handle cast(T &src, return_value_policy policy, handle parent) { - return cast(const_cast(src), policy, parent); // Mutbl2Const - } - - static handle cast(T const *src, return_value_policy policy, handle parent) { - auto st = type_caster_base::src_and_type(src); - return cast_const_raw_ptr( // Originally type_caster_generic::cast. - st.first, - policy, - parent, - st.second, - make_constructor::make_copy_constructor(src), - make_constructor::make_move_constructor(src)); - } - - static handle cast(T *src, return_value_policy policy, handle parent) { - return cast(const_cast(src), policy, parent); // Mutbl2Const - } - - template - using cast_op_type = conditional_t< - std::is_same, T const *>::value, - T const *, - conditional_t, T *>::value, - T *, - conditional_t::value, T const &, T &>>>; - - // The const operators here prove that the existing type_caster mechanism already supports - // const-correctness. However, fully implementing const-correctness inside this type_caster - // is still a major project. - // NOLINTNEXTLINE(google-explicit-constructor) - operator T const &() const { - return const_cast(this)->loaded_as_lvalue_ref(); - } - // NOLINTNEXTLINE(google-explicit-constructor) - operator T const *() const { - return const_cast(this)->loaded_as_raw_ptr_unowned(); - } - // NOLINTNEXTLINE(google-explicit-constructor) - operator T &() { return this->loaded_as_lvalue_ref(); } - // NOLINTNEXTLINE(google-explicit-constructor) - operator T *() { return this->loaded_as_raw_ptr_unowned(); } - - // Originally type_caster_generic::cast. - PYBIND11_NOINLINE static handle cast_const_raw_ptr(const void *_src, - return_value_policy policy, - handle parent, - const detail::type_info *tinfo, - void *(*copy_constructor)(const void *), - void *(*move_constructor)(const void *), - const void *existing_holder = nullptr) { - if (!tinfo) { // no type info: error will be set already - return handle(); - } - - void *src = const_cast(_src); - if (src == nullptr) { - return none().release(); - } - - if (handle existing_inst = find_registered_python_instance(src, tinfo)) { - return existing_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *wrapper = reinterpret_cast(inst.ptr()); - wrapper->owned = false; - void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); - - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::take_ownership: - valueptr = src; - wrapper->owned = true; - break; - - case return_value_policy::automatic_reference: - case return_value_policy::reference: - valueptr = src; - wrapper->owned = false; - break; - - case return_value_policy::copy: - if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error("return_value_policy = copy, but type is " - "non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or " - "compile in debug mode for details)"); -#else - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = copy, but type " + type_name - + " is non-copyable!"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::move: - if (move_constructor) { - valueptr = move_constructor(src); - } else if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error("return_value_policy = move, but type is neither " - "movable nor copyable! " - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in " - "debug mode for details)"); -#else - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = move, but type " + type_name - + " is neither movable nor copyable!"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::reference_internal: - valueptr = src; - wrapper->owned = false; - keep_alive_impl(inst, parent); - break; - - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - } - - tinfo->init_instance(wrapper, existing_holder); - - return inst.release(); - } -}; - -template -struct smart_holder_type_caster> : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - break; - case return_value_policy::take_ownership: - throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); - case return_value_policy::copy: - case return_value_policy::move: - break; - case return_value_policy::reference: - throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); - case return_value_policy::reference_internal: - break; - } - if (!src) { - return none().release(); - } - - auto src_raw_ptr = src.get(); - auto st = type_caster_base::src_and_type(src_raw_ptr); - if (st.second == nullptr) { - return handle(); // no type info: error will be set already - } - - void *src_raw_void_ptr = static_cast(src_raw_ptr); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - // SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder. - // SMART_HOLDER_WIP: MISSING: keep_alive. - return existing_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - auto smhldr = pybindit::memory::smart_holder::from_shared_ptr( - std::shared_ptr(src, const_cast(st.first))); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); - } - - template - using cast_op_type = std::shared_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::shared_ptr() { return this->loaded_as_shared_ptr(); } -}; - -template -struct smart_holder_type_caster> : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle - cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { - return smart_holder_type_caster>::cast( - std::const_pointer_cast(src), // Const2Mutbl - policy, - parent); - } - - template - using cast_op_type = std::shared_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::shared_ptr() { return this->loaded_as_shared_ptr(); } // Mutbl2Const -}; - -template -struct smart_holder_type_caster> : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { - if (policy != return_value_policy::automatic - && policy != return_value_policy::automatic_reference - && policy != return_value_policy::reference_internal - && policy != return_value_policy::move) { - // SMART_HOLDER_WIP: IMPROVABLE: Error message. - throw cast_error("Invalid return_value_policy for unique_ptr."); - } - if (!src) { - return none().release(); - } - - auto st = type_caster_base::src_and_type(src.get()); - if (st.second == nullptr) { - return handle(); // no type info: error will be set already - } - - void *src_raw_void_ptr = const_cast(st.first); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(src.get()); - if (self_life_support != nullptr) { - value_and_holder &v_h = self_life_support->v_h; - if (v_h.inst != nullptr && v_h.vh != nullptr) { - auto &holder = v_h.holder(); - if (!holder.is_disowned) { - pybind11_fail("smart_holder_type_casters: unexpected " - "smart_holder.is_disowned failure."); - } - // Critical transfer-of-ownership section. This must stay together. - self_life_support->deactivate_life_support(); - holder.reclaim_disowned(); - (void) src.release(); - // Critical section end. - return existing_inst; - } - } - throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - if (static_cast(src.get()) == src_raw_void_ptr) { - // This is a multiple-inheritance situation that is incompatible with the current - // shared_from_this handling (see PR #3023). - // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? - src_raw_void_ptr = nullptr; - } - auto smhldr - = pybindit::memory::smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); - } - static handle - cast(const std::unique_ptr &src, return_value_policy policy, handle parent) { - if (!src) { - return none().release(); - } - if (policy == return_value_policy::automatic) { - policy = return_value_policy::reference_internal; - } - if (policy != return_value_policy::reference_internal) { - throw cast_error("Invalid return_value_policy for unique_ptr&"); - } - return smart_holder_type_caster::cast(src.get(), policy, parent); - } - - template - using cast_op_type = std::unique_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::unique_ptr() { return this->template loaded_as_unique_ptr(); } -}; - -template -struct smart_holder_type_caster> - : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle - cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { - return smart_holder_type_caster>::cast( - std::unique_ptr(const_cast(src.release()), - std::move(src.get_deleter())), // Const2Mutbl - policy, - parent); - } - - template - using cast_op_type = std::unique_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::unique_ptr() { return this->template loaded_as_unique_ptr(); } -}; - -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT - -# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) \ - namespace pybind11 { \ - namespace detail { \ - template <> \ - class type_caster<__VA_ARGS__> : public smart_holder_type_caster<__VA_ARGS__> {}; \ - template <> \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - template <> \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - template \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - template \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - } \ - } -#else - -# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) - -template -class type_caster_for_class_ : public smart_holder_type_caster {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -#endif - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 481b7c783c..c270687853 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -9,11 +9,15 @@ #pragma once +#include "../gil.h" #include "../pytypes.h" +#include "../trampoline_self_life_support.h" #include "common.h" #include "descr.h" +#include "dynamic_raw_ptr_cast_if_possible.h" #include "internals.h" #include "typeid.h" +#include "using_smart_holder.h" #include "value_and_holder.h" #include @@ -468,6 +472,359 @@ inline PyThreadState *get_thread_state_unchecked() { void keep_alive_impl(handle nurse, handle patient); inline PyObject *make_new_instance(PyTypeObject *type); +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. +inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); + +PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support) + +struct value_and_holder_helper { + value_and_holder loaded_v_h; + + bool have_holder() const { + return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed(); + } + + smart_holder &holder() const { return loaded_v_h.holder(); } + + void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const { + static const std::string missing_value_msg = "Missing value for wrapped C++ type `"; + if (!holder().is_populated) { + throw value_error(missing_value_msg + clean_type_id(typeid_name) + + "`: Python instance is uninitialized."); + } + if (!holder().has_pointee()) { + throw value_error(missing_value_msg + clean_type_id(typeid_name) + + "`: Python instance was disowned."); + } + } + + void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const { + throw_if_uninitialized_or_disowned_holder(type_info.name()); + } + + // have_holder() must be true or this function will fail. + void throw_if_instance_is_currently_owned_by_shared_ptr() const { + auto *vptr_gd_ptr = std::get_deleter(holder().vptr); + if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) { + throw value_error("Python instance is currently owned by a std::shared_ptr."); + } + } + + void *get_void_ptr_or_nullptr() const { + if (have_holder()) { + auto &hld = holder(); + if (hld.is_populated && hld.has_pointee()) { + return hld.template as_raw_ptr_unowned(); + } + } + return nullptr; + } +}; + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + if (policy == return_value_policy::copy) { + throw cast_error("return_value_policy::copy is invalid for unique_ptr."); + } + if (!src) { + return none().release(); + } + void *src_raw_void_ptr = const_cast(st.first); + assert(st.second != nullptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(src.get()); + if (self_life_support != nullptr) { + value_and_holder &v_h = self_life_support->v_h; + if (v_h.inst != nullptr && v_h.vh != nullptr) { + auto &holder = v_h.holder(); + if (!holder.is_disowned) { + pybind11_fail("smart_holder_from_unique_ptr: unexpected " + "smart_holder.is_disowned failure."); + } + // Critical transfer-of-ownership section. This must stay together. + self_life_support->deactivate_life_support(); + holder.reclaim_disowned(); + (void) src.release(); + // Critical section end. + return existing_inst; + } + } + throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + if (static_cast(src.get()) == src_raw_void_ptr) { + // This is a multiple-inheritance situation that is incompatible with the current + // shared_from_this handling (see PR #3023). + // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? + src_raw_void_ptr = nullptr; + } + auto smhldr = smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + return smart_holder_from_unique_ptr( + std::unique_ptr(const_cast(src.release()), + std::move(src.get_deleter())), // Const2Mutbl + policy, + parent, + st); +} + +template +handle smart_holder_from_shared_ptr(const std::shared_ptr &src, + return_value_policy policy, + handle parent, + const std::pair &st) { + switch (policy) { + case return_value_policy::automatic: + case return_value_policy::automatic_reference: + break; + case return_value_policy::take_ownership: + throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); + case return_value_policy::copy: + case return_value_policy::move: + break; + case return_value_policy::reference: + throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); + case return_value_policy::reference_internal: + break; + } + if (!src) { + return none().release(); + } + + auto src_raw_ptr = src.get(); + assert(st.second != nullptr); + void *src_raw_void_ptr = static_cast(src_raw_ptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + // SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder. + // SMART_HOLDER_WIP: MISSING: keep_alive. + return existing_inst; + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + auto smhldr + = smart_holder::from_shared_ptr(std::shared_ptr(src, const_cast(st.first))); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle smart_holder_from_shared_ptr(const std::shared_ptr &src, + return_value_policy policy, + handle parent, + const std::pair &st) { + return smart_holder_from_shared_ptr(std::const_pointer_cast(src), // Const2Mutbl + policy, + parent, + st); +} + +struct shared_ptr_parent_life_support { + PyObject *parent; + explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} { + Py_INCREF(parent); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void operator()(void *) { + gil_scoped_acquire gil; + Py_DECREF(parent); + } +}; + +struct shared_ptr_trampoline_self_life_support { + PyObject *self; + explicit shared_ptr_trampoline_self_life_support(instance *inst) + : self{reinterpret_cast(inst)} { + gil_scoped_acquire gil; + Py_INCREF(self); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void operator()(void *) { + gil_scoped_acquire gil; + Py_DECREF(self); + } +}; + +template ::value, int>::type = 0> +inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { + if (deleter == nullptr) { + return std::unique_ptr(raw_ptr); + } + return std::unique_ptr(raw_ptr, std::move(*deleter)); +} + +template ::value, int>::type = 0> +inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { + if (deleter == nullptr) { + pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no" + " instance available to return."); + } + return std::unique_ptr(raw_ptr, std::move(*deleter)); +} + +template +struct load_helper : value_and_holder_helper { + static std::shared_ptr make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) { + return std::shared_ptr(raw_ptr, shared_ptr_parent_life_support(parent.ptr())); + } + + std::shared_ptr load_as_shared_ptr(void *void_raw_ptr, + handle responsible_parent = nullptr) const { + if (!have_holder()) { + return nullptr; + } + throw_if_uninitialized_or_disowned_holder(typeid(T)); + smart_holder &hld = holder(); + hld.ensure_is_not_disowned("load_as_shared_ptr"); + if (hld.vptr_is_using_noop_deleter) { + if (responsible_parent) { + return make_shared_ptr_with_responsible_parent(static_cast(void_raw_ptr), + responsible_parent); + } + throw std::runtime_error("Non-owning holder (load_as_shared_ptr)."); + } + auto *type_raw_ptr = static_cast(void_raw_ptr); + if (hld.pointee_depends_on_holder_owner) { + auto *vptr_gd_ptr = std::get_deleter(hld.vptr); + if (vptr_gd_ptr != nullptr) { + std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); + if (released_ptr) { + return std::shared_ptr(released_ptr, type_raw_ptr); + } + std::shared_ptr to_be_released( + type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); + vptr_gd_ptr->released_ptr = to_be_released; + return to_be_released; + } + auto *sptsls_ptr = std::get_deleter(hld.vptr); + if (sptsls_ptr != nullptr) { + // This code is reachable only if there are multiple registered_instances for the + // same pointee. + if (reinterpret_cast(loaded_v_h.inst) == sptsls_ptr->self) { + pybind11_fail("smart_holder_type_caster_support load_as_shared_ptr failure: " + "loaded_v_h.inst == sptsls_ptr->self"); + } + } + if (sptsls_ptr != nullptr + || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) { + return std::shared_ptr( + type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); + } + if (hld.vptr_is_external_shared_ptr) { + pybind11_fail("smart_holder_type_casters load_as_shared_ptr failure: not " + "implemented: trampoline-self-life-support for external shared_ptr " + "to type inheriting from std::enable_shared_from_this."); + } + pybind11_fail( + "smart_holder_type_casters: load_as_shared_ptr failure: internal inconsistency."); + } + std::shared_ptr void_shd_ptr = hld.template as_shared_ptr(); + return std::shared_ptr(void_shd_ptr, type_raw_ptr); + } + + template + std::unique_ptr load_as_unique_ptr(void *raw_void_ptr, + const char *context = "load_as_unique_ptr") { + if (!have_holder()) { + return unique_with_deleter(nullptr, std::unique_ptr()); + } + throw_if_uninitialized_or_disowned_holder(typeid(T)); + throw_if_instance_is_currently_owned_by_shared_ptr(); + holder().ensure_is_not_disowned(context); + holder().template ensure_compatible_rtti_uqp_del(context); + holder().ensure_use_count_1(context); + + T *raw_type_ptr = static_cast(raw_void_ptr); + + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); + if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) { + throw value_error("Alias class (also known as trampoline) does not inherit from " + "py::trampoline_self_life_support, therefore the ownership of this " + "instance cannot safely be transferred to C++."); + } + + // Temporary variable to store the extracted deleter in. + std::unique_ptr extracted_deleter; + + auto *gd = std::get_deleter(holder().vptr); + if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. + // In smart_holder_poc, a custom deleter is always stored in a guarded delete. + // The guarded delete's std::function actually points at the + // custom_deleter type, so we can verify it is of the custom deleter type and + // finally extract its deleter. + using custom_deleter_D = pybindit::memory::custom_deleter; + const auto &custom_deleter_ptr = gd->del_fun.template target(); + assert(custom_deleter_ptr != nullptr); + // Now that we have confirmed the type of the deleter matches the desired return + // value we can extract the function. + extracted_deleter = std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); + } + + // Critical transfer-of-ownership section. This must stay together. + if (self_life_support != nullptr) { + holder().disown(); + } else { + holder().release_ownership(); + } + auto result = unique_with_deleter(raw_type_ptr, std::move(extracted_deleter)); + if (self_life_support != nullptr) { + self_life_support->activate_life_support(loaded_v_h); + } else { + void *value_void_ptr = loaded_v_h.value_ptr(); + loaded_v_h.value_ptr() = nullptr; + deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type); + } + // Critical section end. + + return result; + } +}; + +PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) + +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + class type_caster_generic { public: PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info) @@ -572,6 +929,17 @@ class type_caster_generic { // Base methods for generic caster; there are overridden in copyable_holder_caster void load_value(value_and_holder &&v_h) { +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + smart_holder_type_caster_support::value_and_holder_helper v_h_helper; + v_h_helper.loaded_v_h = v_h; + if (v_h_helper.have_holder()) { + v_h_helper.throw_if_uninitialized_or_disowned_holder(cpptype->name()); + value = v_h_helper.holder().template as_raw_ptr_unowned(); + return; + } + } +#endif auto *&vptr = v_h.value_ptr(); // Lazy allocation for unallocated values: if (vptr == nullptr) { diff --git a/include/pybind11/detail/using_smart_holder.h b/include/pybind11/detail/using_smart_holder.h new file mode 100644 index 0000000000..c47b691d45 --- /dev/null +++ b/include/pybind11/detail/using_smart_holder.h @@ -0,0 +1,33 @@ +// Copyright (c) 2024 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +#include "common.h" +#include "internals.h" + +#include + +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT +# include "smart_holder_poc.h" +#endif + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT +using pybindit::memory::smart_holder; +#endif + +PYBIND11_NAMESPACE_BEGIN(detail) + +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT +template +using is_smart_holder = std::is_same; +#else +template +struct is_smart_holder : std::false_type {}; +#endif + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index b7c0e28100..0f2b72cfff 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -11,8 +11,9 @@ #pragma once #include "detail/class.h" +#include "detail/dynamic_raw_ptr_cast_if_possible.h" #include "detail/init.h" -#include "detail/smart_holder_sfinae_hooks_only.h" +#include "detail/using_smart_holder.h" #include "attr.h" #include "gil.h" #include "gil_safe_call_once.h" @@ -1394,8 +1395,7 @@ class generic_type : public object { public: PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check) protected: - void initialize(const type_record &rec, - void *(*type_caster_module_local_load)(PyObject *, const type_info *) ) { + void initialize(const type_record &rec) { if (rec.scope && hasattr(rec.scope, "__dict__") && rec.scope.attr("__dict__").contains(rec.name)) { pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) @@ -1424,6 +1424,9 @@ class generic_type : public object { tinfo->simple_ancestors = true; tinfo->default_holder = rec.default_holder; tinfo->module_local = rec.module_local; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + tinfo->holder_enum_v = rec.holder_enum_v; +#endif with_internals([&](internals &internals) { auto tindex = std::type_index(*rec.type); @@ -1450,7 +1453,7 @@ class generic_type : public object { if (rec.module_local) { // Stash the local typeinfo and loader so that external modules can access it. - tinfo->module_local_load = type_caster_module_local_load; + tinfo->module_local_load = &type_caster_generic::local_load; setattr(m_ptr, PYBIND11_MODULE_LOCAL_ID, capsule(tinfo)); } } @@ -1586,49 +1589,7 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)( return pmf; } -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT - -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 - -template -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, ...) \ - namespace pybind11 { \ - namespace detail { \ - template <> \ - class type_caster : public type_caster_base {}; \ - template <> \ - class type_caster<__VA_ARGS__> : public type_caster_holder {}; \ - } \ - } - -#endif +PYBIND11_NAMESPACE_BEGIN(detail) // Helper for the property_cpp_function static member functions below. // The only purpose of these functions is to support .def_readonly & .def_readwrite. @@ -1637,8 +1598,7 @@ using default_holder_type = smart_holder; // against accidents. As a side-effect, it also explains why the syntactical overhead for // perfect forwarding is not needed. template -using must_be_member_function_pointer - = detail::enable_if_t::value, int>; +using must_be_member_function_pointer = enable_if_t::value, int>; // Note that property_cpp_function is intentionally in the main pybind11 namespace, // because user-defined specializations could be useful. @@ -1647,9 +1607,9 @@ using must_be_member_function_pointer // getter and setter functions. // WARNING: This classic implementation can lead to dangling pointers for raw pointer members. // See test_ptr() in tests/test_class_sh_property.py -// This implementation works as-is (and safely) for smart_holder std::shared_ptr members. -template -struct property_cpp_function { +// However, this implementation works as-is (and safely) for smart_holder std::shared_ptr members. +template +struct property_cpp_function_classic { template = 0> static cpp_function readonly(PM pm, const handle &hdl) { return cpp_function([pm](const T &c) -> const D & { return c.*pm; }, is_method(hdl)); @@ -1666,31 +1626,54 @@ struct property_cpp_function { } }; -// smart_holder specializations for raw pointer members. +PYBIND11_NAMESPACE_END(detail) + +template +struct property_cpp_function : detail::property_cpp_function_classic {}; + +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +struct both_t_and_d_use_type_caster_base : std::false_type {}; + +// `T` is assumed to be equivalent to `intrinsic_t`. +// `D` is may or may not be equivalent to `intrinsic_t`. +template +struct both_t_and_d_use_type_caster_base< + T, + D, + enable_if_t, type_caster>, + std::is_base_of>, make_caster>>::value>> + : std::true_type {}; + +// Specialization for raw pointer members, using smart_holder if that is the class_ holder, +// or falling back to the classic implementation if not. // WARNING: Like the classic implementation, this implementation can lead to dangling pointers. // See test_ptr() in tests/test_class_sh_property.py // However, the read functions return a shared_ptr to the member, emulating the PyCLIF approach: // https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233 // This prevents disowning of the Python object owning the raw pointer member. template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::type_uses_smart_holder_type_caster, - std::is_pointer>::value>> { - +struct property_cpp_function_sh_raw_ptr_member { using drp = typename std::remove_pointer::type; template = 0> static cpp_function readonly(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - D ptr = (*c_sp).*pm; - return std::shared_ptr(c_sp, ptr); - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> std::shared_ptr { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + D ptr = (*c_sp).*pm; + return std::shared_ptr(c_sp, ptr); + }, + is_method(hdl)); + } + return property_cpp_function_classic::readonly(pm, hdl); } template = 0> @@ -1700,81 +1683,95 @@ struct property_cpp_function< template = 0> static cpp_function write(PM pm, const handle &hdl) { - return cpp_function([pm](T &c, D value) { c.*pm = std::forward(value); }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function([pm](T &c, D value) { c.*pm = std::forward(std::move(value)); }, + is_method(hdl)); + } + return property_cpp_function_classic::write(pm, hdl); } }; -// smart_holder specializations for members held by-value. +// Specialization for members held by-value, using smart_holder if that is the class_ holder, +// or falling back to the classic implementation if not. // The read functions return a shared_ptr to the member, emulating the PyCLIF approach: // https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233 // This prevents disowning of the Python object owning the member. template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::type_uses_smart_holder_type_caster, - detail::none_of, - detail::is_std_unique_ptr, - detail::is_std_shared_ptr>>::value>> { - +struct property_cpp_function_sh_member_held_by_value { template = 0> static cpp_function readonly(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr::type> { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - return std::shared_ptr::type>(c_sp, &(c_sp.get()->*pm)); - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> std::shared_ptr::type> { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + return std::shared_ptr::type>(c_sp, + &(c_sp.get()->*pm)); + }, + is_method(hdl)); + } + return property_cpp_function_classic::readonly(pm, hdl); } template = 0> static cpp_function read(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - return std::shared_ptr(c_sp, &(c_sp.get()->*pm)); - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> std::shared_ptr { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + return std::shared_ptr(c_sp, &(c_sp.get()->*pm)); + }, + is_method(hdl)); + } + return property_cpp_function_classic::read(pm, hdl); } template = 0> static cpp_function write(PM pm, const handle &hdl) { - return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl)); + } + return property_cpp_function_classic::write(pm, hdl); } }; -// smart_holder specializations for std::unique_ptr members. +// Specialization for std::unique_ptr members, using smart_holder if that is the class_ holder, +// or falling back to the classic implementation if not. // read disowns the member unique_ptr. // write disowns the passed Python object. // readonly is disabled (static_assert) because there is no safe & intuitive way to make the member // accessible as a Python object without disowning the member unique_ptr. A .def_readonly disowning // the unique_ptr member is deemed highly prone to misunderstandings. template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::is_std_unique_ptr, - detail::type_uses_smart_holder_type_caster>::value>> { - +struct property_cpp_function_sh_unique_ptr_member { template = 0> static cpp_function readonly(PM, const handle &) { - static_assert(!detail::is_std_unique_ptr::value, + static_assert(!is_instantiation::value, "def_readonly cannot be used for std::unique_ptr members."); return cpp_function{}; // Unreachable. } template = 0> static cpp_function read(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> D { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - return D{std::move(c_sp.get()->*pm)}; - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> D { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + return D{std::move(c_sp.get()->*pm)}; + }, + is_method(hdl)); + } + return property_cpp_function_classic::read(pm, hdl); } template = 0> @@ -1783,18 +1780,63 @@ struct property_cpp_function< } }; +PYBIND11_NAMESPACE_END(detail) + +template +struct property_cpp_function< + T, + D, + detail::enable_if_t, + detail::both_t_and_d_use_type_caster_base>::value>> + : detail::property_cpp_function_sh_raw_ptr_member {}; + +template +struct property_cpp_function, + std::is_array, + detail::is_instantiation, + detail::is_instantiation>, + detail::both_t_and_d_use_type_caster_base>::value>> + : detail::property_cpp_function_sh_member_held_by_value {}; + +template +struct property_cpp_function< + T, + D, + detail::enable_if_t, + detail::both_t_and_d_use_type_caster_base>::value>> + : detail::property_cpp_function_sh_unique_ptr_member {}; + +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +#if defined(PYBIND11_USE_SMART_HOLDER_AS_DEFAULT) \ + && defined(PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT) +// NOTE: THIS IS MEANT FOR STRESS-TESTING ONLY! +// As of PR #5257, for production use, there is no longer a strong reason to make +// smart_holder the default holder: +// Simply use `py::classh` (see below) instead of `py::class_` as needed. +// Running the pybind11 unit tests with smart_holder as the default holder is to ensure +// that `py::smart_holder` / `py::classh` is backward-compatible with all pre-existing +// functionality. +# define PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT +template +using default_holder_type = smart_holder; +#else +template +using default_holder_type = std::unique_ptr; +#endif + template class class_ : public detail::generic_type { + template + using is_holder = detail::is_holder_type; template using is_subtype = detail::is_strict_base_of; template using is_base = detail::is_strict_base_of; - template - using is_holder - = detail::any_of, - detail::all_of>, - detail::negation>, - detail::type_uses_smart_holder_type_caster>>; // struct instead of using here to help MSVC: template struct is_valid_class_option : detail::any_of, is_subtype, is_base> {}; @@ -1826,36 +1868,6 @@ class class_ : public detail::generic_type { none_of...>::value), "Error: multiple inheritance bases must be specified via class_ template options"); - static constexpr bool holder_is_smart_holder - = detail::is_smart_holder_type::value; - static constexpr bool wrapped_type_uses_smart_holder_type_caster - = detail::type_uses_smart_holder_type_caster::value; - static constexpr bool type_caster_type_is_type_caster_base_subtype - = std::is_base_of, detail::type_caster>::value; - // Necessary conditions, but not strict. - static_assert(!(detail::is_instantiation::value - && wrapped_type_uses_smart_holder_type_caster), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_TYPE_CASTER_BASE_HOLDER(T, std::unique_ptr)?"); - static_assert(!(detail::is_instantiation::value - && wrapped_type_uses_smart_holder_type_caster), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_TYPE_CASTER_BASE_HOLDER(T, std::shared_ptr)?"); - static_assert(!(holder_is_smart_holder && type_caster_type_is_type_caster_base_subtype), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)?"); -#ifdef PYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX - // Strict conditions cannot be enforced universally at the moment (PR #2836). - static_assert(holder_is_smart_holder == wrapped_type_uses_smart_holder_type_caster, - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)" - " or collision with custom py::detail::type_caster?"); - static_assert(!holder_is_smart_holder == type_caster_type_is_type_caster_base_subtype, - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...)" - " or collision with custom py::detail::type_caster?"); -#endif - type_record record; record.scope = scope; record.name = name; @@ -1869,6 +1881,18 @@ class class_ : public detail::generic_type { // A more fitting name would be uses_unique_ptr_holder. record.default_holder = detail::is_instantiation::value; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + if (detail::is_instantiation::value) { + record.holder_enum_v = detail::holder_enum_t::std_unique_ptr; + } else if (detail::is_instantiation::value) { + record.holder_enum_v = detail::holder_enum_t::std_shared_ptr; + } else if (std::is_same::value) { + record.holder_enum_v = detail::holder_enum_t::smart_holder; + } else { + record.holder_enum_v = detail::holder_enum_t::custom_holder; + } +#endif + set_operator_new(&record); /* Register base classes specified via template arguments to class_, if any */ @@ -1877,7 +1901,7 @@ class class_ : public detail::generic_type { /* Process optional arguments, if any */ process_attributes::init(extra..., &record); - generic_type_initialize(record); + generic_type::initialize(record); if (has_alias) { with_internals([&](internals &internals) { @@ -2138,18 +2162,6 @@ class class_ : public detail::generic_type { } private: - template ::value, int> = 0> - void generic_type_initialize(const detail::type_record &record) { - generic_type::initialize(record, &detail::type_caster_generic::local_load); - } - - template ::value, int> = 0> - void generic_type_initialize(const detail::type_record &record) { - generic_type::initialize(record, detail::type_caster::get_local_load_function_ptr()); - } - /// Initialize holder object, variant 1: object derives from enable_shared_from_this template static void init_holder(detail::instance *inst, @@ -2203,8 +2215,8 @@ class class_ : public detail::generic_type { /// instance. Should be called as soon as the `type` value_ptr is set for an instance. Takes /// an optional pointer to an existing holder to use; if not specified and the instance is /// `.owned`, a new holder will be constructed to manage the value pointer. - template ::value, int> = 0> + template ::value, int> = 0> static void init_instance(detail::instance *inst, const void *holder_ptr) { auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); if (!v_h.instance_registered()) { @@ -2214,13 +2226,70 @@ class class_ : public detail::generic_type { init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr()); } - template ::value, int> = 0> - static void init_instance(detail::instance *inst, const void *holder_ptr) { - detail::type_caster::template init_instance_for_type(inst, holder_ptr); +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + + template + static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) { + return false; + } + + // Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy + // ownership semantics: if we detected via shared_from_this that a shared_ptr exists already, + // it is reused, irrespective of the return_value_policy in effect. + // "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a + // direct base of WrappedType. + template + static bool try_initialization_using_shared_from_this( + holder_type *uninitialized_location, + WrappedType *value_ptr_w_t, + const std::enable_shared_from_this *) { + auto shd_ptr = std::dynamic_pointer_cast( + detail::try_get_shared_from_this(value_ptr_w_t)); + if (!shd_ptr) { + return false; + } + // Note: inst->owned ignored. + new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr)); + return true; + } + + template ::value, int> = 0> + static void init_instance(detail::instance *inst, const void *holder_const_void_ptr) { + // Need for const_cast is a consequence of the type_info::init_instance type: + // void (*init_instance)(instance *, const void *); + auto *holder_void_ptr = const_cast(holder_const_void_ptr); + + auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); + if (!v_h.instance_registered()) { + register_instance(inst, v_h.value_ptr(), v_h.type); + v_h.set_instance_registered(); + } + auto *uninitialized_location = std::addressof(v_h.holder()); + auto *value_ptr_w_t = v_h.value_ptr(); + bool pointee_depends_on_holder_owner + = detail::dynamic_raw_ptr_cast_if_possible(value_ptr_w_t) != nullptr; + if (holder_void_ptr) { + // Note: inst->owned ignored. + auto *holder_ptr = static_cast(holder_void_ptr); + new (uninitialized_location) holder_type(std::move(*holder_ptr)); + } else if (!try_initialization_using_shared_from_this( + uninitialized_location, value_ptr_w_t, value_ptr_w_t)) { + if (inst->owned) { + new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership( + value_ptr_w_t, /*void_cast_raw_ptr*/ pointee_depends_on_holder_owner)); + } else { + new (uninitialized_location) + holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t)); + } + } + v_h.holder().pointee_depends_on_holder_owner + = pointee_depends_on_holder_owner; + v_h.set_holder_constructed(); } +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + /// Deallocates an instance; via holder, if constructed; otherwise via operator delete. static void dealloc(detail::value_and_holder &v_h) { // We could be deallocating because we are cleaning up after a Python exception. @@ -2261,6 +2330,18 @@ class class_ : public detail::generic_type { } }; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +// Supports easier switching between py::class_ and py::class_: +// users can simply replace the `_` in `class_` with `h` or vice versa. +template +class classh : public class_ { +public: + using class_::class_; +}; + +#endif + /// Binds an existing constructor taking arguments Args... template detail::initimpl::constructor init() { diff --git a/include/pybind11/smart_holder.h b/include/pybind11/smart_holder.h index 03ec4bb823..5f568a5529 100644 --- a/include/pybind11/smart_holder.h +++ b/include/pybind11/smart_holder.h @@ -1,29 +1,14 @@ -// Copyright (c) 2021 The Pybind Development Team. +// Copyright (c) 2021-2024 The Pybind Development Team. // All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #pragma once #include "pybind11.h" -#include "detail/common.h" -#include "detail/smart_holder_type_casters.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_: -// users can simply replace the `_` in `class_` with `h` or vice versa. -// Note though that the PYBIND11_SMART_HOLDER_TYPE_CASTERS(T) macro also needs to be -// added (for `classh`) or commented out (when falling back to `class_`). -template -class classh : public class_ { -public: - using class_::class_; -}; - -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +// Legacy macros introduced with smart_holder_type_casters implementation in 2021. +// Deprecated. +#define PYBIND11_TYPE_CASTER_BASE_HOLDER(...) +#define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) +#define PYBIND11_SH_AVL(...) // "Smart_Holder if AVaiLable" +#define PYBIND11_SH_DEF(...) // "Smart_Holder if DEFault" diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 09007cafb2..71bc5902ef 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -392,15 +392,12 @@ struct variant_caster> { template bool load_alternative(handle src, bool convert, type_list) { - PYBIND11_WARNING_PUSH - PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") auto caster = make_caster(); if (caster.load(src, convert)) { value = cast_op(std::move(caster)); return true; } return load_alternative(src, convert, type_list{}); - PYBIND11_WARNING_POP } bool load_alternative(handle, bool, type_list<>) { return false; } diff --git a/include/pybind11/trampoline_self_life_support.h b/include/pybind11/trampoline_self_life_support.h index 47f6239531..0688357269 100644 --- a/include/pybind11/trampoline_self_life_support.h +++ b/include/pybind11/trampoline_self_life_support.h @@ -4,9 +4,13 @@ #pragma once -#include "detail/common.h" -#include "detail/smart_holder_poc.h" -#include "detail/value_and_holder.h" +#include "detail/internals.h" + +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +# include "detail/common.h" +# include "detail/using_smart_holder.h" +# include "detail/value_and_holder.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -40,7 +44,7 @@ struct trampoline_self_life_support { if (value_void_ptr != nullptr) { PyGILState_STATE threadstate = PyGILState_Ensure(); v_h.value_ptr() = nullptr; - v_h.holder().release_disowned(); + v_h.holder().release_disowned(); detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type); Py_DECREF((PyObject *) v_h.inst); // Must be after deregister. PyGILState_Release(threadstate); @@ -59,3 +63,5 @@ struct trampoline_self_life_support { }; PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT diff --git a/pybind11/_version.py b/pybind11/_version.py index c5cc1a0b09..2dfe67616d 100644 --- a/pybind11/_version.py +++ b/pybind11/_version.py @@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str: return s -__version__ = "2.14.0.dev1" +__version__ = "3.0.0.dev1" version_info = tuple(_to_int(s) for s in __version__.split(".")) diff --git a/tests/class_sh_module_local_0.cpp b/tests/class_sh_module_local_0.cpp index bb1f46d5cd..8a0ac0f633 100644 --- a/tests/class_sh_module_local_0.cpp +++ b/tests/class_sh_module_local_0.cpp @@ -19,9 +19,16 @@ atyp rtrn_valu_atyp() { return atyp(); } PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp) PYBIND11_MODULE(class_sh_module_local_0, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace pybind11_tests::class_sh_module_local; m.def("get_mtxt", get_mtxt); m.def("rtrn_valu_atyp", rtrn_valu_atyp); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/class_sh_module_local_1.cpp b/tests/class_sh_module_local_1.cpp index 66bc955163..e5a4584455 100644 --- a/tests/class_sh_module_local_1.cpp +++ b/tests/class_sh_module_local_1.cpp @@ -18,6 +18,12 @@ std::string get_mtxt(const atyp &obj) { return obj.mtxt; } PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp) PYBIND11_MODULE(class_sh_module_local_1, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + namespace py = pybind11; using namespace pybind11_tests::class_sh_module_local; @@ -30,4 +36,5 @@ PYBIND11_MODULE(class_sh_module_local_1, m) { .def("tag", [](const atyp &) { return 1; }); m.def("get_mtxt", get_mtxt); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/class_sh_module_local_2.cpp b/tests/class_sh_module_local_2.cpp index bef105aade..5dd4104a64 100644 --- a/tests/class_sh_module_local_2.cpp +++ b/tests/class_sh_module_local_2.cpp @@ -18,6 +18,12 @@ std::string get_mtxt(const atyp &obj) { return obj.mtxt; } PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp) PYBIND11_MODULE(class_sh_module_local_2, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + namespace py = pybind11; using namespace pybind11_tests::class_sh_module_local; @@ -30,4 +36,5 @@ PYBIND11_MODULE(class_sh_module_local_2, m) { .def("tag", [](const atyp &) { return 2; }); m.def("get_mtxt", get_mtxt); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index a61b428403..5c9705371c 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -60,10 +60,9 @@ "include/pybind11/detail/init.h", "include/pybind11/detail/internals.h", "include/pybind11/detail/smart_holder_poc.h", - "include/pybind11/detail/smart_holder_sfinae_hooks_only.h", - "include/pybind11/detail/smart_holder_type_casters.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", + "include/pybind11/detail/using_smart_holder.h", "include/pybind11/detail/value_and_holder.h", } diff --git a/tests/test_class.cpp b/tests/test_class.cpp index d0ccc12182..73ab609071 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -25,8 +25,6 @@ PYBIND11_WARNING_DISABLE_MSVC(4324) // warning C4324: structure was padded due to alignment specifier -namespace { - // test_brace_initialization struct NoBraceInitialization { explicit NoBraceInitialization(std::vector v) : vec{std::move(v)} {} @@ -36,17 +34,6 @@ struct NoBraceInitialization { std::vector vec; }; -// test_mismatched_holder -struct MismatchBase1 {}; -struct MismatchDerived1 : MismatchBase1 {}; -struct MismatchBase2 {}; -struct MismatchDerived2 : MismatchBase2 {}; - -// test_multiple_instances_with_same_pointer -struct SamePointer {}; - -} // namespace - namespace test_class { namespace pr4220_tripped_over_this { // PR #4227 @@ -67,12 +54,6 @@ void bind_empty0(py::module_ &m) { } // namespace pr4220_tripped_over_this } // namespace test_class -PYBIND11_TYPE_CASTER_BASE_HOLDER(MismatchBase1, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MismatchDerived1, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MismatchBase2, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MismatchDerived2, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(SamePointer, std::unique_ptr) - TEST_SUBMODULE(class_, m) { m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); }); @@ -108,6 +89,16 @@ TEST_SUBMODULE(class_, m) { .def_static("__new__", [](const py::object &) { return NoConstructorNew::new_instance(); }); + // test_pass_unique_ptr + struct ToBeHeldByUniquePtr {}; + py::class_>(m, "ToBeHeldByUniquePtr") + .def(py::init<>()); +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + m.def("pass_unique_ptr", [](std::unique_ptr &&) {}); +#else + m.attr("pass_unique_ptr") = py::none(); +#endif + // test_inheritance class Pet { public: @@ -221,6 +212,12 @@ TEST_SUBMODULE(class_, m) { m.def("as_type", [](const py::object &ob) { return py::type(ob); }); // test_mismatched_holder + struct MismatchBase1 {}; + struct MismatchDerived1 : MismatchBase1 {}; + + struct MismatchBase2 {}; + struct MismatchDerived2 : MismatchBase2 {}; + m.def("mismatched_holder_1", []() { auto mod = py::module_::import("__main__"); py::class_>(mod, "MismatchBase1"); @@ -520,6 +517,7 @@ TEST_SUBMODULE(class_, m) { .def("throw_something", &PyPrintDestructor::throw_something); // test_multiple_instances_with_same_pointer + struct SamePointer {}; static SamePointer samePointer; py::class_>(m, "SamePointer") .def(py::init([]() { return &samePointer; })); @@ -619,18 +617,12 @@ CHECK_NOALIAS(8); static_assert(std::is_same>>::value, \ "DoesntBreak" #N " has wrong holder_type!") -#define CHECK_SMART_HOLDER(N) \ - static_assert(std::is_same::value, \ - "DoesntBreak" #N " has wrong holder_type!") CHECK_HOLDER(1, unique); CHECK_HOLDER(2, unique); CHECK_HOLDER(3, unique); -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT +#ifndef PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT CHECK_HOLDER(4, unique); CHECK_HOLDER(5, unique); -#else -CHECK_SMART_HOLDER(4); -CHECK_SMART_HOLDER(5); #endif CHECK_HOLDER(6, shared); CHECK_HOLDER(7, shared); diff --git a/tests/test_class.py b/tests/test_class.py index 9b2b1d8346..3ecb663f1d 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -41,6 +41,18 @@ def test_instance_new(): assert cstats.alive() == 0 +def test_pass_unique_ptr(): + obj = m.ToBeHeldByUniquePtr() + if m.pass_unique_ptr is None: + pytest.skip("smart_holder not available.") + with pytest.raises(RuntimeError) as execinfo: + m.pass_unique_ptr(obj) + assert str(execinfo.value).startswith( + "Passing `std::unique_ptr` from Python to C++ requires `py::classh` (with T = " + ) + assert "ToBeHeldByUniquePtr" in str(execinfo.value) + + def test_type(): assert m.check_type(1) == m.DerivedClass1 with pytest.raises(RuntimeError) as execinfo: diff --git a/tests/test_class_sh_basic.cpp b/tests/test_class_sh_basic.cpp index fb9395180e..460dd1bd0d 100644 --- a/tests/test_class_sh_basic.cpp +++ b/tests/test_class_sh_basic.cpp @@ -145,6 +145,12 @@ namespace pybind11_tests { namespace class_sh_basic { TEST_SUBMODULE(class_sh_basic, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + namespace py = pybind11; py::classh(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) { @@ -233,11 +239,14 @@ TEST_SUBMODULE(class_sh_basic, m) { []() { return std::unique_ptr(new atyp("rtrn_uq_automatic_reference")); }, pybind11::return_value_policy::automatic_reference); + m.def("pass_shared_ptr_ptr", [](std::shared_ptr *) {}); + py::classh(m, "LocalUnusualOpRef"); m.def("CallCastUnusualOpRefConstRef", []() { return CastUnusualOpRefConstRef(LocalUnusualOpRef()); }); m.def("CallCastUnusualOpRefMovable", []() { return CastUnusualOpRefMovable(LocalUnusualOpRef()); }); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } } // namespace class_sh_basic diff --git a/tests/test_class_sh_basic.py b/tests/test_class_sh_basic.py index 56d45d1854..b3d9b98c6b 100644 --- a/tests/test_class_sh_basic.py +++ b/tests/test_class_sh_basic.py @@ -7,6 +7,9 @@ from pybind11_tests import class_sh_basic as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_atyp_constructors(): obj = m.atyp() @@ -132,9 +135,7 @@ def test_cannot_disown_use_count_ne_1(pass_f, rtrn_f): stash.Add(obj) with pytest.raises(ValueError) as exc_info: pass_f(obj) - assert str(exc_info.value) == ( - "Cannot disown use_count != 1 (loaded_as_unique_ptr)." - ) + assert str(exc_info.value) == ("Cannot disown use_count != 1 (load_as_unique_ptr).") def test_unique_ptr_roundtrip(num_round_trips=1000): @@ -220,6 +221,16 @@ def test_unique_ptr_return_value_policy_automatic_reference(): assert m.get_mtxt(m.rtrn_uq_automatic_reference()) == "rtrn_uq_automatic_reference" +def test_pass_shared_ptr_ptr(): + obj = m.atyp() + with pytest.raises(RuntimeError) as excinfo: + m.pass_shared_ptr_ptr(obj) + assert str(excinfo.value) == ( + "Passing `std::shared_ptr *` from Python to C++ is not supported" + " (inherently unsafe)." + ) + + def test_unusual_op_ref(): # Merely to test that this still exists and built successfully. assert m.CallCastUnusualOpRefConstRef().__class__.__name__ == "LocalUnusualOpRef" diff --git a/tests/test_class_sh_disowning.cpp b/tests/test_class_sh_disowning.cpp index f934852f61..aba3dc8197 100644 --- a/tests/test_class_sh_disowning.cpp +++ b/tests/test_class_sh_disowning.cpp @@ -32,6 +32,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<1>) PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<2>) TEST_SUBMODULE(class_sh_disowning, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace pybind11_tests::class_sh_disowning; py::classh>(m, "Atype1").def(py::init()).def("get", &Atype<1>::get); @@ -43,4 +49,5 @@ TEST_SUBMODULE(class_sh_disowning, m) { m.def("overloaded", (int (*)(std::unique_ptr>, int)) & overloaded); m.def("overloaded", (int (*)(std::unique_ptr>, int)) & overloaded); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_disowning.py b/tests/test_class_sh_disowning.py index 52568203e9..36e461012e 100644 --- a/tests/test_class_sh_disowning.py +++ b/tests/test_class_sh_disowning.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_disowning as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def is_disowned(obj): try: diff --git a/tests/test_class_sh_disowning_mi.cpp b/tests/test_class_sh_disowning_mi.cpp index 86333e8641..1bba401545 100644 --- a/tests/test_class_sh_disowning_mi.cpp +++ b/tests/test_class_sh_disowning_mi.cpp @@ -57,6 +57,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::Base1) PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::Base2) TEST_SUBMODULE(class_sh_disowning_mi, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace pybind11_tests::class_sh_disowning_mi; py::classh(m, "B") @@ -92,4 +98,5 @@ TEST_SUBMODULE(class_sh_disowning_mi, m) { py::classh(m, "Base2").def(py::init()).def("bar", &Base2::bar); m.def("disown_base1", disown_base1); m.def("disown_base2", disown_base2); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_disowning_mi.py b/tests/test_class_sh_disowning_mi.py index 4a4beecce1..781db8c0ea 100644 --- a/tests/test_class_sh_disowning_mi.py +++ b/tests/test_class_sh_disowning_mi.py @@ -5,6 +5,9 @@ import env # noqa: F401 from pybind11_tests import class_sh_disowning_mi as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_diamond_inheritance(): # Very similar to test_multiple_inheritance.py:test_diamond_inheritance. diff --git a/tests/test_class_sh_factory_constructors.cpp b/tests/test_class_sh_factory_constructors.cpp index 937672cbd2..7ee56a8b48 100644 --- a/tests/test_class_sh_factory_constructors.cpp +++ b/tests/test_class_sh_factory_constructors.cpp @@ -87,6 +87,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::with_alias) TEST_SUBMODULE(class_sh_factory_constructors, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace pybind11_tests::class_sh_factory_constructors; py::classh(m, "atyp_valu") @@ -177,4 +183,5 @@ TEST_SUBMODULE(class_sh_factory_constructors, m) { [](int, int, int, int, int) { return std::make_shared(); // Invalid alias factory. })); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_factory_constructors.py b/tests/test_class_sh_factory_constructors.py index 5d45db6fd5..38e529e556 100644 --- a/tests/test_class_sh_factory_constructors.py +++ b/tests/test_class_sh_factory_constructors.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_factory_constructors as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_atyp_factories(): assert m.atyp_valu().get_mtxt() == "Valu" diff --git a/tests/test_class_sh_inheritance.cpp b/tests/test_class_sh_inheritance.cpp index d8f7da0e28..af57f03bdb 100644 --- a/tests/test_class_sh_inheritance.cpp +++ b/tests/test_class_sh_inheritance.cpp @@ -73,6 +73,12 @@ namespace pybind11_tests { namespace class_sh_inheritance { TEST_SUBMODULE(class_sh_inheritance, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "base"); py::classh(m, "drvd"); @@ -99,6 +105,7 @@ TEST_SUBMODULE(class_sh_inheritance, m) { m.def("pass_cptr_base1", pass_cptr_base1); m.def("pass_cptr_base2", pass_cptr_base2); m.def("pass_cptr_drvd2", pass_cptr_drvd2); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } } // namespace class_sh_inheritance diff --git a/tests/test_class_sh_inheritance.py b/tests/test_class_sh_inheritance.py index cd9d6f47e2..f03cee36b2 100644 --- a/tests/test_class_sh_inheritance.py +++ b/tests/test_class_sh_inheritance.py @@ -1,7 +1,12 @@ from __future__ import annotations +import pytest + from pybind11_tests import class_sh_inheritance as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_rtrn_mptr_drvd_pass_cptr_base(): d = m.rtrn_mptr_drvd() diff --git a/tests/test_class_sh_mi_thunks.cpp b/tests/test_class_sh_mi_thunks.cpp index c4f430e864..0990c34fc2 100644 --- a/tests/test_class_sh_mi_thunks.cpp +++ b/tests/test_class_sh_mi_thunks.cpp @@ -40,6 +40,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Base1) PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Derived) TEST_SUBMODULE(class_sh_mi_thunks, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace test_class_sh_mi_thunks; m.def("ptrdiff_drvd_base0", []() { @@ -97,4 +103,5 @@ TEST_SUBMODULE(class_sh_mi_thunks, m) { } return obj_der->vec.size(); }); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_mi_thunks.py b/tests/test_class_sh_mi_thunks.py index c1c8a30431..12b6e9d965 100644 --- a/tests/test_class_sh_mi_thunks.py +++ b/tests/test_class_sh_mi_thunks.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_mi_thunks as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_ptrdiff_drvd_base0(): ptrdiff = m.ptrdiff_drvd_base0() @@ -49,6 +52,5 @@ def test_get_shared_vec_size_unique(): with pytest.raises(ValueError) as exc_info: m.vec_size_base0_unique_ptr(obj) assert ( - str(exc_info.value) - == "Cannot disown external shared_ptr (loaded_as_unique_ptr)." + str(exc_info.value) == "Cannot disown external shared_ptr (load_as_unique_ptr)." ) diff --git a/tests/test_class_sh_module_local.py b/tests/test_class_sh_module_local.py index e504152a16..79ccb8b421 100644 --- a/tests/test_class_sh_module_local.py +++ b/tests/test_class_sh_module_local.py @@ -5,6 +5,9 @@ import class_sh_module_local_2 as m2 import pytest +if not m0.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_cross_module_get_mtxt(): obj1 = m1.atyp("A") diff --git a/tests/test_class_sh_property.cpp b/tests/test_class_sh_property.cpp index 1f6922634c..db744fae4b 100644 --- a/tests/test_class_sh_property.cpp +++ b/tests/test_class_sh_property.cpp @@ -58,6 +58,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::WithCharArrayMember) PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::WithConstCharPtrMember) TEST_SUBMODULE(class_sh_property, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace test_class_sh_property; py::class_>(m, "ClassicField") @@ -103,4 +109,5 @@ TEST_SUBMODULE(class_sh_property, m) { py::classh(m, "WithConstCharPtrMember") .def(py::init<>()) .def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_property.py b/tests/test_class_sh_property.py index 089cf7caa0..dda786a216 100644 --- a/tests/test_class_sh_property.py +++ b/tests/test_class_sh_property.py @@ -7,6 +7,9 @@ import env # noqa: F401 from pybind11_tests import class_sh_property as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + @pytest.mark.xfail("env.PYPY", reason="gc after `del field` is apparently deferred") @pytest.mark.parametrize("m_attr", ["m_valu_readonly", "m_valu_readwrite"]) @@ -18,7 +21,7 @@ def test_valu_getter(m_attr): assert field.num == -99 with pytest.raises(ValueError) as excinfo: m.DisownOuter(outer) - assert str(excinfo.value) == "Cannot disown use_count != 1 (loaded_as_unique_ptr)." + assert str(excinfo.value) == "Cannot disown use_count != 1 (load_as_unique_ptr)." del field m.DisownOuter(outer) with pytest.raises(ValueError, match="Python instance was disowned") as excinfo: diff --git a/tests/test_class_sh_property_non_owning.cpp b/tests/test_class_sh_property_non_owning.cpp index cd7fc5c609..65103148fa 100644 --- a/tests/test_class_sh_property_non_owning.cpp +++ b/tests/test_class_sh_property_non_owning.cpp @@ -51,6 +51,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataField) PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataFieldsHolder) TEST_SUBMODULE(class_sh_property_non_owning, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "CoreField").def_readwrite("int_value", &CoreField::int_value); py::classh(m, "DataField") @@ -65,4 +71,5 @@ TEST_SUBMODULE(class_sh_property_non_owning, m) { py::classh(m, "DataFieldsHolder") .def(py::init()) .def("vec_at", &DataFieldsHolder::vec_at, py::return_value_policy::reference_internal); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_property_non_owning.py b/tests/test_class_sh_property_non_owning.py index 33a9d4503b..89c7c0cd56 100644 --- a/tests/test_class_sh_property_non_owning.py +++ b/tests/test_class_sh_property_non_owning.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_property_non_owning as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + @pytest.mark.parametrize("persistent_holder", [True, False]) @pytest.mark.parametrize( diff --git a/tests/test_class_sh_shared_ptr_copy_move.cpp b/tests/test_class_sh_shared_ptr_copy_move.cpp index ae1afd6c19..3e9eb9ac98 100644 --- a/tests/test_class_sh_shared_ptr_copy_move.cpp +++ b/tests/test_class_sh_shared_ptr_copy_move.cpp @@ -50,7 +50,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::FooSmHld) namespace pybind11_tests { TEST_SUBMODULE(class_sh_shared_ptr_copy_move, m) { -#if true // Trick to avoid clang-format changes, in support of PR #5213. + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + namespace py = pybind11; py::class_>(m, "FooShPtr") @@ -108,7 +113,7 @@ TEST_SUBMODULE(class_sh_shared_ptr_copy_move, m) { l.append(std::move(o)); return l; }); -#endif +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } } // namespace pybind11_tests diff --git a/tests/test_class_sh_shared_ptr_copy_move.py b/tests/test_class_sh_shared_ptr_copy_move.py index 067bb47d2a..092aa1f3f5 100644 --- a/tests/test_class_sh_shared_ptr_copy_move.py +++ b/tests/test_class_sh_shared_ptr_copy_move.py @@ -1,7 +1,12 @@ from __future__ import annotations +import pytest + from pybind11_tests import class_sh_shared_ptr_copy_move as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_shptr_copy(): txt = m.test_ShPtr_copy()[0].get_history() diff --git a/tests/test_class_sh_trampoline_basic.cpp b/tests/test_class_sh_trampoline_basic.cpp index 128701d75d..faadb4024f 100644 --- a/tests/test_class_sh_trampoline_basic.cpp +++ b/tests/test_class_sh_trampoline_basic.cpp @@ -34,6 +34,7 @@ struct AbaseAlias : Abase { } }; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT template <> struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support { using Abase<1>::Abase; @@ -45,6 +46,7 @@ struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support { other_val); } }; +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT template int AddInCppRawPtr(const Abase *obj, int other_val) { @@ -63,6 +65,7 @@ int AddInCppUniquePtr(std::unique_ptr> obj, int other_val) { template void wrap(py::module_ m, const char *py_class_name) { +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT py::classh, AbaseAlias>(m, py_class_name) .def(py::init(), py::arg("val")) .def("Get", &Abase::Get) @@ -71,6 +74,7 @@ void wrap(py::module_ m, const char *py_class_name) { m.def("AddInCppRawPtr", AddInCppRawPtr, py::arg("obj"), py::arg("other_val")); m.def("AddInCppSharedPtr", AddInCppSharedPtr, py::arg("obj"), py::arg("other_val")); m.def("AddInCppUniquePtr", AddInCppUniquePtr, py::arg("obj"), py::arg("other_val")); +#endif } } // namespace class_sh_trampoline_basic @@ -82,6 +86,13 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(Abase<0>) PYBIND11_SMART_HOLDER_TYPE_CASTERS(Abase<1>) TEST_SUBMODULE(class_sh_trampoline_basic, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + wrap<0>(m, "Abase0"); wrap<1>(m, "Abase1"); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_trampoline_basic.py b/tests/test_class_sh_trampoline_basic.py index eab82121fb..7b6e1a0fc4 100644 --- a/tests/test_class_sh_trampoline_basic.py +++ b/tests/test_class_sh_trampoline_basic.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_trampoline_basic as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + class PyDrvd0(m.Abase0): def __init__(self, val): diff --git a/tests/test_class_sh_trampoline_self_life_support.cpp b/tests/test_class_sh_trampoline_self_life_support.cpp index b1b8969a65..859b9f8fbc 100644 --- a/tests/test_class_sh_trampoline_self_life_support.cpp +++ b/tests/test_class_sh_trampoline_self_life_support.cpp @@ -38,9 +38,11 @@ struct Big5 { // Also known as "rule of five". Big5() : history{"DefaultConstructor"} {} }; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT struct Big5Trampoline : Big5, py::trampoline_self_life_support { using Big5::Big5; }; +#endif } // namespace class_sh_trampoline_self_life_support } // namespace pybind11_tests @@ -50,6 +52,12 @@ using namespace pybind11_tests::class_sh_trampoline_self_life_support; PYBIND11_SMART_HOLDER_TYPE_CASTERS(Big5) TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "Big5") .def(py::init()) .def_readonly("history", &Big5::history); @@ -86,4 +94,5 @@ TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) { py::object o1 = py::cast(std::move(obj)); return py::make_tuple(o1, o2); }); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_trampoline_self_life_support.py b/tests/test_class_sh_trampoline_self_life_support.py index d4af2ab99a..d366b48c63 100644 --- a/tests/test_class_sh_trampoline_self_life_support.py +++ b/tests/test_class_sh_trampoline_self_life_support.py @@ -4,6 +4,9 @@ import pybind11_tests.class_sh_trampoline_self_life_support as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + class PyBig5(m.Big5): pass diff --git a/tests/test_class_sh_trampoline_shared_from_this.cpp b/tests/test_class_sh_trampoline_shared_from_this.cpp index ebbe94e900..aaf5a87506 100644 --- a/tests/test_class_sh_trampoline_shared_from_this.cpp +++ b/tests/test_class_sh_trampoline_shared_from_this.cpp @@ -71,9 +71,11 @@ struct SftSharedPtrStash { } }; +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT struct SftTrampoline : Sft, py::trampoline_self_life_support { using Sft::Sft; }; +#endif long use_count(const std::shared_ptr &obj) { return obj.use_count(); } @@ -108,6 +110,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(Sft) PYBIND11_SMART_HOLDER_TYPE_CASTERS(SftSharedPtrStash) TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "Sft") .def(py::init()) .def(py::init([](const std::string &history, int) { @@ -132,4 +140,5 @@ TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) { m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr); m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr); m.def("pass_through_shd_ptr", pass_through_shd_ptr); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_trampoline_shared_from_this.py b/tests/test_class_sh_trampoline_shared_from_this.py index 85fe7858f5..7555842146 100644 --- a/tests/test_class_sh_trampoline_shared_from_this.py +++ b/tests/test_class_sh_trampoline_shared_from_this.py @@ -8,6 +8,9 @@ import env import pybind11_tests.class_sh_trampoline_shared_from_this as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + class PySft(m.Sft): pass @@ -238,7 +241,7 @@ def __init__(self, history): str_exc_info_value = str(exc_info.value) assert ( str_exc_info_value - == "smart_holder_type_casters loaded_as_shared_ptr failure: not implemented:" + == "smart_holder_type_casters load_as_shared_ptr failure: not implemented:" " trampoline-self-life-support for external shared_ptr to type inheriting" " from std::enable_shared_from_this." ) diff --git a/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp index f4de884e3a..2b94310b59 100644 --- a/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp +++ b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp @@ -63,6 +63,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAway) PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAwayTester) TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + // For testing whether a python subclass of a C++ object dies when the // last python reference is lost @@ -95,4 +101,5 @@ TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) { py::classh(m, "SpGoAwayTester") .def(py::init<>()) .def_readwrite("obj", &SpGoAwayTester::m_obj); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py index 431edb7191..54575ddccc 100644 --- a/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py +++ b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py @@ -4,6 +4,9 @@ import pybind11_tests.class_sh_trampoline_shared_ptr_cpp_arg as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_shared_ptr_cpp_arg(): import weakref diff --git a/tests/test_class_sh_trampoline_unique_ptr.cpp b/tests/test_class_sh_trampoline_unique_ptr.cpp index c4b3424555..13dc27b049 100644 --- a/tests/test_class_sh_trampoline_unique_ptr.cpp +++ b/tests/test_class_sh_trampoline_unique_ptr.cpp @@ -39,6 +39,7 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_trampoline_unique_pt namespace pybind11_tests { namespace class_sh_trampoline_unique_ptr { +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT class PyClass : public Class, public py::trampoline_self_life_support { public: std::unique_ptr clone() const override { @@ -47,11 +48,18 @@ class PyClass : public Class, public py::trampoline_self_life_support { int foo() const override { PYBIND11_OVERRIDE_PURE(int, Class, foo); } }; +#endif } // namespace class_sh_trampoline_unique_ptr } // namespace pybind11_tests TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + using namespace pybind11_tests::class_sh_trampoline_unique_ptr; py::classh(m, "Class") @@ -63,4 +71,5 @@ TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) { m.def("clone", [](const Class &obj) { return obj.clone(); }); m.def("clone_and_foo", [](const Class &obj) { return obj.clone()->foo(); }); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_trampoline_unique_ptr.py b/tests/test_class_sh_trampoline_unique_ptr.py index 7799df6d61..22505dc6ea 100644 --- a/tests/test_class_sh_trampoline_unique_ptr.py +++ b/tests/test_class_sh_trampoline_unique_ptr.py @@ -1,7 +1,12 @@ from __future__ import annotations +import pytest + import pybind11_tests.class_sh_trampoline_unique_ptr as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + class MyClass(m.Class): def foo(self): diff --git a/tests/test_class_sh_unique_ptr_custom_deleter.cpp b/tests/test_class_sh_unique_ptr_custom_deleter.cpp index 070a10e0fb..973bf49087 100644 --- a/tests/test_class_sh_unique_ptr_custom_deleter.cpp +++ b/tests/test_class_sh_unique_ptr_custom_deleter.cpp @@ -31,9 +31,16 @@ namespace pybind11_tests { namespace class_sh_unique_ptr_custom_deleter { TEST_SUBMODULE(class_sh_unique_ptr_custom_deleter, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "Pet").def_readwrite("name", &Pet::name); m.def("create", &Pet::New); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } } // namespace class_sh_unique_ptr_custom_deleter diff --git a/tests/test_class_sh_unique_ptr_custom_deleter.py b/tests/test_class_sh_unique_ptr_custom_deleter.py index 34aa520682..f246e2d7e5 100644 --- a/tests/test_class_sh_unique_ptr_custom_deleter.py +++ b/tests/test_class_sh_unique_ptr_custom_deleter.py @@ -1,7 +1,12 @@ from __future__ import annotations +import pytest + from pybind11_tests import class_sh_unique_ptr_custom_deleter as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_create(): pet = m.create("abc") diff --git a/tests/test_class_sh_unique_ptr_member.cpp b/tests/test_class_sh_unique_ptr_member.cpp index 3de12fe627..1341f140e5 100644 --- a/tests/test_class_sh_unique_ptr_member.cpp +++ b/tests/test_class_sh_unique_ptr_member.cpp @@ -45,6 +45,12 @@ namespace pybind11_tests { namespace class_sh_unique_ptr_member { TEST_SUBMODULE(class_sh_unique_ptr_member, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "pointee").def(py::init<>()).def("get_int", &pointee::get_int); m.def("make_unique_pointee", make_unique_pointee); @@ -54,6 +60,7 @@ TEST_SUBMODULE(class_sh_unique_ptr_member, m) { .def("is_owner", &ptr_owner::is_owner) .def("give_up_ownership_via_unique_ptr", &ptr_owner::give_up_ownership_via_unique_ptr) .def("give_up_ownership_via_shared_ptr", &ptr_owner::give_up_ownership_via_shared_ptr); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } } // namespace class_sh_unique_ptr_member diff --git a/tests/test_class_sh_unique_ptr_member.py b/tests/test_class_sh_unique_ptr_member.py index a5d2ccd234..dc1d5482fd 100644 --- a/tests/test_class_sh_unique_ptr_member.py +++ b/tests/test_class_sh_unique_ptr_member.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_unique_ptr_member as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + def test_make_unique_pointee(): obj = m.make_unique_pointee() diff --git a/tests/test_class_sh_virtual_py_cpp_mix.cpp b/tests/test_class_sh_virtual_py_cpp_mix.cpp index 467d55aa6f..a68cb76aa3 100644 --- a/tests/test_class_sh_virtual_py_cpp_mix.cpp +++ b/tests/test_class_sh_virtual_py_cpp_mix.cpp @@ -31,6 +31,8 @@ int get_from_cpp_plainc_ptr(const Base *b) { return b->get() + 4000; } int get_from_cpp_unique_ptr(std::unique_ptr b) { return b->get() + 5000; } +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + struct BaseVirtualOverrider : Base, py::trampoline_self_life_support { using Base::Base; @@ -43,6 +45,8 @@ struct CppDerivedVirtualOverrider : CppDerived, py::trampoline_self_life_support int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); } }; +#endif + } // namespace class_sh_virtual_py_cpp_mix } // namespace pybind11_tests @@ -53,6 +57,12 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(CppDerivedPlain) PYBIND11_SMART_HOLDER_TYPE_CASTERS(CppDerived) TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) { + m.attr("defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = +#ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + false; +#else + true; + py::classh(m, "Base").def(py::init<>()).def("get", &Base::get); py::classh(m, "CppDerivedPlain").def(py::init<>()); @@ -61,4 +71,5 @@ TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) { m.def("get_from_cpp_plainc_ptr", get_from_cpp_plainc_ptr, py::arg("b")); m.def("get_from_cpp_unique_ptr", get_from_cpp_unique_ptr, py::arg("b")); +#endif // PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT } diff --git a/tests/test_class_sh_virtual_py_cpp_mix.py b/tests/test_class_sh_virtual_py_cpp_mix.py index 33133eb889..3361713c77 100644 --- a/tests/test_class_sh_virtual_py_cpp_mix.py +++ b/tests/test_class_sh_virtual_py_cpp_mix.py @@ -4,6 +4,9 @@ from pybind11_tests import class_sh_virtual_py_cpp_mix as m +if not m.defined_PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT: + pytest.skip("smart_holder not available.", allow_module_level=True) + class PyBase(m.Base): # Avoiding name PyDerived, for more systematic naming. def __init__(self): diff --git a/tests/test_classh_mock.cpp b/tests/test_classh_mock.cpp index 38e765fb01..135320320f 100644 --- a/tests/test_classh_mock.cpp +++ b/tests/test_classh_mock.cpp @@ -1,18 +1,20 @@ #include "pybind11_tests.h" -// The main purpose of this test is to ensure the suggested BOILERPLATE code block below is -// correct. +// The main purpose of this test was to ensure that the suggested +// BOILERPLATE code block (NOW DEPRECATED!) block below is correct. // Copy this block of code into your project. // Replace FOOEXT with the name of your project. -// BOILERPLATE BEGIN +// BOILERPLATE BEGIN DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED #ifdef FOOEXT_USING_PYBIND11_SMART_HOLDER # include #else # include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +# ifndef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT template using classh = class_; +# endif PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) # ifndef PYBIND11_SH_AVL # define PYBIND11_SH_AVL(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if AVaiLable" @@ -27,7 +29,7 @@ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) # define PYBIND11_TYPE_CASTER_BASE_HOLDER(...) # endif #endif -// BOILERPLATE END +// BOILERPLATE END DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED namespace { struct FooUc {}; @@ -37,8 +39,8 @@ struct FooSc {}; struct FooSp {}; } // namespace -PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooUp) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooSp) +PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooUp) // DEPRECATED +PYBIND11_SMART_HOLDER_TYPE_CASTERS(FooSp) // DEPRECATED PYBIND11_TYPE_CASTER_BASE_HOLDER(FooSa, std::shared_ptr) diff --git a/tests/test_embed/test_interpreter.cpp b/tests/test_embed/test_interpreter.cpp index 6e9506d63f..c6c8a22d98 100644 --- a/tests/test_embed/test_interpreter.cpp +++ b/tests/test_embed/test_interpreter.cpp @@ -66,7 +66,7 @@ PYBIND11_EMBEDDED_MODULE(widget_module, m) { PYBIND11_EMBEDDED_MODULE(trampoline_module, m) { py::class_(m, "test_override_cache_helper") + std::shared_ptr>(m, "test_override_cache_helper") .def(py::init_alias<>()) .def("func", &test_override_cache_helper::func); } diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index c8e065bc0d..a387cd2e76 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -250,7 +250,7 @@ TEST_SUBMODULE(factory_constructors, m) { }; // 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(); })); @@ -277,12 +277,12 @@ TEST_SUBMODULE(factory_constructors, m) { .def_readwrite("value", &TestFactory3::value); // 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 @@ -305,7 +305,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); })) .def(py::init([](pointer_tag, int i) { return new TestFactory7(i); }, [](pointer_tag, int i) { return new PyTF7(i); })) diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 8a8ae66d91..0ddad5e323 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -280,11 +280,10 @@ def get(self): assert not g1.has_alias() with pytest.raises(TypeError) as excinfo: PythFactory7(tag.shared_ptr, tag.invalid_base, 14) - 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 ( + str(excinfo.value) + == "pybind11::init(): construction failed: returned holder-wrapped instance 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 c13ae933ed..f433847c76 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -121,7 +121,7 @@ class NoneTester { }; 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 none3(std::shared_ptr &obj) { return obj ? obj->answer : -1; } int none4(std::shared_ptr *obj) { return obj && *obj ? (*obj)->answer : -1; } int none5(const std::shared_ptr &obj) { return obj ? obj->answer : -1; } @@ -417,21 +417,17 @@ 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").def(py::init<>()); + 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_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("ok_none5", &none5); 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 ec794457c8..dfa31f5465 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -387,10 +387,12 @@ 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: @@ -402,6 +404,9 @@ 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) @@ -422,6 +427,7 @@ 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: @@ -437,14 +443,6 @@ 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_casts_none(): """#2778: implicit casting from None to object (not pointer)""" diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 3005b0d5c2..5916ae9010 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -148,15 +148,15 @@ 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_( + py::class_>( m, "Base12a", py::multiple_inheritance()) .def(py::init()); @@ -173,14 +173,14 @@ TEST_SUBMODULE(multiple_inheritance, m) { }; struct I801E : I801B3, I801D {}; - py::class_(m, "I801B1") + py::class_>(m, "I801B1") .def(py::init<>()) .def_readonly("a", &I801B1::a); - py::class_(m, "I801B2") + 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, "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 diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 1d1d8a16ea..496073b3c1 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -287,42 +287,6 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr); PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_with_addressof_operator); PYBIND11_DECLARE_HOLDER_TYPE(T, unique_ptr_with_addressof_operator); -PYBIND11_TYPE_CASTER_BASE_HOLDER(Object, ref) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject1, ref) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject2, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject3, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject4, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject4a, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject4b, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(MyObject5, huge_unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(SharedPtrRef::A, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(SharedPtrRef, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(SharedFromThisRef::B, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(SharedFromThisRef, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(SharedFromThisVirt, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(C, custom_unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(TypeForHolderWithAddressOf, - shared_ptr_with_addressof_operator) -PYBIND11_TYPE_CASTER_BASE_HOLDER( - TypeForMoveOnlyHolderWithAddressOf, - unique_ptr_with_addressof_operator) -PYBIND11_TYPE_CASTER_BASE_HOLDER(HeldByDefaultHolder, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(ElementBase, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(ElementA, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(ElementList, std::shared_ptr) - -#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -// To prevent triggering a static_assert in the smart_holder code. -// This is a very special case, because the associated test exercises a holder mismatch. -namespace pybind11 { -namespace detail { -template <> -class type_caster> - : public copyable_holder_caster> {}; -} // namespace detail -} // namespace pybind11 -#endif - TEST_SUBMODULE(smart_ptr, m) { // Please do not interleave `struct` and `class` definitions with bindings code, // but implement `struct`s and `class`es in the anonymous namespace above. diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index 893d609f51..bf0ae4aeb1 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -301,9 +301,9 @@ def test_smart_ptr_from_default(): instance = m.HeldByDefaultHolder() with pytest.raises(RuntimeError) as excinfo: m.HeldByDefaultHolder.load_shared_ptr(instance) - assert str(excinfo.value) in ( - "Unable to load a smart-pointer type from a non-smart_holder instance.", - "Unable to load a custom holder type from a default-holder instance", + assert ( + "Unable to load a custom holder type from a " + "default-holder instance" in str(excinfo.value) ) diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index 17a999ef58..93b136ad3c 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -406,7 +406,7 @@ TEST_SUBMODULE(virtual_functions, m) { py::class_(m, "test_override_cache_helper") + std::shared_ptr>(m, "test_override_cache_helper") .def(py::init_alias<>()) .def("func", &test_override_cache_helper::func); diff --git a/ubench/holder_comparison.cpp b/ubench/holder_comparison.cpp index 75896c1bbb..f532bcfdc9 100644 --- a/ubench/holder_comparison.cpp +++ b/ubench/holder_comparison.cpp @@ -1,4 +1,4 @@ -#include +#include #include "number_bucket.h" @@ -22,6 +22,8 @@ void wrap_number_bucket(py::module m, const char *class_name) { .def("add", &WrappedType::add, py::arg("other")); } +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + template class padded_unique_ptr { std::unique_ptr ptr; @@ -35,20 +37,21 @@ class padded_unique_ptr { static_assert(sizeof(padded_unique_ptr) == sizeof(py::smart_holder), "Unexpected sizeof mismatch."); +#endif + } // namespace hc +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT PYBIND11_DECLARE_HOLDER_TYPE(T, hc::padded_unique_ptr); - -PYBIND11_TYPE_CASTER_BASE_HOLDER(hc::nb_up, std::unique_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(hc::nb_sp, std::shared_ptr) -PYBIND11_TYPE_CASTER_BASE_HOLDER(hc::nb_pu, hc::padded_unique_ptr) -PYBIND11_SMART_HOLDER_TYPE_CASTERS(hc::nb_sh) +#endif PYBIND11_MODULE(pybind11_ubench_holder_comparison, m) { using namespace hc; - m.def("sizeof_smart_holder", []() { return sizeof(py::smart_holder); }); wrap_number_bucket>(m, "number_bucket_up"); wrap_number_bucket>(m, "number_bucket_sp"); +#ifdef PYBIND11_HAVE_INTERNALS_WITH_SMART_HOLDER_SUPPORT + m.def("sizeof_smart_holder", []() { return sizeof(py::smart_holder); }); wrap_number_bucket>(m, "number_bucket_pu"); wrap_number_bucket(m, "number_bucket_sh"); +#endif }