Squashed 'wrap/' changes from 4cca84a07..4cefad122
4cefad122 Merge pull request #172 from ProfFan/fan/revert_and_update_pybind 385f78e16 Merging 'v2.13.6' into 'pybind11' 3a9158898 Squashed 'pybind11/' changes from d3c999c7..a2e59f0e 147e3ce03 Revert "Merge pull request #169 from borglab/pybind11-upgrade" git-subtree-dir: wrap git-subtree-split: 4cefad122866ab8f476c143211b0e620ad066c25release/4.3a0
parent
a0be9de490
commit
c727db52c1
|
@ -12,17 +12,6 @@ template <op_id id, op_type ot, typename L, typename R>
|
|||
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
|
||||
class_ &def(const detail::op_<id, ot, L, R> &op, const Extra &...extra) {
|
||||
class_ &def_cast(const detail::op_<id, ot, L, R> &op, const Extra &...extra) {
|
||||
int valu;
|
||||
explicit movable_int(int v) : valu{v} {}
|
||||
movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; }
|
||||
explicit indestructible_int(int v) : valu{v} {}
|
||||
REQUIRE(hld.as_raw_ptr_unowned<zombie>()->valu == 19);
|
||||
REQUIRE(othr.valu == 19);
|
||||
REQUIRE(orig.valu == 91);
|
||||
(m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"),
|
||||
atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
|
||||
assert m.atyp_valu().get_mtxt() == "Valu"
|
||||
// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,
|
||||
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
|
||||
struct IntStruct {
|
||||
explicit IntStruct(int v) : value(v){};
|
||||
|
|
|
@ -81,7 +81,7 @@ nox -s build
|
|||
### Full setup
|
||||
|
||||
To setup an ideal development environment, run the following commands on a
|
||||
system with CMake 3.15+:
|
||||
system with CMake 3.14+:
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
|
@ -96,8 +96,8 @@ Tips:
|
|||
* You can use `virtualenv` (faster, from PyPI) instead of `venv`.
|
||||
* You can select any name for your environment folder; if it contains "env" it
|
||||
will be ignored by git.
|
||||
* If you don't have CMake 3.15+, just add "cmake" to the pip install command.
|
||||
* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython.
|
||||
* If you don't have CMake 3.14+, just add "cmake" to the pip install command.
|
||||
* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+
|
||||
* In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`.
|
||||
FindPython uses `-DPython_ROOT_DIR=/path/to` or
|
||||
`-DPython_EXECUTABLE=/path/to/python`.
|
||||
|
@ -149,8 +149,8 @@ To run the tests, you can "build" the check target:
|
|||
cmake --build build --target check
|
||||
```
|
||||
|
||||
`--target` can be spelled `-t`. You can also run individual tests with these
|
||||
targets:
|
||||
`--target` can be spelled `-t` in CMake 3.15+. You can also run individual
|
||||
tests with these targets:
|
||||
|
||||
* `pytest`: Python tests only, using the
|
||||
[pytest](https://docs.pytest.org/en/stable/) framework
|
||||
|
|
|
@ -39,8 +39,6 @@ jobs:
|
|||
- 'pypy-3.8'
|
||||
- 'pypy-3.9'
|
||||
- 'pypy-3.10'
|
||||
- 'pypy-3.11'
|
||||
- 'graalpy-24.1'
|
||||
|
||||
# Items in here will either be added to the build matrix (if not
|
||||
# present), or add new keys to an existing matrix element if all the
|
||||
|
@ -66,45 +64,9 @@ jobs:
|
|||
# Inject a couple Windows 2019 runs
|
||||
- runs-on: windows-2019
|
||||
python: '3.9'
|
||||
# Inject a few runs with different runtime libraries
|
||||
- runs-on: windows-2022
|
||||
python: '3.9'
|
||||
args: >
|
||||
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded
|
||||
- runs-on: windows-2022
|
||||
python: '3.10'
|
||||
args: >
|
||||
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL
|
||||
# This needs a python built with MTd
|
||||
# - runs-on: windows-2022
|
||||
# python: '3.11'
|
||||
# args: >
|
||||
# -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug
|
||||
- runs-on: windows-2022
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL
|
||||
# Extra ubuntu latest job
|
||||
- runs-on: ubuntu-latest
|
||||
python: '3.11'
|
||||
# Run tests with py::smart_holder as the default holder
|
||||
# with recent (or ideally latest) released Python version.
|
||||
- runs-on: ubuntu-latest
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE"
|
||||
- runs-on: macos-13
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE"
|
||||
- runs-on: windows-2022
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="/DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE /GR /EHsc"
|
||||
exclude:
|
||||
# The setup-python action currently doesn't have graalpy for windows
|
||||
- python: 'graalpy-24.1'
|
||||
runs-on: 'windows-2022'
|
||||
|
||||
|
||||
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"
|
||||
|
@ -160,7 +122,6 @@ jobs:
|
|||
-DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
|
||||
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON
|
||||
-DPYBIND11_NUMPY_1_ONLY=ON
|
||||
-DPYBIND11_PYTEST_ARGS=-v
|
||||
-DDOWNLOAD_CATCH=ON
|
||||
-DDOWNLOAD_EIGEN=ON
|
||||
-DCMAKE_CXX_STANDARD=11
|
||||
|
@ -190,7 +151,6 @@ jobs:
|
|||
-DPYBIND11_WERROR=ON
|
||||
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
|
||||
-DPYBIND11_NUMPY_1_ONLY=ON
|
||||
-DPYBIND11_PYTEST_ARGS=-v
|
||||
-DDOWNLOAD_CATCH=ON
|
||||
-DDOWNLOAD_EIGEN=ON
|
||||
-DCMAKE_CXX_STANDARD=17
|
||||
|
@ -210,7 +170,6 @@ jobs:
|
|||
run: >
|
||||
cmake -S . -B build3
|
||||
-DPYBIND11_WERROR=ON
|
||||
-DPYBIND11_PYTEST_ARGS=-v
|
||||
-DDOWNLOAD_CATCH=ON
|
||||
-DDOWNLOAD_EIGEN=ON
|
||||
-DCMAKE_CXX_STANDARD=17
|
||||
|
@ -351,11 +310,22 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
clang:
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.9
|
||||
- 7
|
||||
- 9
|
||||
- dev
|
||||
std:
|
||||
- 11
|
||||
container_suffix:
|
||||
- ""
|
||||
include:
|
||||
- clang: 5
|
||||
std: 14
|
||||
- clang: 10
|
||||
std: 17
|
||||
- clang: 11
|
||||
std: 20
|
||||
- clang: 12
|
||||
|
@ -533,6 +503,10 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { gcc: 7, std: 11 }
|
||||
- { gcc: 7, std: 17 }
|
||||
- { gcc: 8, std: 14 }
|
||||
- { gcc: 8, std: 17 }
|
||||
- { gcc: 9, std: 20 }
|
||||
- { gcc: 10, std: 17 }
|
||||
- { gcc: 10, std: 20 }
|
||||
|
@ -753,9 +727,9 @@ jobs:
|
|||
|
||||
# This tests an "install" with the CMake tools
|
||||
install-classic:
|
||||
name: "🐍 3.9 • Debian • x86 • Install"
|
||||
name: "🐍 3.7 • Debian • x86 • Install"
|
||||
runs-on: ubuntu-latest
|
||||
container: i386/debian:bullseye
|
||||
container: i386/debian:buster
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1 # v1 is required to run inside docker
|
||||
|
@ -835,6 +809,7 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
python:
|
||||
- '3.7'
|
||||
- '3.8'
|
||||
- '3.9'
|
||||
- '3.10'
|
||||
|
@ -852,6 +827,8 @@ jobs:
|
|||
args: -DCMAKE_CXX_STANDARD=20
|
||||
- python: '3.8'
|
||||
args: -DCMAKE_CXX_STANDARD=17
|
||||
- python: '3.7'
|
||||
args: -DCMAKE_CXX_STANDARD=14
|
||||
|
||||
|
||||
name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}"
|
||||
|
@ -1030,6 +1007,7 @@ jobs:
|
|||
git
|
||||
mingw-w64-${{matrix.env}}-gcc
|
||||
mingw-w64-${{matrix.env}}-python-pip
|
||||
mingw-w64-${{matrix.env}}-python-numpy
|
||||
mingw-w64-${{matrix.env}}-cmake
|
||||
mingw-w64-${{matrix.env}}-make
|
||||
mingw-w64-${{matrix.env}}-python-pytest
|
||||
|
@ -1041,7 +1019,7 @@ jobs:
|
|||
with:
|
||||
msystem: ${{matrix.sys}}
|
||||
install: >-
|
||||
mingw-w64-${{matrix.env}}-python-numpy
|
||||
git
|
||||
mingw-w64-${{matrix.env}}-python-scipy
|
||||
mingw-w64-${{matrix.env}}-eigen3
|
||||
|
||||
|
@ -1139,7 +1117,7 @@ jobs:
|
|||
uses: jwlawson/actions-setup-cmake@v2.0
|
||||
|
||||
- name: Install ninja-build tool
|
||||
uses: seanmiddleditch/gha-setup-ninja@v6
|
||||
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||
|
||||
- name: Run pip installs
|
||||
run: |
|
||||
|
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
include:
|
||||
- runs-on: ubuntu-20.04
|
||||
arch: x64
|
||||
cmake: "3.15"
|
||||
cmake: "3.5"
|
||||
|
||||
- runs-on: ubuntu-20.04
|
||||
arch: x64
|
||||
|
@ -39,22 +39,22 @@ jobs:
|
|||
|
||||
- runs-on: macos-13
|
||||
arch: x64
|
||||
cmake: "3.15"
|
||||
cmake: "3.7"
|
||||
|
||||
- runs-on: windows-2019
|
||||
arch: x64 # x86 compilers seem to be missing on 2019 image
|
||||
cmake: "3.18"
|
||||
|
||||
name: 🐍 3.8 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
|
||||
name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python 3.8
|
||||
- name: Setup Python 3.7
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.7
|
||||
architecture: ${{ matrix.arch }}
|
||||
|
||||
- name: Prepare env
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pypa/cibuildwheel@v2.23
|
||||
- uses: pypa/cibuildwheel@v2.20
|
||||
env:
|
||||
PYODIDE_BUILD_EXPORTS: whole_archive
|
||||
with:
|
||||
|
|
|
@ -103,7 +103,7 @@ jobs:
|
|||
- uses: actions/download-artifact@v4
|
||||
|
||||
- name: Generate artifact attestation for sdist and wheel
|
||||
uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2
|
||||
uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3
|
||||
with:
|
||||
subject-path: "*/pybind11*"
|
||||
|
||||
|
|
|
@ -25,14 +25,14 @@ repos:
|
|||
|
||||
# Clang format the codebase automatically
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: "v19.1.7"
|
||||
rev: "v18.1.8"
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c++, c, cuda]
|
||||
|
||||
# Ruff, the Python auto-correcting linter/formatter written in Rust
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.9
|
||||
rev: v0.6.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--fix", "--show-fixes"]
|
||||
|
@ -40,7 +40,7 @@ repos:
|
|||
|
||||
# Check static types with mypy
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: "v1.15.0"
|
||||
rev: "v1.11.2"
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: []
|
||||
|
@ -62,7 +62,7 @@ repos:
|
|||
|
||||
# Standard hooks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: "v5.0.0"
|
||||
rev: "v4.6.0"
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
|
@ -76,11 +76,10 @@ repos:
|
|||
- id: mixed-line-ending
|
||||
- id: requirements-txt-fixer
|
||||
- id: trailing-whitespace
|
||||
exclude: \.patch?$
|
||||
|
||||
# Also code format the docs
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: "1.19.1"
|
||||
rev: "1.18.0"
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies:
|
||||
|
@ -91,11 +90,10 @@ repos:
|
|||
rev: "v1.5.5"
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (^docs/.*|\.patch)?$
|
||||
|
||||
# Avoid directional quotes
|
||||
- repo: https://github.com/sirosen/texthooks
|
||||
rev: "0.6.8"
|
||||
rev: "0.6.7"
|
||||
hooks:
|
||||
- id: fix-ligatures
|
||||
- id: fix-smartquotes
|
||||
|
@ -110,7 +108,7 @@ repos:
|
|||
|
||||
# Checks the manifest for missing files (native support)
|
||||
- repo: https://github.com/mgedmin/check-manifest
|
||||
rev: "0.50"
|
||||
rev: "0.49"
|
||||
hooks:
|
||||
- id: check-manifest
|
||||
# This is a slow hook, so only run this if --hook-stage manual is passed
|
||||
|
@ -121,7 +119,7 @@ repos:
|
|||
# Use tools/codespell_ignore_lines_from_errors.py
|
||||
# to rebuild .codespell-ignore-lines
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: "v2.4.1"
|
||||
rev: "v2.3.0"
|
||||
hooks:
|
||||
- id: codespell
|
||||
exclude: ".supp$"
|
||||
|
@ -144,14 +142,14 @@ repos:
|
|||
|
||||
# PyLint has native support - not always usable, but works for us
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: "v3.3.4"
|
||||
rev: "v3.2.7"
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^pybind11
|
||||
|
||||
# Check schemas on some of our YAML files
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.31.2
|
||||
rev: 0.29.2
|
||||
hooks:
|
||||
- id: check-readthedocs
|
||||
- id: check-github-workflows
|
||||
|
|
|
@ -10,7 +10,16 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.27")
|
|||
cmake_policy(GET CMP0148 _pybind11_cmp0148)
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with
|
||||
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
|
||||
# the behavior using the following workaround:
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.29)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
else()
|
||||
cmake_policy(VERSION 3.29)
|
||||
endif()
|
||||
|
||||
if(_pybind11_cmp0148)
|
||||
cmake_policy(SET CMP0148 ${_pybind11_cmp0148})
|
||||
|
@ -18,7 +27,9 @@ if(_pybind11_cmp0148)
|
|||
endif()
|
||||
|
||||
# Avoid infinite recursion if tests include this as a subdirectory
|
||||
include_guard(GLOBAL)
|
||||
if(DEFINED PYBIND11_MASTER_PROJECT)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Extract project version from source
|
||||
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/pybind11/detail/common.h"
|
||||
|
@ -63,6 +74,14 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
|
|||
|
||||
set(PYBIND11_MASTER_PROJECT ON)
|
||||
|
||||
if(OSX AND CMAKE_VERSION VERSION_LESS 3.7)
|
||||
# Bug in macOS CMake < 3.7 is unable to download catch
|
||||
message(WARNING "CMAKE 3.7+ needed on macOS to download catch, and newer HIGHLY recommended")
|
||||
elseif(WINDOWS AND CMAKE_VERSION VERSION_LESS 3.8)
|
||||
# Only tested with 3.8+ in CI.
|
||||
message(WARNING "CMAKE 3.8+ tested on Windows, previous versions untested")
|
||||
endif()
|
||||
|
||||
message(STATUS "CMake ${CMAKE_VERSION}")
|
||||
|
||||
if(CMAKE_CXX_STANDARD)
|
||||
|
@ -114,7 +133,8 @@ cmake_dependent_option(
|
|||
"Install pybind11 headers in Python include directory instead of default installation prefix"
|
||||
OFF "PYBIND11_INSTALL" OFF)
|
||||
|
||||
option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default})
|
||||
cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default}
|
||||
"NOT CMAKE_VERSION VERSION_LESS 3.12" OFF)
|
||||
|
||||
# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests
|
||||
# (makes transition easier while we support both modes).
|
||||
|
@ -131,13 +151,10 @@ set(PYBIND11_HEADERS
|
|||
include/pybind11/detail/common.h
|
||||
include/pybind11/detail/cpp_conduit.h
|
||||
include/pybind11/detail/descr.h
|
||||
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
|
||||
include/pybind11/detail/init.h
|
||||
include/pybind11/detail/internals.h
|
||||
include/pybind11/detail/struct_smart_holder.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/detail/exception_translation.h
|
||||
include/pybind11/attr.h
|
||||
|
@ -146,9 +163,6 @@ set(PYBIND11_HEADERS
|
|||
include/pybind11/chrono.h
|
||||
include/pybind11/common.h
|
||||
include/pybind11/complex.h
|
||||
include/pybind11/conduit/pybind11_conduit_v1.h
|
||||
include/pybind11/conduit/pybind11_platform_abi_id.h
|
||||
include/pybind11/conduit/wrap_include_python_h.h
|
||||
include/pybind11/options.h
|
||||
include/pybind11/eigen.h
|
||||
include/pybind11/eigen/common.h
|
||||
|
@ -167,13 +181,11 @@ set(PYBIND11_HEADERS
|
|||
include/pybind11/stl.h
|
||||
include/pybind11/stl_bind.h
|
||||
include/pybind11/stl/filesystem.h
|
||||
include/pybind11/trampoline_self_life_support.h
|
||||
include/pybind11/type_caster_pyobject_ptr.h
|
||||
include/pybind11/typing.h
|
||||
include/pybind11/warnings.h)
|
||||
include/pybind11/typing.h)
|
||||
|
||||
# Compare with grep and warn if mismatched
|
||||
if(PYBIND11_MASTER_PROJECT)
|
||||
if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12)
|
||||
file(
|
||||
GLOB_RECURSE _pybind11_header_check
|
||||
LIST_DIRECTORIES false
|
||||
|
@ -191,7 +203,10 @@ if(PYBIND11_MASTER_PROJECT)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
list(TRANSFORM PYBIND11_HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/")
|
||||
# CMake 3.12 added list(TRANSFORM <list> PREPEND
|
||||
# But we can't use it yet
|
||||
string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS
|
||||
"${PYBIND11_HEADERS}")
|
||||
|
||||
# Cache variable so this can be used in parent projects
|
||||
set(pybind11_INCLUDE_DIR
|
||||
|
@ -261,11 +276,25 @@ if(PYBIND11_INSTALL)
|
|||
tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
||||
|
||||
# CMake natively supports header-only libraries
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT)
|
||||
if(CMAKE_VERSION VERSION_LESS 3.14)
|
||||
# Remove CMAKE_SIZEOF_VOID_P from ConfigVersion.cmake since the library does
|
||||
# not depend on architecture specific settings or libraries.
|
||||
set(_PYBIND11_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P})
|
||||
unset(CMAKE_SIZEOF_VOID_P)
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
|
||||
set(CMAKE_SIZEOF_VOID_P ${_PYBIND11_CMAKE_SIZEOF_VOID_P})
|
||||
else()
|
||||
# CMake 3.14+ natively supports header-only libraries
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT)
|
||||
endif()
|
||||
|
||||
install(
|
||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. figure:: https://github.com/pybind/pybind11/raw/master/docs/pybind11-logo.png
|
||||
:alt: pybind11 logo
|
||||
|
||||
**pybind11 (v3) — Seamless interoperability between C++ and Python**
|
||||
**pybind11 — Seamless operability between C++11 and Python**
|
||||
|
||||
|Latest Documentation Status| |Stable Documentation Status| |Gitter chat| |GitHub Discussions| |CI| |Build status|
|
||||
|
||||
|
@ -34,7 +34,7 @@ dependency.
|
|||
Think of this library as a tiny self-contained version of Boost.Python
|
||||
with everything stripped away that isn't relevant for binding
|
||||
generation. Without comments, the core header files only require ~4K
|
||||
lines of code and depend on Python (3.8+, or PyPy) and the C++
|
||||
lines of code and depend on Python (3.7+, or PyPy) and the C++
|
||||
standard library. This compact implementation was possible thanks to
|
||||
some C++11 language features (specifically: tuples, lambda functions and
|
||||
variadic templates). Since its creation, this library has grown beyond
|
||||
|
@ -79,7 +79,7 @@ Goodies
|
|||
In addition to the core functionality, pybind11 provides some extra
|
||||
goodies:
|
||||
|
||||
- Python 3.8+, and PyPy3 7.3 are supported with an implementation-agnostic
|
||||
- Python 3.7+, and PyPy3 7.3 are supported with an implementation-agnostic
|
||||
interface (pybind11 2.9 was the last version to support Python 2 and 3.5).
|
||||
|
||||
- It is possible to bind C++11 lambda functions with captured
|
||||
|
@ -134,34 +134,11 @@ About
|
|||
|
||||
This project was created by `Wenzel
|
||||
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
|
||||
improvements to the code were contributed by
|
||||
Jonas Adler,
|
||||
Lori A. Burns,
|
||||
Sylvain Corlay,
|
||||
Eric Cousineau,
|
||||
Aaron Gokaslan,
|
||||
Ralf Grosse-Kunstleve,
|
||||
Trent Houliston,
|
||||
Axel Huebl,
|
||||
@hulucc,
|
||||
Yannick Jadoul,
|
||||
Sergey Lyskov,
|
||||
Johan Mabille,
|
||||
Tomasz Miąsko,
|
||||
Dean Moldovan,
|
||||
Ben Pritchard,
|
||||
Jason Rhinelander,
|
||||
Boris Schäling,
|
||||
Pim Schellart,
|
||||
Henry Schreiner,
|
||||
Ivan Smirnov,
|
||||
Dustin Spicuzza,
|
||||
Boris Staletic,
|
||||
Ethan Steinberg,
|
||||
Patrick Stewart,
|
||||
Ivor Wanders,
|
||||
and
|
||||
Xiaofei Wang.
|
||||
improvements to the code were contributed by Jonas Adler, Lori A. Burns,
|
||||
Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel
|
||||
Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov, Johan Mabille, Tomasz Miąsko,
|
||||
Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim
|
||||
Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart.
|
||||
|
||||
We thank Google for a generous financial contribution to the continuous
|
||||
integration infrastructure used by this project.
|
||||
|
|
|
@ -1,53 +1,35 @@
|
|||
Custom type casters
|
||||
===================
|
||||
|
||||
Some applications may prefer custom type casters that convert between existing
|
||||
Python types and C++ types, similar to the ``list`` ↔ ``std::vector``
|
||||
and ``dict`` ↔ ``std::map`` conversions which are built into pybind11.
|
||||
Implementing custom type casters is fairly advanced usage.
|
||||
While it is recommended to use the pybind11 API as much as possible, more complex examples may
|
||||
require familiarity with the intricacies of the Python C API.
|
||||
You can refer to the `Python/C API Reference Manual <https://docs.python.org/3/c-api/index.html>`_
|
||||
for more information.
|
||||
In very rare cases, applications may require custom type casters that cannot be
|
||||
expressed using the abstractions provided by pybind11, thus requiring raw
|
||||
Python C API calls. This is fairly advanced usage and should only be pursued by
|
||||
experts who are familiar with the intricacies of Python reference counting.
|
||||
|
||||
The following snippets demonstrate how this works for a very simple ``Point2D`` type.
|
||||
We want this type to be convertible to C++ from Python types implementing the
|
||||
``Sequence`` protocol and having two elements of type ``float``.
|
||||
When returned from C++ to Python, it should be converted to a Python ``tuple[float, float]``.
|
||||
For this type we could provide Python bindings for different arithmetic functions implemented
|
||||
in C++ (here demonstrated by a simple ``negate`` function).
|
||||
|
||||
..
|
||||
PLEASE KEEP THE CODE BLOCKS IN SYNC WITH
|
||||
tests/test_docs_advanced_cast_custom.cpp
|
||||
tests/test_docs_advanced_cast_custom.py
|
||||
Ideally, change the test, run pre-commit (incl. clang-format),
|
||||
then copy the changed code back here.
|
||||
Also use TEST_SUBMODULE in tests, but PYBIND11_MODULE in docs.
|
||||
The following snippets demonstrate how this works for a very simple ``inty``
|
||||
type that that should be convertible from Python types that provide a
|
||||
``__int__(self)`` method.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
namespace user_space {
|
||||
struct inty { long long_value; };
|
||||
|
||||
struct Point2D {
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
void print(inty s) {
|
||||
std::cout << s.long_value << std::endl;
|
||||
}
|
||||
|
||||
Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; }
|
||||
|
||||
} // namespace user_space
|
||||
|
||||
|
||||
The following Python snippet demonstrates the intended usage of ``negate`` from the Python side:
|
||||
The following Python snippet demonstrates the intended usage from the Python side:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from my_math_module import docs_advanced_cast_custom as m
|
||||
class A:
|
||||
def __int__(self):
|
||||
return 123
|
||||
|
||||
point1 = [1.0, -1.0]
|
||||
point2 = m.negate(point1)
|
||||
assert point2 == (-1.0, 1.0)
|
||||
|
||||
from example import print
|
||||
|
||||
print(A())
|
||||
|
||||
To register the necessary conversion routines, it is necessary to add an
|
||||
instantiation of the ``pybind11::detail::type_caster<T>`` template.
|
||||
|
@ -56,57 +38,47 @@ type is explicitly allowed.
|
|||
|
||||
.. code-block:: cpp
|
||||
|
||||
namespace pybind11 {
|
||||
namespace detail {
|
||||
namespace PYBIND11_NAMESPACE { namespace detail {
|
||||
template <> struct type_caster<inty> {
|
||||
public:
|
||||
/**
|
||||
* This macro establishes the name 'inty' in
|
||||
* function signatures and declares a local variable
|
||||
* 'value' of type inty
|
||||
*/
|
||||
PYBIND11_TYPE_CASTER(inty, const_name("inty"));
|
||||
|
||||
template <>
|
||||
struct type_caster<user_space::Point2D> {
|
||||
// This macro inserts a lot of boilerplate code and sets the type hint.
|
||||
// `io_name` is used to specify different type hints for arguments and return values.
|
||||
// The signature of our negate function would then look like:
|
||||
// `negate(Sequence[float]) -> tuple[float, float]`
|
||||
PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]"));
|
||||
|
||||
// C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments
|
||||
// are used to indicate the return value policy and parent object (for
|
||||
// return_value_policy::reference_internal) and are often ignored by custom casters.
|
||||
// The return value should reflect the type hint specified by the second argument of `io_name`.
|
||||
static handle
|
||||
cast(const user_space::Point2D &number, return_value_policy /*policy*/, handle /*parent*/) {
|
||||
return py::make_tuple(number.x, number.y).release();
|
||||
}
|
||||
|
||||
// Python -> C++: convert a `PyObject` into a `Point2D` and return false upon failure. The
|
||||
// second argument indicates whether implicit conversions should be allowed.
|
||||
// The accepted types should reflect the type hint specified by the first argument of
|
||||
// `io_name`.
|
||||
bool load(handle src, bool /*convert*/) {
|
||||
// Check if handle is a Sequence
|
||||
if (!py::isinstance<py::sequence>(src)) {
|
||||
return false;
|
||||
}
|
||||
auto seq = py::reinterpret_borrow<py::sequence>(src);
|
||||
// Check if exactly two values are in the Sequence
|
||||
if (seq.size() != 2) {
|
||||
return false;
|
||||
}
|
||||
// Check if each element is either a float or an int
|
||||
for (auto item : seq) {
|
||||
if (!py::isinstance<py::float_>(item) && !py::isinstance<py::int_>(item)) {
|
||||
/**
|
||||
* Conversion part 1 (Python->C++): convert a PyObject into a inty
|
||||
* instance or return false upon failure. The second argument
|
||||
* indicates whether implicit conversions should be applied.
|
||||
*/
|
||||
bool load(handle src, bool) {
|
||||
/* Extract PyObject from handle */
|
||||
PyObject *source = src.ptr();
|
||||
/* Try converting into a Python integer value */
|
||||
PyObject *tmp = PyNumber_Long(source);
|
||||
if (!tmp)
|
||||
return false;
|
||||
}
|
||||
/* Now try to convert into a C++ int */
|
||||
value.long_value = PyLong_AsLong(tmp);
|
||||
Py_DECREF(tmp);
|
||||
/* Ensure return code was OK (to avoid out-of-range errors etc) */
|
||||
return !(value.long_value == -1 && !PyErr_Occurred());
|
||||
}
|
||||
value.x = seq[0].cast<double>();
|
||||
value.y = seq[1].cast<double>();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
|
||||
// Bind the negate function
|
||||
PYBIND11_MODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); }
|
||||
/**
|
||||
* Conversion part 2 (C++ -> Python): convert an inty instance into
|
||||
* a Python object. The second and third arguments are used to
|
||||
* indicate the return value policy and parent object (for
|
||||
* ``return_value_policy::reference_internal``) and are generally
|
||||
* ignored by implicit casters.
|
||||
*/
|
||||
static handle cast(inty src, return_value_policy /* policy */, handle /* parent */) {
|
||||
return PyLong_FromLong(src.long_value);
|
||||
}
|
||||
};
|
||||
}} // namespace PYBIND11_NAMESPACE::detail
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -114,22 +86,8 @@ type is explicitly allowed.
|
|||
that ``T`` is default-constructible (``value`` is first default constructed
|
||||
and then ``load()`` assigns to it).
|
||||
|
||||
.. note::
|
||||
For further information on the ``return_value_policy`` argument of ``cast`` refer to :ref:`return_value_policies`.
|
||||
To learn about the ``convert`` argument of ``load`` see :ref:`nonconverting_arguments`.
|
||||
|
||||
.. warning::
|
||||
|
||||
When using custom type casters, it's important to declare them consistently
|
||||
in every compilation unit of the Python extension module to satisfy the C++ One Definition Rule
|
||||
(`ODR <https://en.cppreference.com/w/cpp/language/definition>`_). Otherwise,
|
||||
in every compilation unit of the Python extension module. Otherwise,
|
||||
undefined behavior can ensue.
|
||||
|
||||
.. note::
|
||||
|
||||
Using the type hint ``Sequence[float]`` signals to static type checkers, that not only tuples may be
|
||||
passed, but any type implementing the Sequence protocol, e.g., ``list[float]``.
|
||||
Unfortunately, that loses the length information ``tuple[float, float]`` provides.
|
||||
One way of still providing some length information in type hints is using ``typing.Annotated``, e.g.,
|
||||
``Annotated[Sequence[float], 2]``, or further add libraries like
|
||||
`annotated-types <https://github.com/annotated-types/annotated-types>`_.
|
||||
|
|
|
@ -151,7 +151,7 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
|
|||
+------------------------------------+---------------------------+-----------------------------------+
|
||||
| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` |
|
||||
+------------------------------------+---------------------------+-----------------------------------+
|
||||
| ``std::filesystem::path`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` |
|
||||
| ``std::filesystem::path<T>`` | STL path (C++17) [#]_ | :file:`pybind11/stl/filesystem.h` |
|
||||
+------------------------------------+---------------------------+-----------------------------------+
|
||||
| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` |
|
||||
+------------------------------------+---------------------------+-----------------------------------+
|
||||
|
@ -167,4 +167,4 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
|
|||
+------------------------------------+---------------------------+-----------------------------------+
|
||||
|
||||
.. [#] ``std::filesystem::path`` is converted to ``pathlib.Path`` and
|
||||
can be loaded from ``os.PathLike``, ``str``, and ``bytes``.
|
||||
``os.PathLike`` is converted to ``std::filesystem::path``.
|
||||
|
|
|
@ -169,8 +169,8 @@ macro must be specified at the top level (and outside of any namespaces), since
|
|||
it adds a template instantiation of ``type_caster``. If your binding code consists of
|
||||
multiple compilation units, it must be present in every file (typically via a
|
||||
common header) preceding any usage of ``std::vector<int>``. Opaque types must
|
||||
also have a corresponding ``py::class_`` declaration to associate them with a
|
||||
name in Python, and to define a set of available operations, e.g.:
|
||||
also have a corresponding ``class_`` declaration to associate them with a name
|
||||
in Python, and to define a set of available operations, e.g.:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ helper class that is defined as follows:
|
|||
|
||||
.. code-block:: cpp
|
||||
|
||||
class PyAnimal : public Animal, py::trampoline_self_life_support {
|
||||
class PyAnimal : public Animal {
|
||||
public:
|
||||
/* Inherit the constructors */
|
||||
using Animal::Animal;
|
||||
|
@ -80,18 +80,6 @@ helper class that is defined as follows:
|
|||
}
|
||||
};
|
||||
|
||||
The ``py::trampoline_self_life_support`` base class is needed to ensure
|
||||
that a ``std::unique_ptr`` can safely be passed between Python and C++. To
|
||||
steer clear of notorious pitfalls (e.g. inheritance slicing), it is best
|
||||
practice to always use the base class, in combination with
|
||||
``py::smart_holder``.
|
||||
|
||||
.. note::
|
||||
For completeness, the base class has no effect if a holder other than
|
||||
``py::smart_holder`` used, including the default ``std::unique_ptr<T>``.
|
||||
Please think twice, though, the pitfalls are very real, and the overhead
|
||||
for using the safer ``py::smart_holder`` is very likely to be in the noise.
|
||||
|
||||
The macro :c:macro:`PYBIND11_OVERRIDE_PURE` should be used for pure virtual
|
||||
functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have
|
||||
a default implementation. There are also two alternate macros
|
||||
|
@ -107,18 +95,18 @@ The binding code also needs a few minor adaptations (highlighted):
|
|||
:emphasize-lines: 2,3
|
||||
|
||||
PYBIND11_MODULE(example, m) {
|
||||
py::class_<Animal, PyAnimal /* <--- trampoline */, py::smart_holder>(m, "Animal")
|
||||
py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal")
|
||||
.def(py::init<>())
|
||||
.def("go", &Animal::go);
|
||||
|
||||
py::class_<Dog, Animal, py::smart_holder>(m, "Dog")
|
||||
py::class_<Dog, Animal>(m, "Dog")
|
||||
.def(py::init<>());
|
||||
|
||||
m.def("call_go", &call_go);
|
||||
}
|
||||
|
||||
Importantly, pybind11 is made aware of the trampoline helper class by
|
||||
specifying it as an extra template argument to ``py::class_``. (This can also
|
||||
specifying it as an extra template argument to :class:`class_`. (This can also
|
||||
be combined with other template arguments such as a custom holder type; the
|
||||
order of template types does not matter). Following this, we are able to
|
||||
define a constructor as usual.
|
||||
|
@ -128,9 +116,9 @@ Bindings should be made against the actual class, not the trampoline helper clas
|
|||
.. code-block:: cpp
|
||||
:emphasize-lines: 3
|
||||
|
||||
py::class_<Animal, PyAnimal /* <--- trampoline */, py::smart_holder>(m, "Animal");
|
||||
py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal");
|
||||
.def(py::init<>())
|
||||
.def("go", &Animal::go); /* <--- DO NOT USE &PyAnimal::go HERE */
|
||||
.def("go", &PyAnimal::go); /* <--- THIS IS WRONG, use &Animal::go */
|
||||
|
||||
Note, however, that the above is sufficient for allowing python classes to
|
||||
extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the
|
||||
|
@ -256,13 +244,13 @@ override the ``name()`` method):
|
|||
|
||||
.. code-block:: cpp
|
||||
|
||||
class PyAnimal : public Animal, py::trampoline_self_life_support {
|
||||
class PyAnimal : public Animal {
|
||||
public:
|
||||
using Animal::Animal; // Inherit constructors
|
||||
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Animal, go, n_times); }
|
||||
std::string name() override { PYBIND11_OVERRIDE(std::string, Animal, name, ); }
|
||||
};
|
||||
class PyDog : public Dog, py::trampoline_self_life_support {
|
||||
class PyDog : public Dog {
|
||||
public:
|
||||
using Dog::Dog; // Inherit constructors
|
||||
std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, Dog, go, n_times); }
|
||||
|
@ -284,7 +272,7 @@ declare or override any virtual methods itself:
|
|||
.. code-block:: cpp
|
||||
|
||||
class Husky : public Dog {};
|
||||
class PyHusky : public Husky, py::trampoline_self_life_support {
|
||||
class PyHusky : public Husky {
|
||||
public:
|
||||
using Husky::Husky; // Inherit constructors
|
||||
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Husky, go, n_times); }
|
||||
|
@ -299,15 +287,13 @@ follows:
|
|||
|
||||
.. code-block:: cpp
|
||||
|
||||
template <class AnimalBase = Animal>
|
||||
class PyAnimal : public AnimalBase, py::trampoline_self_life_support {
|
||||
template <class AnimalBase = Animal> class PyAnimal : public AnimalBase {
|
||||
public:
|
||||
using AnimalBase::AnimalBase; // Inherit constructors
|
||||
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); }
|
||||
std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); }
|
||||
};
|
||||
template <class DogBase = Dog>
|
||||
class PyDog : public PyAnimal<DogBase>, py::trampoline_self_life_support {
|
||||
template <class DogBase = Dog> class PyDog : public PyAnimal<DogBase> {
|
||||
public:
|
||||
using PyAnimal<DogBase>::PyAnimal; // Inherit constructors
|
||||
// Override PyAnimal's pure virtual go() with a non-pure one:
|
||||
|
@ -325,9 +311,9 @@ The classes are then registered with pybind11 using:
|
|||
|
||||
.. code-block:: cpp
|
||||
|
||||
py::class_<Animal, PyAnimal<>, py::smart_holder> animal(m, "Animal");
|
||||
py::class_<Dog, Animal, PyDog<>, py::smart_holder> dog(m, "Dog");
|
||||
py::class_<Husky, Dog, PyDog<Husky>, py::smart_holder> husky(m, "Husky");
|
||||
py::class_<Animal, PyAnimal<>> animal(m, "Animal");
|
||||
py::class_<Dog, Animal, PyDog<>> dog(m, "Dog");
|
||||
py::class_<Husky, Dog, PyDog<Husky>> husky(m, "Husky");
|
||||
// ... add animal, dog, husky definitions
|
||||
|
||||
Note that ``Husky`` did not require a dedicated trampoline template class at
|
||||
|
@ -513,12 +499,12 @@ an alias:
|
|||
// ...
|
||||
virtual ~Example() = default;
|
||||
};
|
||||
class PyExample : public Example, py::trampoline_self_life_support {
|
||||
class PyExample : public Example {
|
||||
public:
|
||||
using Example::Example;
|
||||
PyExample(Example &&base) : Example(std::move(base)) {}
|
||||
};
|
||||
py::class_<Example, PyExample, py::smart_holder>(m, "Example")
|
||||
py::class_<Example, PyExample>(m, "Example")
|
||||
// Returns an Example pointer. If a PyExample is needed, the Example
|
||||
// instance will be moved via the extra constructor in PyExample, above.
|
||||
.def(py::init([]() { return new Example(); }))
|
||||
|
@ -564,10 +550,9 @@ pybind11. The underlying issue is that the ``std::unique_ptr`` holder type that
|
|||
is responsible for managing the lifetime of instances will reference the
|
||||
destructor even if no deallocations ever take place. In order to expose classes
|
||||
with private or protected destructors, it is possible to override the holder
|
||||
type via a holder type argument to ``py::class_``. Pybind11 provides a helper
|
||||
class ``py::nodelete`` that disables any destructor invocations. In this case,
|
||||
it is crucial that instances are deallocated on the C++ side to avoid memory
|
||||
leaks.
|
||||
type via a holder type argument to ``class_``. Pybind11 provides a helper class
|
||||
``py::nodelete`` that disables any destructor invocations. In this case, it is
|
||||
crucial that instances are deallocated on the C++ side to avoid memory leaks.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
@ -841,7 +826,8 @@ An instance can now be pickled as follows:
|
|||
always use the latest available version. Beware: failure to follow these
|
||||
instructions will cause important pybind11 memory allocation routines to be
|
||||
skipped during unpickling, which will likely lead to memory corruption
|
||||
and/or segmentation faults.
|
||||
and/or segmentation faults. Python defaults to version 3 (Python 3-3.7) and
|
||||
version 4 for Python 3.8+.
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
@ -886,7 +872,7 @@ Multiple Inheritance
|
|||
|
||||
pybind11 can create bindings for types that derive from multiple base types
|
||||
(aka. *multiple inheritance*). To do so, specify all bases in the template
|
||||
arguments of the ``py::class_`` declaration:
|
||||
arguments of the ``class_`` declaration:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
@ -961,11 +947,11 @@ because of conflicting definitions on the external type:
|
|||
// dogs.cpp
|
||||
|
||||
// Binding for external library class:
|
||||
py::class_<pets::Pet>(m, "Pet")
|
||||
py::class<pets::Pet>(m, "Pet")
|
||||
.def("name", &pets::Pet::name);
|
||||
|
||||
// Binding for local extension class:
|
||||
py::class_<Dog, pets::Pet>(m, "Dog")
|
||||
py::class<Dog, pets::Pet>(m, "Dog")
|
||||
.def(py::init<std::string>());
|
||||
|
||||
.. code-block:: cpp
|
||||
|
@ -973,11 +959,11 @@ because of conflicting definitions on the external type:
|
|||
// cats.cpp, in a completely separate project from the above dogs.cpp.
|
||||
|
||||
// Binding for external library class:
|
||||
py::class_<pets::Pet>(m, "Pet")
|
||||
py::class<pets::Pet>(m, "Pet")
|
||||
.def("get_name", &pets::Pet::name);
|
||||
|
||||
// Binding for local extending class:
|
||||
py::class_<Cat, pets::Pet>(m, "Cat")
|
||||
py::class<Cat, pets::Pet>(m, "Cat")
|
||||
.def(py::init<std::string>());
|
||||
|
||||
.. code-block:: pycon
|
||||
|
@ -995,13 +981,13 @@ the ``py::class_`` constructor:
|
|||
.. code-block:: cpp
|
||||
|
||||
// Pet binding in dogs.cpp:
|
||||
py::class_<pets::Pet>(m, "Pet", py::module_local())
|
||||
py::class<pets::Pet>(m, "Pet", py::module_local())
|
||||
.def("name", &pets::Pet::name);
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// Pet binding in cats.cpp:
|
||||
py::class_<pets::Pet>(m, "Pet", py::module_local())
|
||||
py::class<pets::Pet>(m, "Pet", py::module_local())
|
||||
.def("get_name", &pets::Pet::name);
|
||||
|
||||
This makes the Python-side ``dogs.Pet`` and ``cats.Pet`` into distinct classes,
|
||||
|
@ -1119,7 +1105,7 @@ described trampoline:
|
|||
virtual int foo() const { return 42; }
|
||||
};
|
||||
|
||||
class Trampoline : public A, py::trampoline_self_life_support {
|
||||
class Trampoline : public A {
|
||||
public:
|
||||
int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); }
|
||||
};
|
||||
|
@ -1129,7 +1115,7 @@ described trampoline:
|
|||
using A::foo;
|
||||
};
|
||||
|
||||
py::class_<A, Trampoline, py::smart_holder>(m, "A") // <-- `Trampoline` here
|
||||
py::class_<A, Trampoline>(m, "A") // <-- `Trampoline` here
|
||||
.def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`!
|
||||
|
||||
Binding final classes
|
||||
|
@ -1210,7 +1196,7 @@ but once again each instantiation must be explicitly specified:
|
|||
T fn(V v);
|
||||
};
|
||||
|
||||
py::class_<MyClass<int>>(m, "MyClassT")
|
||||
py::class<MyClass<int>>(m, "MyClassT")
|
||||
.def("fn", &MyClass<int>::fn<std::string>);
|
||||
|
||||
Custom automatic downcasters
|
||||
|
|
|
@ -1,391 +0,0 @@
|
|||
# Double locking, deadlocking, GIL
|
||||
|
||||
[TOC]
|
||||
|
||||
## Introduction
|
||||
|
||||
### Overview
|
||||
|
||||
In concurrent programming with locks, *deadlocks* can arise when more than one
|
||||
mutex is locked at the same time, and careful attention has to be paid to lock
|
||||
ordering to avoid this. Here we will look at a common situation that occurs in
|
||||
native extensions for CPython written in C++.
|
||||
|
||||
### Deadlocks
|
||||
|
||||
A deadlock can occur when more than one thread attempts to lock more than one
|
||||
mutex, and two of the threads lock two of the mutexes in different orders. For
|
||||
example, consider mutexes `mu1` and `mu2`, and threads T1 and T2, executing:
|
||||
|
||||
| | T1 | T2 |
|
||||
|--- | ------------------- | -------------------|
|
||||
|1 | `mu1.lock()`{.good} | `mu2.lock()`{.good}|
|
||||
|2 | `mu2.lock()`{.bad} | `mu1.lock()`{.bad} |
|
||||
|3 | `/* work */` | `/* work */` |
|
||||
|4 | `mu2.unlock()` | `mu1.unlock()` |
|
||||
|5 | `mu1.unlock()` | `mu2.unlock()` |
|
||||
|
||||
Now if T1 manages to lock `mu1` and T2 manages to lock `mu2` (as indicated in
|
||||
green), then both threads will block while trying to lock the respective other
|
||||
mutex (as indicated in red), but they are also unable to release the mutex that
|
||||
they have locked (step 5).
|
||||
|
||||
**The problem** is that it is possible for one thread to attempt to lock `mu1`
|
||||
and then `mu2`, and for another thread to attempt to lock `mu2` and then `mu1`.
|
||||
Note that it does not matter if either mutex is unlocked at any intermediate
|
||||
point; what matters is only the order of any attempt to *lock* the mutexes. For
|
||||
example, the following, more complex series of operations is just as prone to
|
||||
deadlock:
|
||||
|
||||
| | T1 | T2 |
|
||||
|--- | ------------------- | -------------------|
|
||||
|1 | `mu1.lock()`{.good} | `mu1.lock()`{.good}|
|
||||
|2 | waiting for T2 | `mu2.lock()`{.good}|
|
||||
|3 | waiting for T2 | `/* work */` |
|
||||
|3 | waiting for T2 | `mu1.unlock()` |
|
||||
|3 | `mu2.lock()`{.bad} | `/* work */` |
|
||||
|3 | `/* work */` | `mu1.lock()`{.bad} |
|
||||
|3 | `/* work */` | `/* work */` |
|
||||
|4 | `mu2.unlock()` | `mu1.unlock()` |
|
||||
|5 | `mu1.unlock()` | `mu2.unlock()` |
|
||||
|
||||
When the mutexes involved in a locking sequence are known at compile-time, then
|
||||
avoiding deadlocks is “merely” a matter of arranging the lock
|
||||
operations carefully so as to only occur in one single, fixed order. However, it
|
||||
is also possible for mutexes to only be determined at runtime. A typical example
|
||||
of this is a database where each row has its own mutex. An operation that
|
||||
modifies two rows in a single transaction (e.g. “transferring an amount
|
||||
from one account to another”) must lock two row mutexes, but the locking
|
||||
order cannot be established at compile time. In this case, a dynamic
|
||||
“deadlock avoidance algorithm” is needed. (In C++, `std::lock`
|
||||
provides such an algorithm. An algorithm might use a non-blocking `try_lock`
|
||||
operation on a mutex, which can either succeed or fail to lock the mutex, but
|
||||
returns without blocking.)
|
||||
|
||||
Conceptually, one could also consider it a deadlock if _the same_ thread
|
||||
attempts to lock a mutex that it has already locked (e.g. when some locked
|
||||
operation accidentally recurses into itself): `mu.lock();`{.good}
|
||||
`mu.lock();`{.bad} However, this is a slightly separate issue: Typical mutexes
|
||||
are either of _recursive_ or _non-recursive_ kind. A recursive mutex allows
|
||||
repeated locking and requires balanced unlocking. A non-recursive mutex can be
|
||||
implemented more efficiently, and/but for efficiency reasons does not actually
|
||||
guarantee a deadlock on second lock. Instead, the API simply forbids such use,
|
||||
making it a precondition that the thread not already hold the mutex, with
|
||||
undefined behaviour on violation.
|
||||
|
||||
### “Once” initialization
|
||||
|
||||
A common programming problem is to have an operation happen precisely once, even
|
||||
if requested concurrently. While it is clear that we need to track in some
|
||||
shared state somewhere whether the operation has already happened, it is worth
|
||||
noting that this state only ever transitions, once, from `false` to `true`. This
|
||||
is considerably simpler than a general shared state that can change values
|
||||
arbitrarily. Next, we also need a mechanism for all but one thread to block
|
||||
until the initialization has completed, which we can provide with a mutex. The
|
||||
simplest solution just always locks the mutex:
|
||||
|
||||
```c++
|
||||
// The "once" mechanism:
|
||||
constinit absl::Mutex mu(absl::kConstInit);
|
||||
constinit bool init_done = false;
|
||||
|
||||
// The operation of interest:
|
||||
void f();
|
||||
|
||||
void InitOnceNaive() {
|
||||
absl::MutexLock lock(&mu);
|
||||
if (!init_done) {
|
||||
f();
|
||||
init_done = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This works, but the efficiency-minded reader will observe that once the
|
||||
operation has completed, all future lock contention on the mutex is
|
||||
unnecessary. This leads to the (in)famous “double-locking”
|
||||
algorithm, which was historically hard to write correctly. The idea is to check
|
||||
the boolean *before* locking the mutex, and avoid locking if the operation has
|
||||
already completed. However, accessing shared state concurrently when at least
|
||||
one access is a write is prone to causing a data race and needs to be done
|
||||
according to an appropriate concurrent programming model. In C++ we use atomic
|
||||
variables:
|
||||
|
||||
```c++
|
||||
// The "once" mechanism:
|
||||
constinit absl::Mutex mu(absl::kConstInit);
|
||||
constinit std::atomic<bool> init_done = false;
|
||||
|
||||
// The operation of interest:
|
||||
void f();
|
||||
|
||||
void InitOnceWithFastPath() {
|
||||
if (!init_done.load(std::memory_order_acquire)) {
|
||||
absl::MutexLock lock(&mu);
|
||||
if (!init_done.load(std::memory_order_relaxed)) {
|
||||
f();
|
||||
init_done.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Checking the flag now happens without holding the mutex lock, and if the
|
||||
operation has already completed, we return immediately. After locking the mutex,
|
||||
we need to check the flag again, since multiple threads can reach this point.
|
||||
|
||||
*Atomic details.* Since the atomic flag variable is accessed concurrently, we
|
||||
have to think about the memory order of the accesses. There are two separate
|
||||
cases: The first, outer check outside the mutex lock, and the second, inner
|
||||
check under the lock. The outer check and the flag update form an
|
||||
acquire/release pair: *if* the load sees the value `true` (which must have been
|
||||
written by the store operation), then it also sees everything that happened
|
||||
before the store, namely the operation `f()`. By contrast, the inner check can
|
||||
use relaxed memory ordering, since in that case the mutex operations provide the
|
||||
necessary ordering: if the inner load sees the value `true`, it happened after
|
||||
the `lock()`, which happened after the `unlock()`, which happened after the
|
||||
store.
|
||||
|
||||
The C++ standard library, and Abseil, provide a ready-made solution of this
|
||||
algorithm called `std::call_once`/`absl::call_once`. (The interface is the same,
|
||||
but the Abseil implementation is possibly better.)
|
||||
|
||||
```c++
|
||||
// The "once" mechanism:
|
||||
constinit absl::once_flag init_flag;
|
||||
|
||||
// The operation of interest:
|
||||
void f();
|
||||
|
||||
void InitOnceWithCallOnce() {
|
||||
absl::call_once(once_flag, f);
|
||||
}
|
||||
```
|
||||
|
||||
Even though conceptually this is performing the same algorithm, this
|
||||
implementation has some considerable advantages: The `once_flag` type is a small
|
||||
and trivial, integer-like type and is trivially destructible. Not only does it
|
||||
take up less space than a mutex, it also generates less code since it does not
|
||||
have to run a destructor, which would need to be added to the program's global
|
||||
destructor list.
|
||||
|
||||
The final clou comes with the C++ semantics of a `static` variable declared at
|
||||
block scope: According to [[stmt.dcl]](https://eel.is/c++draft/stmt.dcl#3):
|
||||
|
||||
> Dynamic initialization of a block variable with static storage duration or
|
||||
> thread storage duration is performed the first time control passes through its
|
||||
> declaration; such a variable is considered initialized upon the completion of
|
||||
> its initialization. [...] If control enters the declaration concurrently while
|
||||
> the variable is being initialized, the concurrent execution shall wait for
|
||||
> completion of the initialization.
|
||||
|
||||
This is saying that the initialization of a local, `static` variable precisely
|
||||
has the “once” semantics that we have been discussing. We can
|
||||
therefore write the above example as follows:
|
||||
|
||||
```c++
|
||||
// The operation of interest:
|
||||
void f();
|
||||
|
||||
void InitOnceWithStatic() {
|
||||
static int unused = (f(), 0);
|
||||
}
|
||||
```
|
||||
|
||||
This approach is by far the simplest and easiest, but the big difference is that
|
||||
the mutex (or mutex-like object) in this implementation is no longer visible or
|
||||
in the user’s control. This is perfectly fine if the initializer is
|
||||
simple, but if the initializer itself attempts to lock any other mutex
|
||||
(including by initializing another static variable!), then we have no control
|
||||
over the lock ordering!
|
||||
|
||||
Finally, you may have noticed the `constinit`s around the earlier code. Both
|
||||
`constinit` and `constexpr` specifiers on a declaration mean that the variable
|
||||
is *constant-initialized*, which means that no initialization is performed at
|
||||
runtime (the initial value is already known at compile time). This in turn means
|
||||
that a static variable guard mutex may not be needed, and static initialization
|
||||
never blocks. The difference between the two is that a `constexpr`-specified
|
||||
variable is also `const`, and a variable cannot be `constexpr` if it has a
|
||||
non-trivial destructor. Such a destructor also means that the guard mutex is
|
||||
needed after all, since the destructor must be registered to run at exit,
|
||||
conditionally on initialization having happened.
|
||||
|
||||
## Python, CPython, GIL
|
||||
|
||||
With CPython, a Python program can call into native code. To this end, the
|
||||
native code registers callback functions with the Python runtime via the CPython
|
||||
API. In order to ensure that the internal state of the Python runtime remains
|
||||
consistent, there is a single, shared mutex called the “global interpreter
|
||||
lock”, or GIL for short. Upon entry of one of the user-provided callback
|
||||
functions, the GIL is locked (or “held”), so that no other mutations
|
||||
of the Python runtime state can occur until the native callback returns.
|
||||
|
||||
Many native extensions do not interact with the Python runtime for at least some
|
||||
part of them, and so it is common for native extensions to _release_ the GIL, do
|
||||
some work, and then reacquire the GIL before returning. Similarly, when code is
|
||||
generally not holding the GIL but needs to interact with the runtime briefly, it
|
||||
will first reacquire the GIL. The GIL is reentrant, and constructions to acquire
|
||||
and subsequently release the GIL are common, and often don't worry about whether
|
||||
the GIL is already held.
|
||||
|
||||
If the native code is written in C++ and contains local, `static` variables,
|
||||
then we are now dealing with at least _two_ mutexes: the static variable guard
|
||||
mutex, and the GIL from CPython.
|
||||
|
||||
A common problem in such code is an operation with “only once”
|
||||
semantics that also ends up requiring the GIL to be held at some point. As per
|
||||
the above description of “once”-style techniques, one might find a
|
||||
static variable:
|
||||
|
||||
```c++
|
||||
// CPython callback, assumes that the GIL is held on entry.
|
||||
PyObject* InvokeWidget(PyObject* self) {
|
||||
static PyObject* impl = CreateWidget();
|
||||
return PyObject_CallOneArg(impl, self);
|
||||
}
|
||||
```
|
||||
|
||||
This seems reasonable, but bear in mind that there are two mutexes (the "guard
|
||||
mutex" and "the GIL"), and we must think about the lock order. Otherwise, if the
|
||||
callback is called from multiple threads, a deadlock may ensue.
|
||||
|
||||
Let us consider what we can see here: On entry, the GIL is already locked, and
|
||||
we are locking the guard mutex. This is one lock order. Inside the initializer
|
||||
`CreateWidget`, with both mutexes already locked, the function can freely access
|
||||
the Python runtime.
|
||||
|
||||
However, it is entirely possible that `CreateWidget` will want to release the
|
||||
GIL at one point and reacquire it later:
|
||||
|
||||
```c++
|
||||
// Assumes that the GIL is held on entry.
|
||||
// Ensures that the GIL is held on exit.
|
||||
PyObject* CreateWidget() {
|
||||
// ...
|
||||
Py_BEGIN_ALLOW_THREADS // releases GIL
|
||||
// expensive work, not accessing the Python runtime
|
||||
Py_END_ALLOW_THREADS // acquires GIL, #!
|
||||
// ...
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
Now we have a second lock order: the guard mutex is locked, and then the GIL is
|
||||
locked (at `#!`). To see how this deadlocks, consider threads T1 and T2 both
|
||||
having the runtime attempt to call `InvokeWidget`. T1 locks the GIL and
|
||||
proceeds, locking the guard mutex and calling `CreateWidget`; T2 is blocked
|
||||
waiting for the GIL. Then T1 releases the GIL to do “expensive
|
||||
work”, and T2 awakes and locks the GIL. Now T2 is blocked trying to
|
||||
acquire the guard mutex, but T1 is blocked reacquiring the GIL (at `#!`).
|
||||
|
||||
In other words: if we want to support “once-called” functions that
|
||||
can arbitrarily release and reacquire the GIL, as is very common, then the only
|
||||
lock order that we can ensure is: guard mutex first, GIL second.
|
||||
|
||||
To implement this, we must rewrite our code. Naively, we could always release
|
||||
the GIL before a `static` variable with blocking initializer:
|
||||
|
||||
```c++
|
||||
// CPython callback, assumes that the GIL is held on entry.
|
||||
PyObject* InvokeWidget(PyObject* self) {
|
||||
Py_BEGIN_ALLOW_THREADS // releases GIL
|
||||
static PyObject* impl = CreateWidget();
|
||||
Py_END_ALLOW_THREADS // acquires GIL
|
||||
|
||||
return PyObject_CallOneArg(impl, self);
|
||||
}
|
||||
```
|
||||
|
||||
But similar to the `InitOnceNaive` example above, this code cycles the GIL
|
||||
(possibly descheduling the thread) even when the static variable has already
|
||||
been initialized. If we want to avoid this, we need to abandon the use of a
|
||||
static variable, since we do not control the guard mutex well enough. Instead,
|
||||
we use an operation whose mutex locking is under our control, such as
|
||||
`call_once`. For example:
|
||||
|
||||
```c++
|
||||
// CPython callback, assumes that the GIL is held on entry.
|
||||
PyObject* InvokeWidget(PyObject* self) {
|
||||
static constinit PyObject* impl = nullptr;
|
||||
static constinit std::atomic<bool> init_done = false;
|
||||
static constinit absl::once_flag init_flag;
|
||||
|
||||
if (!init_done.load(std::memory_order_acquire)) {
|
||||
Py_BEGIN_ALLOW_THREADS // releases GIL
|
||||
absl::call_once(init_flag, [&]() {
|
||||
PyGILState_STATE s = PyGILState_Ensure(); // acquires GIL
|
||||
impl = CreateWidget();
|
||||
PyGILState_Release(s); // releases GIL
|
||||
init_done.store(true, std::memory_order_release);
|
||||
});
|
||||
Py_END_ALLOW_THREADS // acquires GIL
|
||||
}
|
||||
|
||||
return PyObject_CallOneArg(impl, self);
|
||||
}
|
||||
```
|
||||
|
||||
The lock order is now always guard mutex first, GIL second. Unfortunately we
|
||||
have to duplicate the “double-checked done flag”, effectively
|
||||
leading to triple checking, because the flag state inside the `absl::once_flag`
|
||||
is not accessible to the user. In other words, we cannot ask `init_flag` whether
|
||||
it has been used yet.
|
||||
|
||||
However, we can perform one last, minor optimisation: since we assume that the
|
||||
GIL is held on entry, and again when the initializing operation returns, the GIL
|
||||
actually serializes access to our done flag variable, which therefore does not
|
||||
need to be atomic. (The difference to the previous, atomic code may be small,
|
||||
depending on the architecture. For example, on x86-64, acquire/release on a bool
|
||||
is nearly free ([demo](https://godbolt.org/z/P9vYWf4fE)).)
|
||||
|
||||
```c++
|
||||
// CPython callback, assumes that the GIL is held on entry, and indeed anywhere
|
||||
// directly in this function (i.e. the GIL can be released inside CreateWidget,
|
||||
// but must be reaqcuired when that call returns).
|
||||
PyObject* InvokeWidget(PyObject* self) {
|
||||
static constinit PyObject* impl = nullptr;
|
||||
static constinit bool init_done = false; // guarded by GIL
|
||||
static constinit absl::once_flag init_flag;
|
||||
|
||||
if (!init_done) {
|
||||
Py_BEGIN_ALLOW_THREADS // releases GIL
|
||||
// (multiple threads may enter here)
|
||||
absl::call_once(init_flag, [&]() {
|
||||
// (only one thread enters here)
|
||||
PyGILState_STATE s = PyGILState_Ensure(); // acquires GIL
|
||||
impl = CreateWidget();
|
||||
init_done = true; // (GIL is held)
|
||||
PyGILState_Release(s); // releases GIL
|
||||
});
|
||||
|
||||
Py_END_ALLOW_THREADS // acquires GIL
|
||||
}
|
||||
|
||||
return PyObject_CallOneArg(impl, self);
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging tips
|
||||
|
||||
* Build with symbols.
|
||||
* <kbd>Ctrl</kbd>-<kbd>C</kbd> sends `SIGINT`, <kbd>Ctrl</kbd>-<kbd>\\</kbd>
|
||||
sends `SIGQUIT`. Both have their uses.
|
||||
* Useful `gdb` commands:
|
||||
* `py-bt` prints a Python backtrace if you are in a Python frame.
|
||||
* `thread apply all bt 10` prints the top-10 frames for each thread. A
|
||||
full backtrace can be prohibitively expensive, and the top few frames
|
||||
are often good enough.
|
||||
* `p PyGILState_Check()` shows whether a thread is holding the GIL. For
|
||||
all threads, run `thread apply all p PyGILState_Check()` to find out
|
||||
which thread is holding the GIL.
|
||||
* The `static` variable guard mutex is accessed with functions like
|
||||
`cxa_guard_acquire` (though this depends on ABI details and can vary).
|
||||
The guard mutex itself contains information about which thread is
|
||||
currently holding it.
|
||||
|
||||
## Links
|
||||
|
||||
* Article on
|
||||
[double-checked locking](https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/)
|
||||
* [The Deadlock Empire](https://deadlockempire.github.io/), hands-on exercises
|
||||
to construct deadlocks
|
|
@ -18,7 +18,7 @@ information, see :doc:`/compiling`.
|
|||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.5...3.29)
|
||||
project(example)
|
||||
|
||||
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`
|
||||
|
|
|
@ -368,7 +368,8 @@ Should they throw or fail to catch any exceptions in their call graph,
|
|||
the C++ runtime calls ``std::terminate()`` to abort immediately.
|
||||
|
||||
Similarly, Python exceptions raised in a class's ``__del__`` method do not
|
||||
propagate, but ``sys.unraisablehook()`` `is triggered
|
||||
propagate, but are logged by Python as an unraisable error. In Python 3.8+, a
|
||||
`system hook is triggered
|
||||
<https://docs.python.org/3/library/sys.html#sys.unraisablehook>`_
|
||||
and an auditing event is logged.
|
||||
|
||||
|
|
|
@ -81,11 +81,9 @@ The following table provides an overview of available policies:
|
|||
| | it is no longer used. Warning: undefined behavior will ensue when the C++ |
|
||||
| | side deletes an object that is still referenced and used by Python. |
|
||||
+--------------------------------------------------+----------------------------------------------------------------------------+
|
||||
| :enum:`return_value_policy::reference_internal` | If the return value is an lvalue reference or a pointer, the parent object |
|
||||
| | (the implicit ``this``, or ``self`` argument of the called method or |
|
||||
| | property) is kept alive for at least the lifespan of the return value. |
|
||||
| | **Otherwise this policy falls back to :enum:`return_value_policy::move` |
|
||||
| | (see #5528).** Internally, this policy works just like |
|
||||
| :enum:`return_value_policy::reference_internal` | Indicates that the lifetime of the return value is tied to the lifetime |
|
||||
| | of a parent object, namely the implicit ``this``, or ``self`` argument of |
|
||||
| | the called method or property. Internally, this policy works just like |
|
||||
| | :enum:`return_value_policy::reference` but additionally applies a |
|
||||
| | ``keep_alive<0, 1>`` *call policy* (described in the next section) that |
|
||||
| | prevents the parent object from being garbage collected as long as the |
|
||||
|
|
|
@ -62,11 +62,7 @@ will acquire the GIL before calling the Python callback. Similarly, the
|
|||
back into Python.
|
||||
|
||||
When writing C++ code that is called from other C++ code, if that code accesses
|
||||
Python state, it must explicitly acquire and release the GIL. A separate
|
||||
document on deadlocks [#f8]_ elaborates on a particularly subtle interaction
|
||||
with C++'s block-scope static variable initializer guard mutexes.
|
||||
|
||||
.. [#f8] See docs/advanced/deadlock.md
|
||||
Python state, it must explicitly acquire and release the GIL.
|
||||
|
||||
The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be
|
||||
used to acquire and release the global interpreter lock in the body of a C++
|
||||
|
@ -80,7 +76,7 @@ could be realized as follows (important changes highlighted):
|
|||
.. code-block:: cpp
|
||||
:emphasize-lines: 8,30,31
|
||||
|
||||
class PyAnimal : public Animal, py::trampoline_self_life_support {
|
||||
class PyAnimal : public Animal {
|
||||
public:
|
||||
/* Inherit the constructors */
|
||||
using Animal::Animal;
|
||||
|
@ -98,12 +94,12 @@ could be realized as follows (important changes highlighted):
|
|||
};
|
||||
|
||||
PYBIND11_MODULE(example, m) {
|
||||
py::class_<Animal, PyAnimal, py::smart_holder> animal(m, "Animal");
|
||||
py::class_<Animal, PyAnimal> animal(m, "Animal");
|
||||
animal
|
||||
.def(py::init<>())
|
||||
.def("go", &Animal::go);
|
||||
|
||||
py::class_<Dog, py::smart_holder>(m, "Dog", animal)
|
||||
py::class_<Dog>(m, "Dog", animal)
|
||||
.def(py::init<>());
|
||||
|
||||
m.def("call_go", [](Animal *animal) -> std::string {
|
||||
|
@ -146,9 +142,6 @@ following checklist.
|
|||
destructors can sometimes get invoked in weird and unexpected circumstances as a result
|
||||
of exceptions.
|
||||
|
||||
- C++ static block-scope variable initialization that calls back into Python can
|
||||
cause deadlocks; see [#f8]_ for a detailed discussion.
|
||||
|
||||
- You should try running your code in a debug build. That will enable additional assertions
|
||||
within pybind11 that will throw exceptions on certain GIL handling errors
|
||||
(reference counting operations).
|
||||
|
@ -188,7 +181,7 @@ from Section :ref:`inheritance`.
|
|||
Suppose now that ``Pet`` bindings are defined in a module named ``basic``,
|
||||
whereas the ``Dog`` bindings are defined somewhere else. The challenge is of
|
||||
course that the variable ``pet`` is not available anymore though it is needed
|
||||
to indicate the inheritance relationship to the constructor of ``py::class_<Dog>``.
|
||||
to indicate the inheritance relationship to the constructor of ``class_<Dog>``.
|
||||
However, it can be acquired as follows:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
@ -200,7 +193,7 @@ However, it can be acquired as follows:
|
|||
.def("bark", &Dog::bark);
|
||||
|
||||
Alternatively, you can specify the base class as a template parameter option to
|
||||
``py::class_``, which performs an automated lookup of the corresponding Python
|
||||
``class_``, which performs an automated lookup of the corresponding Python
|
||||
type. Like the above code, however, this also requires invoking the ``import``
|
||||
function once to ensure that the pybind11 binding code of the module ``basic``
|
||||
has been executed:
|
||||
|
|
|
@ -1,70 +1,11 @@
|
|||
Smart pointers & ``py::class_``
|
||||
###############################
|
||||
Smart pointers
|
||||
##############
|
||||
|
||||
The binding generator for classes, ``py::class_``, can be passed a template
|
||||
type that denotes a special *holder* type that is used to manage references to
|
||||
the object. If no such holder type template argument is given, the default for
|
||||
a type ``T`` is ``std::unique_ptr<T>``.
|
||||
std::unique_ptr
|
||||
===============
|
||||
|
||||
.. note::
|
||||
|
||||
A ``py::class_`` for a given C++ type ``T`` — and all its derived types —
|
||||
can only use a single holder type.
|
||||
|
||||
|
||||
.. _smart_holder:
|
||||
|
||||
``py::smart_holder``
|
||||
====================
|
||||
|
||||
Starting with pybind11v3, ``py::smart_holder`` is built into pybind11. It is
|
||||
the recommended ``py::class_`` holder for most situations. However, for
|
||||
backward compatibility it is **not** the default holder, and there are no
|
||||
plans to make it the default holder in the future.
|
||||
|
||||
It is extremely easy to use the safer and more versatile ``py::smart_holder``:
|
||||
simply add ``py::smart_holder`` to ``py::class_``:
|
||||
|
||||
* ``py::class_<T>`` to
|
||||
|
||||
* ``py::class_<T, py::smart_holder>``.
|
||||
|
||||
.. note::
|
||||
|
||||
A shorthand, ``py::classh<T>``, is provided for
|
||||
``py::class_<T, py::smart_holder>``. The ``h`` in ``py::classh`` stands
|
||||
for **smart_holder** but is shortened for brevity, ensuring it has the
|
||||
same number of characters as ``py::class_``. This design choice facilitates
|
||||
easy experimentation with ``py::smart_holder`` without introducing
|
||||
distracting whitespace noise in diffs.
|
||||
|
||||
The ``py::smart_holder`` functionality includes the following:
|
||||
|
||||
* Support for **two-way** Python/C++ conversions for both
|
||||
``std::unique_ptr<T>`` and ``std::shared_ptr<T>`` **simultaneously**.
|
||||
|
||||
* Passing a Python object back to C++ via ``std::unique_ptr<T>``, safely
|
||||
**disowning** the Python object.
|
||||
|
||||
* Safely passing "trampoline" objects (objects with C++ virtual function
|
||||
overrides implemented in Python, see :ref:`overriding_virtuals`) via
|
||||
``std::unique_ptr<T>`` or ``std::shared_ptr<T>`` back to C++:
|
||||
associated Python objects are automatically kept alive for the lifetime
|
||||
of the smart-pointer.
|
||||
|
||||
* Full support for ``std::enable_shared_from_this`` (`cppreference
|
||||
<http://en.cppreference.com/w/cpp/memory/enable_shared_from_this>`_).
|
||||
|
||||
|
||||
``std::unique_ptr``
|
||||
===================
|
||||
|
||||
This is the default ``py::class_`` holder and works as expected in
|
||||
most situations. However, handling base-and-derived classes involves a
|
||||
``reinterpret_cast``, which is, strictly speaking, undefined behavior.
|
||||
Also note that the ``std::unique_ptr`` holder only supports passing a
|
||||
``std::unique_ptr`` from C++ to Python, but not the other way around.
|
||||
For example, the following code works as expected with ``py::class_<Example>``:
|
||||
Given a class ``Example`` with Python bindings, it's possible to return
|
||||
instances wrapped in C++11 unique pointers, like so
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
@ -74,50 +15,112 @@ For example, the following code works as expected with ``py::class_<Example>``:
|
|||
|
||||
m.def("create_example", &create_example);
|
||||
|
||||
However, this will fail with ``py::class_<Example>`` (but works with
|
||||
``py::class_<Example, py::smart_holder>``):
|
||||
In other words, there is nothing special that needs to be done. While returning
|
||||
unique pointers in this way is allowed, it is *illegal* to use them as function
|
||||
arguments. For instance, the following function signature cannot be processed
|
||||
by pybind11.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void do_something_with_example(std::unique_ptr<Example> ex) { ... }
|
||||
|
||||
.. note::
|
||||
The above signature would imply that Python needs to give up ownership of an
|
||||
object that is passed to this function, which is generally not possible (for
|
||||
instance, the object might be referenced elsewhere).
|
||||
|
||||
The ``reinterpret_cast`` mentioned above is `here
|
||||
<https://github.com/pybind/pybind11/blob/30eb39ed79d1e2eeff15219ac00773034300a5e6/include/pybind11/cast.h#L235>`_.
|
||||
For completeness: The same cast is also applied to ``py::smart_holder``,
|
||||
but that is safe, because ``py::smart_holder`` is not templated.
|
||||
std::shared_ptr
|
||||
===============
|
||||
|
||||
The binding generator for classes, :class:`class_`, can be passed a template
|
||||
type that denotes a special *holder* type that is used to manage references to
|
||||
the object. If no such holder type template argument is given, the default for
|
||||
a type named ``Type`` is ``std::unique_ptr<Type>``, which means that the object
|
||||
is deallocated when Python's reference count goes to zero.
|
||||
|
||||
``std::shared_ptr``
|
||||
===================
|
||||
|
||||
It is possible to use ``std::shared_ptr`` as the holder, for example:
|
||||
It is possible to switch to other types of reference counting wrappers or smart
|
||||
pointers, which is useful in codebases that rely on them. For instance, the
|
||||
following snippet causes ``std::shared_ptr`` to be used instead.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
py::class_<Example, std::shared_ptr<Example> /* <- holder type */>(m, "Example");
|
||||
py::class_<Example, std::shared_ptr<Example> /* <- holder type */> obj(m, "Example");
|
||||
|
||||
Compared to using ``py::class_<Example, py::smart_holder>``, there are two noteworthy disadvantages:
|
||||
Note that any particular class can only be associated with a single holder type.
|
||||
|
||||
* Because a ``py::class_`` for a given C++ type ``T`` can only use a
|
||||
single holder type, ``std::unique_ptr<T>`` cannot even be passed from C++
|
||||
to Python. This will become apparent only at runtime, often through a
|
||||
segmentation fault.
|
||||
One potential stumbling block when using holder types is that they need to be
|
||||
applied consistently. Can you guess what's broken about the following binding
|
||||
code?
|
||||
|
||||
* Similar to the ``std::unique_ptr`` holder, the handling of base-and-derived
|
||||
classes involves a ``reinterpret_cast`` that has strictly speaking undefined
|
||||
behavior, although it works as expected in most situations.
|
||||
.. code-block:: cpp
|
||||
|
||||
class Child { };
|
||||
|
||||
class Parent {
|
||||
public:
|
||||
Parent() : child(std::make_shared<Child>()) { }
|
||||
Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */
|
||||
private:
|
||||
std::shared_ptr<Child> child;
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(example, m) {
|
||||
py::class_<Child, std::shared_ptr<Child>>(m, "Child");
|
||||
|
||||
py::class_<Parent, std::shared_ptr<Parent>>(m, "Parent")
|
||||
.def(py::init<>())
|
||||
.def("get_child", &Parent::get_child);
|
||||
}
|
||||
|
||||
The following Python code will cause undefined behavior (and likely a
|
||||
segmentation fault).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from example import Parent
|
||||
|
||||
print(Parent().get_child())
|
||||
|
||||
The problem is that ``Parent::get_child()`` returns a pointer to an instance of
|
||||
``Child``, but the fact that this instance is already managed by
|
||||
``std::shared_ptr<...>`` is lost when passing raw pointers. In this case,
|
||||
pybind11 will create a second independent ``std::shared_ptr<...>`` that also
|
||||
claims ownership of the pointer. In the end, the object will be freed **twice**
|
||||
since these shared pointers have no way of knowing about each other.
|
||||
|
||||
There are two ways to resolve this issue:
|
||||
|
||||
1. For types that are managed by a smart pointer class, never use raw pointers
|
||||
in function arguments or return values. In other words: always consistently
|
||||
wrap pointers into their designated holder types (such as
|
||||
``std::shared_ptr<...>``). In this case, the signature of ``get_child()``
|
||||
should be modified as follows:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
std::shared_ptr<Child> get_child() { return child; }
|
||||
|
||||
2. Adjust the definition of ``Child`` by specifying
|
||||
``std::enable_shared_from_this<T>`` (see cppreference_ for details) as a
|
||||
base class. This adds a small bit of information to ``Child`` that allows
|
||||
pybind11 to realize that there is already an existing
|
||||
``std::shared_ptr<...>`` and communicate with it. In this case, the
|
||||
declaration of ``Child`` should look as follows:
|
||||
|
||||
.. _cppreference: http://en.cppreference.com/w/cpp/memory/enable_shared_from_this
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
class Child : public std::enable_shared_from_this<Child> { };
|
||||
|
||||
.. _smart_pointers:
|
||||
|
||||
Custom smart pointers
|
||||
=====================
|
||||
|
||||
For custom smart pointers (e.g. ``c10::intrusive_ptr`` in pytorch), transparent
|
||||
conversions can be enabled using a macro invocation similar to the following.
|
||||
It must be declared at the top namespace level before any binding code:
|
||||
pybind11 supports ``std::unique_ptr`` and ``std::shared_ptr`` right out of the
|
||||
box. For any other custom smart pointer, transparent conversions can be enabled
|
||||
using a macro invocation similar to the following. It must be declared at the
|
||||
top namespace level before any binding code:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
@ -164,70 +167,8 @@ specialized:
|
|||
The above specialization informs pybind11 that the custom ``SmartPtr`` class
|
||||
provides ``.get()`` functionality via ``.getPointer()``.
|
||||
|
||||
.. note::
|
||||
|
||||
The two noteworthy disadvantages mentioned under the ``std::shared_ptr``
|
||||
section apply similarly to custom smart pointer holders, but there is no
|
||||
established safe alternative in this case.
|
||||
|
||||
.. seealso::
|
||||
|
||||
The file :file:`tests/test_smart_ptr.cpp` contains a complete example
|
||||
that demonstrates how to work with custom reference-counting holder types
|
||||
in more detail.
|
||||
|
||||
|
||||
Be careful not to accidentally undermine automatic lifetime management
|
||||
======================================================================
|
||||
|
||||
``py::class_``-wrapped objects automatically manage the lifetime of the
|
||||
wrapped C++ object, in collaboration with the chosen holder type.
|
||||
When wrapping C++ functions involving raw pointers, care needs to be taken
|
||||
to not inadvertently transfer ownership, resulting in multiple Python
|
||||
objects acting as owners, causing heap-use-after-free or double-free errors.
|
||||
For example:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
class Child { };
|
||||
|
||||
class Parent {
|
||||
public:
|
||||
Parent() : child(std::make_shared<Child>()) { }
|
||||
Child *get_child() { return child.get(); } /* DANGER */
|
||||
private:
|
||||
std::shared_ptr<Child> child;
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(example, m) {
|
||||
py::class_<Child, std::shared_ptr<Child>>(m, "Child");
|
||||
|
||||
py::class_<Parent, std::shared_ptr<Parent>>(m, "Parent")
|
||||
.def(py::init<>())
|
||||
.def("get_child", &Parent::get_child); /* PROBLEM */
|
||||
}
|
||||
|
||||
The following Python code leads to undefined behavior, likely resulting in
|
||||
a segmentation fault.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from example import Parent
|
||||
|
||||
print(Parent().get_child())
|
||||
|
||||
Part of the ``/* PROBLEM */`` here is that pybind11 falls back to using
|
||||
``return_value_policy::take_ownership`` as the default (see
|
||||
:ref:`return_value_policies`). The fact that the ``Child`` instance is
|
||||
already managed by ``std::shared_ptr<Child>`` is lost. Therefore pybind11
|
||||
will create a second independent ``std::shared_ptr<Child>`` that also
|
||||
claims ownership of the pointer, eventually leading to heap-use-after-free
|
||||
or double-free errors.
|
||||
|
||||
There are various ways to resolve this issue, either by changing
|
||||
the ``Child`` or ``Parent`` C++ implementations (e.g. using
|
||||
``std::enable_shared_from_this<Child>`` as a base class for
|
||||
``Child``, or adding a member function to ``Parent`` that returns
|
||||
``std::shared_ptr<Child>``), or if that is not feasible, by using
|
||||
``return_value_policy::reference_internal``. What is the best approach
|
||||
depends on the exact situation.
|
||||
|
|
|
@ -142,7 +142,7 @@ On Linux, the above example can be compiled using the following command:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
$ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3 -m pybind11 --extension-suffix)
|
||||
$ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ def generate_dummy_code_boost(nclasses=10):
|
|||
decl += "\n"
|
||||
|
||||
for cl in range(nclasses):
|
||||
decl += f"class cl{cl:03} {{\n"
|
||||
decl += "class cl%03i {\n" % cl
|
||||
decl += "public:\n"
|
||||
bindings += f' py::class_<cl{cl:03}>("cl{cl:03}")\n'
|
||||
for fn in range(nfns):
|
||||
|
@ -85,5 +85,5 @@ for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]:
|
|||
n2 = dt.datetime.now()
|
||||
elapsed = (n2 - n1).total_seconds()
|
||||
size = os.stat("test.so").st_size
|
||||
print(f" {{{nclasses * nfns}, {elapsed:.6f}, {size}}},")
|
||||
print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size))
|
||||
print("}")
|
||||
|
|
|
@ -34,18 +34,11 @@ The binding code for ``Pet`` looks as follows:
|
|||
.def("getName", &Pet::getName);
|
||||
}
|
||||
|
||||
``py::class_`` creates bindings for a C++ *class* or *struct*-style data
|
||||
:class:`class_` creates bindings for a C++ *class* or *struct*-style data
|
||||
structure. :func:`init` is a convenience function that takes the types of a
|
||||
constructor's parameters as template arguments and wraps the corresponding
|
||||
constructor (see the :ref:`custom_constructors` section for details).
|
||||
|
||||
.. note::
|
||||
|
||||
Starting with pybind11v3, it is recommended to include `py::smart_holder`
|
||||
in most situations for safety, especially if you plan to support conversions
|
||||
to C++ smart pointers. See :ref:`smart_holder` for more information.
|
||||
|
||||
An interactive Python session demonstrating this example is shown below:
|
||||
constructor (see the :ref:`custom_constructors` section for details). An
|
||||
interactive Python session demonstrating this example is shown below:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
|
@ -265,7 +258,7 @@ inheritance relationship:
|
|||
|
||||
There are two different ways of indicating a hierarchical relationship to
|
||||
pybind11: the first specifies the C++ base class as an extra template
|
||||
parameter of the ``py::class_``:
|
||||
parameter of the :class:`class_`:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
@ -279,7 +272,7 @@ parameter of the ``py::class_``:
|
|||
.def("bark", &Dog::bark);
|
||||
|
||||
Alternatively, we can also assign a name to the previously bound ``Pet``
|
||||
``py::class_`` object and reference it when binding the ``Dog`` class:
|
||||
:class:`class_` object and reference it when binding the ``Dog`` class:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
|
@ -505,7 +498,7 @@ The binding code for this example looks as follows:
|
|||
|
||||
|
||||
To ensure that the nested types ``Kind`` and ``Attributes`` are created within the scope of ``Pet``, the
|
||||
``pet`` ``py::class_`` instance must be supplied to the :class:`enum_` and ``py::class_``
|
||||
``pet`` :class:`class_` instance must be supplied to the :class:`enum_` and :class:`class_`
|
||||
constructor. The :func:`enum_::export_values` function exports the enum entries
|
||||
into the parent scope, which should be skipped for newer C++11-style strongly
|
||||
typed enums.
|
||||
|
|
|
@ -18,7 +18,7 @@ A Python extension module can be created with just a few lines of code:
|
|||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.15...3.29)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
set(PYBIND11_FINDPYTHON ON)
|
||||
|
@ -319,11 +319,11 @@ Building with CMake
|
|||
|
||||
For C++ codebases that have an existing CMake-based build system, a Python
|
||||
extension module can be created with just a few lines of code, as seen above in
|
||||
the module section. Pybind11 currently defaults to the old mechanism, though be
|
||||
aware that CMake 3.27 removed the old mechanism, so pybind11 will automatically
|
||||
switch if the old mechanism is not available. Please opt into the new mechanism
|
||||
if at all possible. Our default may change in future versions. This is the
|
||||
minimum required:
|
||||
the module section. Pybind11 currently supports a lower minimum if you don't
|
||||
use the modern FindPython, though be aware that CMake 3.27 removed the old
|
||||
mechanism, so pybind11 will automatically switch if the old mechanism is not
|
||||
available. Please opt into the new mechanism if at all possible. Our default
|
||||
may change in future versions. This is the minimum required:
|
||||
|
||||
|
||||
|
||||
|
@ -333,9 +333,6 @@ minimum required:
|
|||
.. versionchanged:: 2.11
|
||||
CMake 3.5+ is required.
|
||||
|
||||
.. versionchanged:: 2.14
|
||||
CMake 3.15+ is required.
|
||||
|
||||
|
||||
Further information can be found at :doc:`cmake/index`.
|
||||
|
||||
|
@ -429,7 +426,7 @@ with ``PYTHON_EXECUTABLE``. For example:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
cmake -DPYBIND11_PYTHON_VERSION=3.8 ..
|
||||
cmake -DPYBIND11_PYTHON_VERSION=3.7 ..
|
||||
|
||||
# Another method:
|
||||
cmake -DPYTHON_EXECUTABLE=/path/to/python ..
|
||||
|
@ -447,7 +444,7 @@ See the `Config file`_ docstring for details of relevant CMake variables.
|
|||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.4...3.18)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(pybind11 REQUIRED)
|
||||
|
@ -486,16 +483,17 @@ can refer to the same [cmake_example]_ repository for a full sample project
|
|||
FindPython mode
|
||||
---------------
|
||||
|
||||
Modern CMake (3.18.2+ ideal) added a new module called FindPython that had a
|
||||
highly improved search algorithm and modern targets and tools. If you use
|
||||
FindPython, pybind11 will detect this and use the existing targets instead:
|
||||
CMake 3.12+ (3.15+ recommended, 3.18.2+ ideal) added a new module called
|
||||
FindPython that had a highly improved search algorithm and modern targets
|
||||
and tools. If you use FindPython, pybind11 will detect this and use the
|
||||
existing targets instead:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.15...3.22)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED)
|
||||
find_package(Python 3.7 COMPONENTS Interpreter Development REQUIRED)
|
||||
find_package(pybind11 CONFIG REQUIRED)
|
||||
# or add_subdirectory(pybind11)
|
||||
|
||||
|
@ -543,7 +541,7 @@ available in all modes. The targets provided are:
|
|||
Just the "linking" part of pybind11:module
|
||||
|
||||
``pybind11::module``
|
||||
Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython) or ``pybind11::python_link_helper``
|
||||
Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython CMake 3.15+) or ``pybind11::python_link_helper``
|
||||
|
||||
``pybind11::embed``
|
||||
Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Python`` (FindPython) or Python libs
|
||||
|
@ -570,7 +568,7 @@ You can use these targets to build complex applications. For example, the
|
|||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.5...3.29)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
||||
|
@ -628,7 +626,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
|
|||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.5...3.29)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
||||
|
|
|
@ -302,9 +302,9 @@ CMake configure line. (Replace ``$(which python)`` with a path to python if
|
|||
your prefer.)
|
||||
|
||||
You can alternatively try ``-DPYBIND11_FINDPYTHON=ON``, which will activate the
|
||||
new CMake FindPython support instead of pybind11's custom search. Newer CMake,
|
||||
like, 3.18.2+, is recommended. You can set this in your ``CMakeLists.txt``
|
||||
before adding or finding pybind11, as well.
|
||||
new CMake FindPython support instead of pybind11's custom search. Requires
|
||||
CMake 3.12+, and 3.15+ or 3.18.2+ are even better. You can set this in your
|
||||
``CMakeLists.txt`` before adding or finding pybind11, as well.
|
||||
|
||||
Inconsistent detection of Python version in CMake and pybind11
|
||||
==============================================================
|
||||
|
@ -325,11 +325,11 @@ There are three possible solutions:
|
|||
from CMake and rely on pybind11 in detecting Python version. If this is not
|
||||
possible, the CMake machinery should be called *before* including pybind11.
|
||||
2. Set ``PYBIND11_FINDPYTHON`` to ``True`` or use ``find_package(Python
|
||||
COMPONENTS Interpreter Development)`` on modern CMake ( 3.18.2+ best).
|
||||
Pybind11 in these cases uses the new CMake FindPython instead of the old,
|
||||
deprecated search tools, and these modules are much better at finding the
|
||||
correct Python. If FindPythonLibs/Interp are not available (CMake 3.27+),
|
||||
then this will be ignored and FindPython will be used.
|
||||
COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better,
|
||||
3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead
|
||||
of the old, deprecated search tools, and these modules are much better at
|
||||
finding the correct Python. If FindPythonLibs/Interp are not available
|
||||
(CMake 3.27+), then this will be ignored and FindPython will be used.
|
||||
3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python.
|
||||
However, you will have to use the target-based system, and do more setup
|
||||
yourself, because it does not know about or include things that depend on
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
.. _installing:
|
||||
|
||||
Installing the library
|
||||
######################
|
||||
|
||||
There are several ways to get the pybind11 source, which lives at
|
||||
`pybind/pybind11 on GitHub <https://github.com/pybind/pybind11>`_. The pybind11
|
||||
developers recommend one of the first three ways listed here, submodule, PyPI,
|
||||
or conda-forge, for obtaining pybind11.
|
||||
|
||||
.. _include_as_a_submodule:
|
||||
|
||||
Include as a submodule
|
||||
======================
|
||||
|
||||
When you are working on a project in Git, you can use the pybind11 repository
|
||||
as a submodule. From your git repository, use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git submodule add -b stable ../../pybind/pybind11 extern/pybind11
|
||||
git submodule update --init
|
||||
|
||||
This assumes you are placing your dependencies in ``extern/``, and that you are
|
||||
using GitHub; if you are not using GitHub, use the full https or ssh URL
|
||||
instead of the relative URL ``../../pybind/pybind11`` above. Some other servers
|
||||
also require the ``.git`` extension (GitHub does not).
|
||||
|
||||
From here, you can now include ``extern/pybind11/include``, or you can use
|
||||
the various integration tools (see :ref:`compiling`) pybind11 provides directly
|
||||
from the local folder.
|
||||
|
||||
Include with PyPI
|
||||
=================
|
||||
|
||||
You can download the sources and CMake files as a Python package from PyPI
|
||||
using Pip. Just use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install pybind11
|
||||
|
||||
This will provide pybind11 in a standard Python package format. If you want
|
||||
pybind11 available directly in your environment root, you can use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install "pybind11[global]"
|
||||
|
||||
This is not recommended if you are installing with your system Python, as it
|
||||
will add files to ``/usr/local/include/pybind11`` and
|
||||
``/usr/local/share/cmake/pybind11``, so unless that is what you want, it is
|
||||
recommended only for use in virtual environments or your ``pyproject.toml``
|
||||
file (see :ref:`compiling`).
|
||||
|
||||
Include with conda-forge
|
||||
========================
|
||||
|
||||
You can use pybind11 with conda packaging via `conda-forge
|
||||
<https://github.com/conda-forge/pybind11-feedstock>`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
conda install -c conda-forge pybind11
|
||||
|
||||
|
||||
Include with vcpkg
|
||||
==================
|
||||
You can download and install pybind11 using the Microsoft `vcpkg
|
||||
<https://github.com/Microsoft/vcpkg/>`_ dependency manager:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/Microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
./bootstrap-vcpkg.sh
|
||||
./vcpkg integrate install
|
||||
vcpkg install pybind11
|
||||
|
||||
The pybind11 port in vcpkg is kept up to date by Microsoft team members and
|
||||
community contributors. If the version is out of date, please `create an issue
|
||||
or pull request <https://github.com/Microsoft/vcpkg/>`_ on the vcpkg
|
||||
repository.
|
||||
|
||||
Global install with brew
|
||||
========================
|
||||
|
||||
The brew package manager (Homebrew on macOS, or Linuxbrew on Linux) has a
|
||||
`pybind11 package
|
||||
<https://github.com/Homebrew/homebrew-core/blob/master/Formula/pybind11.rb>`_.
|
||||
To install:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
brew install pybind11
|
||||
|
||||
.. We should list Conan, and possibly a few other C++ package managers (hunter,
|
||||
.. perhaps). Conan has a very clean CMake integration that would be good to show.
|
||||
|
||||
Other options
|
||||
=============
|
||||
|
||||
Other locations you can find pybind11 are `listed here
|
||||
<https://repology.org/project/python:pybind11/versions>`_; these are maintained
|
||||
by various packagers and the community.
|
|
@ -68,8 +68,8 @@ Convenience functions converting to Python types
|
|||
|
||||
.. _extras:
|
||||
|
||||
Passing extra arguments to ``def`` or ``py::class_``
|
||||
====================================================
|
||||
Passing extra arguments to ``def`` or ``class_``
|
||||
================================================
|
||||
|
||||
.. doxygengroup:: annotations
|
||||
:members:
|
||||
|
|
|
@ -130,9 +130,9 @@ imagesize==1.4.1 \
|
|||
--hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
|
||||
--hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
|
||||
# via sphinx
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
jinja2==3.1.4 \
|
||||
--hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \
|
||||
--hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d
|
||||
# via sphinx
|
||||
markupsafe==2.1.5 \
|
||||
--hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \
|
||||
|
|
|
@ -24,8 +24,7 @@ changes are that:
|
|||
function is not available anymore.
|
||||
|
||||
Due to NumPy changes, you may experience difficulties updating to NumPy 2.
|
||||
Please see the `NumPy 2 migration guide <https://numpy.org/devdocs/numpy_2_0_migration_guide.html>`_
|
||||
for details.
|
||||
Please see the [NumPy 2 migration guide](https://numpy.org/devdocs/numpy_2_0_migration_guide.html) for details.
|
||||
For example, a more direct change could be that the default integer ``"int_"``
|
||||
(and ``"uint"``) is now ``ssize_t`` and not ``long`` (affects 64bit windows).
|
||||
|
||||
|
|
|
@ -81,10 +81,6 @@ struct dynamic_attr {};
|
|||
/// Annotation which enables the buffer protocol for a type
|
||||
struct buffer_protocol {};
|
||||
|
||||
/// Annotation which enables releasing the GIL before calling the C++ destructor of wrapped
|
||||
/// instances (pybind/pybind11#1446).
|
||||
struct release_gil_before_calling_cpp_dtor {};
|
||||
|
||||
/// Annotation which requests that a special metaclass is created for a type
|
||||
struct metaclass {
|
||||
handle value;
|
||||
|
@ -276,7 +272,7 @@ struct function_record {
|
|||
struct type_record {
|
||||
PYBIND11_NOINLINE type_record()
|
||||
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
|
||||
module_local(false), is_final(false), release_gil_before_calling_cpp_dtor(false) {}
|
||||
default_holder(true), module_local(false), is_final(false) {}
|
||||
|
||||
/// Handle to the parent scope
|
||||
handle scope;
|
||||
|
@ -326,17 +322,15 @@ struct type_record {
|
|||
/// Does the class implement the buffer protocol?
|
||||
bool buffer_protocol : 1;
|
||||
|
||||
/// Is the default (unique_ptr) holder type used?
|
||||
bool default_holder : 1;
|
||||
|
||||
/// Is the class definition local to the module shared object?
|
||||
bool module_local : 1;
|
||||
|
||||
/// Is the class inheritable from python classes?
|
||||
bool is_final : 1;
|
||||
|
||||
/// Solves pybind/pybind11#1446
|
||||
bool release_gil_before_calling_cpp_dtor : 1;
|
||||
|
||||
holder_enum_t holder_enum_v = holder_enum_t::undefined;
|
||||
|
||||
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) {
|
||||
|
@ -346,22 +340,18 @@ struct type_record {
|
|||
+ "\" referenced unknown base type \"" + tname + "\"");
|
||||
}
|
||||
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks.
|
||||
bool this_has_unique_ptr_holder = (holder_enum_v == holder_enum_t::std_unique_ptr);
|
||||
bool base_has_unique_ptr_holder
|
||||
= (base_info->holder_enum_v == holder_enum_t::std_unique_ptr);
|
||||
if (this_has_unique_ptr_holder != base_has_unique_ptr_holder) {
|
||||
if (default_holder != base_info->default_holder) {
|
||||
std::string tname(base.name());
|
||||
detail::clean_type_id(tname);
|
||||
pybind11_fail("generic_type: type \"" + std::string(name) + "\" "
|
||||
+ (this_has_unique_ptr_holder ? "does not have" : "has")
|
||||
+ (default_holder ? "does not have" : "has")
|
||||
+ " a non-default holder type while its base \"" + tname + "\" "
|
||||
+ (base_has_unique_ptr_holder ? "does not" : "does"));
|
||||
+ (base_info->default_holder ? "does not" : "does"));
|
||||
}
|
||||
|
||||
bases.append((PyObject *) base_info->type);
|
||||
|
||||
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
|
||||
#if PY_VERSION_HEX < 0x030B0000
|
||||
dynamic_attr |= base_info->type->tp_dictoffset != 0;
|
||||
#else
|
||||
dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
|
||||
|
@ -613,14 +603,6 @@ struct process_attribute<module_local> : process_attribute_default<module_local>
|
|||
static void init(const module_local &l, type_record *r) { r->module_local = l.value; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct process_attribute<release_gil_before_calling_cpp_dtor>
|
||||
: process_attribute_default<release_gil_before_calling_cpp_dtor> {
|
||||
static void init(const release_gil_before_calling_cpp_dtor &, type_record *r) {
|
||||
r->release_gil_before_calling_cpp_dtor = true;
|
||||
}
|
||||
};
|
||||
|
||||
/// Process a 'prepend' attribute, putting this at the beginning of the overload chain
|
||||
template <>
|
||||
struct process_attribute<prepend> : process_attribute_default<prepend> {
|
||||
|
|
|
@ -158,7 +158,7 @@ public:
|
|||
} else {
|
||||
handle src_or_index = src;
|
||||
// PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls.
|
||||
#if defined(PYPY_VERSION)
|
||||
#if PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION)
|
||||
object index;
|
||||
if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr())
|
||||
index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
|
||||
|
@ -343,7 +343,7 @@ public:
|
|||
#else
|
||||
// Alternate approach for CPython: this does the same as the above, but optimized
|
||||
// using the CPython API so as to avoid an unneeded attribute lookup.
|
||||
else if (auto *tp_as_number = Py_TYPE(src.ptr())->tp_as_number) {
|
||||
else if (auto *tp_as_number = src.ptr()->ob_type->tp_as_number) {
|
||||
if (PYBIND11_NB_BOOL(tp_as_number)) {
|
||||
res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr());
|
||||
}
|
||||
|
@ -754,7 +754,6 @@ 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
|
||||
|
@ -790,10 +789,7 @@ public:
|
|||
protected:
|
||||
friend class type_caster_generic;
|
||||
void check_holder_compat() {
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks.
|
||||
bool inst_has_unique_ptr_holder
|
||||
= (typeinfo->holder_enum_v == holder_enum_t::std_unique_ptr);
|
||||
if (inst_has_unique_ptr_holder) {
|
||||
if (typeinfo->default_holder) {
|
||||
throw cast_error("Unable to load a custom holder type from a default-holder instance");
|
||||
}
|
||||
}
|
||||
|
@ -839,144 +835,10 @@ protected:
|
|||
holder_type holder;
|
||||
};
|
||||
|
||||
template <typename, typename SFINAE = void>
|
||||
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 <typename type>
|
||||
struct copyable_holder_caster<
|
||||
type,
|
||||
std::shared_ptr<type>,
|
||||
enable_if_t<copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled<type>::value>>
|
||||
: public type_caster_base<type> {
|
||||
public:
|
||||
using base = type_caster_base<type>;
|
||||
static_assert(std::is_base_of<base, type_caster<type>>::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) {
|
||||
if (base::template load_impl<copyable_holder_caster<type, std::shared_ptr<type>>>(
|
||||
src, convert)) {
|
||||
sh_load_helper.maybe_set_python_instance_is_alias(src);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
explicit operator std::shared_ptr<type> *() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
pybind11_fail("Passing `std::shared_ptr<T> *` from Python to C++ is not supported "
|
||||
"(inherently unsafe).");
|
||||
}
|
||||
return std::addressof(shared_ptr_storage);
|
||||
}
|
||||
|
||||
explicit operator std::shared_ptr<type> &() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
|
||||
}
|
||||
return shared_ptr_storage;
|
||||
}
|
||||
|
||||
static handle
|
||||
cast(const std::shared_ptr<type> &src, return_value_policy policy, handle parent) {
|
||||
const auto *ptr = src.get();
|
||||
auto st = type_caster_base<type>::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<type>::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<type> 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() {
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks.
|
||||
bool inst_has_unique_ptr_holder
|
||||
= (typeinfo->holder_enum_v == holder_enum_t::std_unique_ptr);
|
||||
if (inst_has_unique_ptr_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;
|
||||
sh_load_helper.was_populated = true;
|
||||
value = sh_load_helper.get_void_ptr_or_nullptr();
|
||||
return;
|
||||
}
|
||||
if (v_h.holder_constructed()) {
|
||||
value = v_h.value_ptr();
|
||||
shared_ptr_storage = v_h.template holder<std::shared_ptr<type>>();
|
||||
return;
|
||||
}
|
||||
throw cast_error("Unable to cast from non-held to held instance (T& to Holder<T>) "
|
||||
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for "
|
||||
"type information)");
|
||||
#else
|
||||
"of type '"
|
||||
+ type_id<std::shared_ptr<type>>() + "''");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T = std::shared_ptr<type>,
|
||||
detail::enable_if_t<!std::is_constructible<T, const T &, type *>::value, int> = 0>
|
||||
bool try_implicit_casts(handle, bool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T = std::shared_ptr<type>,
|
||||
detail::enable_if_t<std::is_constructible<T, const T &, type *>::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_storage
|
||||
= std::shared_ptr<type>(sub_caster.shared_ptr_storage, (type *) value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool try_direct_conversions(handle) { return false; }
|
||||
|
||||
smart_holder_type_caster_support::load_helper<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
|
||||
std::shared_ptr<type> shared_ptr_storage;
|
||||
};
|
||||
|
||||
/// Specialize for the common std::shared_ptr, so users don't need to
|
||||
template <typename T>
|
||||
class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> {};
|
||||
|
||||
// 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.
|
||||
|
@ -992,143 +854,6 @@ struct move_only_holder_caster {
|
|||
static constexpr auto name = type_caster_base<type>::name;
|
||||
};
|
||||
|
||||
template <typename, typename SFINAE = void>
|
||||
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 <typename type, typename deleter>
|
||||
struct move_only_holder_caster<
|
||||
type,
|
||||
std::unique_ptr<type, deleter>,
|
||||
enable_if_t<move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled<type>::value>>
|
||||
: public type_caster_base<type> {
|
||||
public:
|
||||
using base = type_caster_base<type>;
|
||||
static_assert(std::is_base_of<base, type_caster<type>>::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<type, deleter> &&src, return_value_policy policy, handle parent) {
|
||||
auto *ptr = src.get();
|
||||
auto st = type_caster_base<type>::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<type, deleter> &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 const unique_ptr&");
|
||||
}
|
||||
return type_caster_base<type>::cast(src.get(), policy, parent);
|
||||
}
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (base::template load_impl<
|
||||
move_only_holder_caster<type, std::unique_ptr<type, deleter>>>(src, convert)) {
|
||||
sh_load_helper.maybe_set_python_instance_is_alias(src);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
sh_load_helper.was_populated = true;
|
||||
value = sh_load_helper.get_void_ptr_or_nullptr();
|
||||
return;
|
||||
}
|
||||
pybind11_fail("Passing `std::unique_ptr<T>` from Python to C++ requires `py::class_<T, "
|
||||
"py::smart_holder>` (with T = "
|
||||
+ clean_type_id(typeinfo->cpptype->name()) + ")");
|
||||
}
|
||||
|
||||
template <typename T_>
|
||||
using cast_op_type
|
||||
= conditional_t<std::is_same<typename std::remove_volatile<T_>::type,
|
||||
const std::unique_ptr<type, deleter> &>::value
|
||||
|| std::is_same<typename std::remove_volatile<T_>::type,
|
||||
const std::unique_ptr<const type, deleter> &>::value,
|
||||
const std::unique_ptr<type, deleter> &,
|
||||
std::unique_ptr<type, deleter>>;
|
||||
|
||||
explicit operator std::unique_ptr<type, deleter>() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
return sh_load_helper.template load_as_unique_ptr<deleter>(value);
|
||||
}
|
||||
pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
|
||||
}
|
||||
|
||||
explicit operator const std::unique_ptr<type, deleter> &() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
// Get shared_ptr to ensure that the Python object is not disowned elsewhere.
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
|
||||
// Build a temporary unique_ptr that is meant to never expire.
|
||||
unique_ptr_storage = std::shared_ptr<std::unique_ptr<type, deleter>>(
|
||||
new std::unique_ptr<type, deleter>{
|
||||
sh_load_helper.template load_as_const_unique_ptr<deleter>(
|
||||
shared_ptr_storage.get())},
|
||||
[](std::unique_ptr<type, deleter> *ptr) {
|
||||
if (!ptr) {
|
||||
pybind11_fail("FATAL: `const std::unique_ptr<T, D> &` was disowned "
|
||||
"(EXPECT UNDEFINED BEHAVIOR).");
|
||||
}
|
||||
(void) ptr->release();
|
||||
delete ptr;
|
||||
});
|
||||
return *unique_ptr_storage;
|
||||
}
|
||||
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<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
|
||||
std::shared_ptr<type> shared_ptr_storage; // Serves as a pseudo lock.
|
||||
std::shared_ptr<std::unique_ptr<type, deleter>> unique_ptr_storage;
|
||||
};
|
||||
|
||||
template <typename type, typename deleter>
|
||||
class type_caster<std::unique_ptr<type, deleter>>
|
||||
: public move_only_holder_caster<type, std::unique_ptr<type, deleter>> {};
|
||||
|
@ -1138,20 +863,18 @@ using type_caster_holder = conditional_t<is_copy_constructible<holder_type>::val
|
|||
copyable_holder_caster<type, holder_type>,
|
||||
move_only_holder_caster<type, holder_type>>;
|
||||
|
||||
template <bool Value = false>
|
||||
struct always_construct_holder_value {
|
||||
template <typename T, bool Value = false>
|
||||
struct always_construct_holder {
|
||||
static constexpr bool value = Value;
|
||||
};
|
||||
|
||||
template <typename T, bool Value = false>
|
||||
struct always_construct_holder : always_construct_holder_value<Value> {};
|
||||
|
||||
/// Create a specialization for custom holder types (silently ignores std::shared_ptr)
|
||||
#define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \
|
||||
namespace detail { \
|
||||
template <typename type> \
|
||||
struct always_construct_holder<holder_type> : always_construct_holder_value<__VA_ARGS__> {}; \
|
||||
struct always_construct_holder<holder_type> : always_construct_holder<void, ##__VA_ARGS__> { \
|
||||
}; \
|
||||
template <typename type> \
|
||||
class type_caster<holder_type, enable_if_t<!is_shared_ptr<holder_type>::value>> \
|
||||
: public type_caster_holder<type, holder_type> {}; \
|
||||
|
@ -1162,14 +885,10 @@ struct always_construct_holder : always_construct_holder_value<Value> {};
|
|||
template <typename base, typename holder>
|
||||
struct is_holder_type
|
||||
: std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
|
||||
|
||||
// Specializations for always-supported holders:
|
||||
// Specialization for always-supported unique_ptr holders:
|
||||
template <typename base, typename deleter>
|
||||
struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
|
||||
|
||||
template <typename base>
|
||||
struct is_holder_type<base, smart_holder> : std::true_type {};
|
||||
|
||||
#ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888
|
||||
|
||||
// This leads to compilation errors if a specialization is missing.
|
||||
|
@ -1293,18 +1012,10 @@ template <>
|
|||
struct handle_type_name<args> {
|
||||
static constexpr auto name = const_name("*args");
|
||||
};
|
||||
template <typename T>
|
||||
struct handle_type_name<Args<T>> {
|
||||
static constexpr auto name = const_name("*args: ") + make_caster<T>::name;
|
||||
};
|
||||
template <>
|
||||
struct handle_type_name<kwargs> {
|
||||
static constexpr auto name = const_name("**kwargs");
|
||||
};
|
||||
template <typename T>
|
||||
struct handle_type_name<KWArgs<T>> {
|
||||
static constexpr auto name = const_name("**kwargs: ") + make_caster<T>::name;
|
||||
};
|
||||
template <>
|
||||
struct handle_type_name<obj_attr_accessor> {
|
||||
static constexpr auto name = const_name<obj_attr_accessor>();
|
||||
|
@ -1610,31 +1321,6 @@ object object_or_cast(T &&o) {
|
|||
return pybind11::cast(std::forward<T>(o));
|
||||
}
|
||||
|
||||
// Declared in pytypes.h:
|
||||
// Implemented here so that make_caster<T> can be used.
|
||||
template <typename D>
|
||||
template <typename T>
|
||||
str_attr_accessor object_api<D>::attr_with_type_hint(const char *key) const {
|
||||
#if !defined(__cpp_inline_variables)
|
||||
static_assert(always_false<T>::value,
|
||||
"C++17 feature __cpp_inline_variables not available: "
|
||||
"https://en.cppreference.com/w/cpp/language/static#Static_data_members");
|
||||
#endif
|
||||
object ann = annotations();
|
||||
if (ann.contains(key)) {
|
||||
throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already.");
|
||||
}
|
||||
ann[key] = make_caster<T>::name.text;
|
||||
return {derived(), key};
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
template <typename T>
|
||||
obj_attr_accessor object_api<D>::attr_with_type_hint(handle key) const {
|
||||
(void) attr_with_type_hint<T>(key.cast<std::string>().c_str());
|
||||
return {derived(), reinterpret_borrow<object>(key)};
|
||||
}
|
||||
|
||||
// Placeholder type for the unneeded (and dead code) static variable in the
|
||||
// PYBIND11_OVERRIDE_OVERRIDE macro
|
||||
struct override_unused {};
|
||||
|
@ -1817,7 +1503,7 @@ struct kw_only {};
|
|||
|
||||
/// \ingroup annotations
|
||||
/// Annotation indicating that all previous arguments are positional-only; the is the equivalent of
|
||||
/// an unnamed '/' argument
|
||||
/// an unnamed '/' argument (in Python 3.8)
|
||||
struct pos_only {};
|
||||
|
||||
template <typename T>
|
||||
|
@ -1878,24 +1564,15 @@ struct function_call {
|
|||
handle init_self;
|
||||
};
|
||||
|
||||
// See PR #5396 for the discussion that led to this
|
||||
template <typename Base, typename Derived, typename = void>
|
||||
struct is_same_or_base_of : std::is_same<Base, Derived> {};
|
||||
|
||||
// Only evaluate is_base_of if Derived is complete.
|
||||
// is_base_of raises a compiler error if Derived is incomplete.
|
||||
template <typename Base, typename Derived>
|
||||
struct is_same_or_base_of<Base, Derived, decltype(void(sizeof(Derived)))>
|
||||
: any_of<std::is_same<Base, Derived>, std::is_base_of<Base, Derived>> {};
|
||||
|
||||
/// Helper class which loads arguments for C++ functions called from Python
|
||||
template <typename... Args>
|
||||
class argument_loader {
|
||||
using indices = make_index_sequence<sizeof...(Args)>;
|
||||
|
||||
template <typename Arg>
|
||||
using argument_is_args = is_same_or_base_of<args, intrinsic_t<Arg>>;
|
||||
using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
|
||||
template <typename Arg>
|
||||
using argument_is_kwargs = is_same_or_base_of<kwargs, intrinsic_t<Arg>>;
|
||||
using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;
|
||||
// Get kwargs argument position, or -1 if not present:
|
||||
static constexpr auto kwargs_pos = constexpr_last<argument_is_kwargs, Args...>();
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
NOTE
|
||||
----
|
||||
|
||||
The C++ code here
|
||||
|
||||
** only depends on <Python.h> **
|
||||
|
||||
and nothing else.
|
||||
|
||||
DO NOT ADD CODE WITH OTHER EXTERNAL DEPENDENCIES TO THIS DIRECTORY.
|
||||
|
||||
Read on:
|
||||
|
||||
pybind11_conduit_v1.h — Type-safe interoperability between different
|
||||
independent Python/C++ bindings systems.
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright (c) 2024 The pybind Community.
|
||||
|
||||
/* The pybind11_conduit_v1 feature enables type-safe interoperability between
|
||||
|
||||
* different independent Python/C++ bindings systems,
|
||||
|
||||
* including pybind11 versions with different PYBIND11_INTERNALS_VERSION's.
|
||||
|
||||
The naming of the feature is a bit misleading:
|
||||
|
||||
* The feature is in no way tied to pybind11 internals.
|
||||
|
||||
* It just happens to originate from pybind11 and currently still lives there.
|
||||
|
||||
* The only external dependency is <Python.h>.
|
||||
|
||||
The implementation is a VERY light-weight dependency. It is designed to be
|
||||
compatible with any ISO C++11 (or higher) compiler, and does NOT require
|
||||
C++ Exception Handling to be enabled.
|
||||
|
||||
Please see https://github.com/pybind/pybind11/pull/5296 for more background.
|
||||
|
||||
The implementation involves a
|
||||
|
||||
def _pybind11_conduit_v1_(
|
||||
self,
|
||||
pybind11_platform_abi_id: bytes,
|
||||
cpp_type_info_capsule: capsule,
|
||||
pointer_kind: bytes) -> capsule
|
||||
|
||||
method that is meant to be added to Python objects wrapping C++ objects
|
||||
(e.g. pybind11::class_-wrapped types).
|
||||
|
||||
The design of the _pybind11_conduit_v1_ feature provides two layers of
|
||||
protection against C++ ABI mismatches:
|
||||
|
||||
* The first and most important layer is that the pybind11_platform_abi_id's
|
||||
must match between extensions. — This will never be perfect, but is the same
|
||||
pragmatic approach used in pybind11 since 2017
|
||||
(https://github.com/pybind/pybind11/commit/96997a4b9d4ec3d389a570604394af5d5eee2557,
|
||||
PYBIND11_INTERNALS_ID).
|
||||
|
||||
* The second layer is that the typeid(std::type_info).name()'s must match
|
||||
between extensions.
|
||||
|
||||
The implementation below (which is shorter than this comment!), serves as a
|
||||
battle-tested specification. The main API is this one function:
|
||||
|
||||
auto *cpp_pointer = pybind11_conduit_v1::get_type_pointer_ephemeral<YourType>(py_obj);
|
||||
|
||||
It is meant to be a minimalistic reference implementation, intentionally
|
||||
without comprehensive error reporting. It is expected that major bindings
|
||||
systems will roll their own, compatible implementations, potentially with
|
||||
system-specific error reporting. The essential specifications all bindings
|
||||
systems need to agree on are merely:
|
||||
|
||||
* PYBIND11_PLATFORM_ABI_ID (const char* literal).
|
||||
|
||||
* The cpp_type_info capsule (see below: a void *ptr and a const char *name).
|
||||
|
||||
* The cpp_conduit capsule (see below: a void *ptr and a const char *name).
|
||||
|
||||
* "raw_pointer_ephemeral" means: the lifetime of the pointer is the lifetime
|
||||
of the py_obj.
|
||||
|
||||
*/
|
||||
|
||||
// THIS MUST STAY AT THE TOP!
|
||||
#include "pybind11_platform_abi_id.h"
|
||||
|
||||
#include <Python.h>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace pybind11_conduit_v1 {
|
||||
|
||||
inline void *get_raw_pointer_ephemeral(PyObject *py_obj, const std::type_info *cpp_type_info) {
|
||||
PyObject *cpp_type_info_capsule
|
||||
= PyCapsule_New(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
|
||||
typeid(std::type_info).name(),
|
||||
nullptr);
|
||||
if (cpp_type_info_capsule == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
PyObject *cpp_conduit = PyObject_CallMethod(py_obj,
|
||||
"_pybind11_conduit_v1_",
|
||||
"yOy",
|
||||
PYBIND11_PLATFORM_ABI_ID,
|
||||
cpp_type_info_capsule,
|
||||
"raw_pointer_ephemeral");
|
||||
Py_DECREF(cpp_type_info_capsule);
|
||||
if (cpp_conduit == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
void *raw_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name());
|
||||
Py_DECREF(cpp_conduit);
|
||||
if (PyErr_Occurred()) {
|
||||
return nullptr;
|
||||
}
|
||||
return raw_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *get_type_pointer_ephemeral(PyObject *py_obj) {
|
||||
void *raw_ptr = get_raw_pointer_ephemeral(py_obj, &typeid(T));
|
||||
if (raw_ptr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<T *>(raw_ptr);
|
||||
}
|
||||
|
||||
} // namespace pybind11_conduit_v1
|
|
@ -1,87 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// Copyright (c) 2024 The pybind Community.
|
||||
|
||||
// To maximize reusability:
|
||||
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
|
||||
|
||||
#include "wrap_include_python_h.h"
|
||||
|
||||
// Implementation details. DO NOT USE ELSEWHERE. (Unfortunately we cannot #undef them.)
|
||||
// This is duplicated here to maximize portability.
|
||||
#define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x
|
||||
#define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x)
|
||||
|
||||
#ifdef PYBIND11_COMPILER_TYPE
|
||||
// // To maintain backward compatibility (see PR #5439).
|
||||
# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE ""
|
||||
#else
|
||||
# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "_"
|
||||
# if defined(__MINGW32__)
|
||||
# define PYBIND11_COMPILER_TYPE "mingw"
|
||||
# elif defined(__CYGWIN__)
|
||||
# define PYBIND11_COMPILER_TYPE "gcc_cygwin"
|
||||
# elif defined(_MSC_VER)
|
||||
# define PYBIND11_COMPILER_TYPE "msvc"
|
||||
# elif defined(__clang__) || defined(__GNUC__)
|
||||
# define PYBIND11_COMPILER_TYPE "system" // Assumed compatible with system compiler.
|
||||
# else
|
||||
# error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE."
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// PR #5439 made this macro obsolete. However, there are many manipulations of this macro in the
|
||||
// wild. Therefore, to maintain backward compatibility, it is kept around.
|
||||
#ifndef PYBIND11_STDLIB
|
||||
# define PYBIND11_STDLIB ""
|
||||
#endif
|
||||
|
||||
#ifndef PYBIND11_BUILD_ABI
|
||||
# if defined(_MSC_VER) // See PR #4953.
|
||||
# if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd.
|
||||
# if (_MSC_VER) / 100 == 19
|
||||
# define PYBIND11_BUILD_ABI "_md_mscver19"
|
||||
# else
|
||||
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
|
||||
# endif
|
||||
# elif defined(_MT) // Corresponding to CL command line options /MT or /MTd.
|
||||
# define PYBIND11_BUILD_ABI "_mt_mscver" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_MSC_VER)
|
||||
# else
|
||||
# if (_MSC_VER) / 100 == 19
|
||||
# define PYBIND11_BUILD_ABI "_none_mscver19"
|
||||
# else
|
||||
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
|
||||
# endif
|
||||
# endif
|
||||
# elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html
|
||||
# define PYBIND11_BUILD_ABI \
|
||||
"_libcpp_abi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_LIBCPP_ABI_VERSION)
|
||||
# elif defined(_GLIBCXX_USE_CXX11_ABI) // See PR #5439.
|
||||
# if defined(__NVCOMPILER)
|
||||
// // Assume that NVHPC is in the 1xxx ABI family.
|
||||
// // THIS ASSUMPTION IS NOT FUTURE PROOF but apparently the best we can do.
|
||||
// // Please let us know if there is a way to validate the assumption here.
|
||||
# elif !defined(__GXX_ABI_VERSION)
|
||||
# error \
|
||||
"Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI): PLEASE REVISE THIS CODE."
|
||||
# endif
|
||||
# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000
|
||||
# error "Unknown platform or compiler (__GXX_ABI_VERSION): PLEASE REVISE THIS CODE."
|
||||
# endif
|
||||
# define PYBIND11_BUILD_ABI \
|
||||
"_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" PYBIND11_PLATFORM_ABI_ID_TOSTRING( \
|
||||
_GLIBCXX_USE_CXX11_ABI)
|
||||
# else
|
||||
# error "Unknown platform or compiler: PLEASE REVISE THIS CODE."
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// On MSVC, debug and release builds are not ABI-compatible!
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
# define PYBIND11_BUILD_TYPE "_debug"
|
||||
#else
|
||||
# define PYBIND11_BUILD_TYPE ""
|
||||
#endif
|
||||
|
||||
#define PYBIND11_PLATFORM_ABI_ID \
|
||||
PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE
|
|
@ -1,72 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// Copyright (c) 2024 The pybind Community.
|
||||
|
||||
// STRONG REQUIREMENT:
|
||||
// This header is a wrapper around `#include <Python.h>`, therefore it
|
||||
// MUST BE INCLUDED BEFORE ANY STANDARD HEADERS are included.
|
||||
// See also:
|
||||
// https://docs.python.org/3/c-api/intro.html#include-files
|
||||
// Quoting from there:
|
||||
// Note: Since Python may define some pre-processor definitions which affect
|
||||
// the standard headers on some systems, you must include Python.h before
|
||||
// any standard headers are included.
|
||||
|
||||
// To maximize reusability:
|
||||
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
|
||||
|
||||
// Disable linking to pythonX_d.lib on Windows in debug mode.
|
||||
#if defined(_MSC_VER) && defined(_DEBUG) && !defined(Py_DEBUG)
|
||||
// Workaround for a VS 2022 issue.
|
||||
// See https://github.com/pybind/pybind11/pull/3497 for full context.
|
||||
// NOTE: This workaround knowingly violates the Python.h include order
|
||||
// requirement (see above).
|
||||
# include <yvals.h>
|
||||
# if _MSVC_STL_VERSION >= 143
|
||||
# include <crtdefs.h>
|
||||
# endif
|
||||
# define PYBIND11_DEBUG_MARKER
|
||||
# undef _DEBUG
|
||||
#endif
|
||||
|
||||
// Don't let Python.h #define (v)snprintf as macro because they are implemented
|
||||
// properly in Visual Studio since 2015.
|
||||
#if defined(_MSC_VER)
|
||||
# define HAVE_SNPRINTF 1
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable : 4505)
|
||||
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
#include <frameobject.h>
|
||||
#include <pythread.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#if defined(PYBIND11_DEBUG_MARKER)
|
||||
# define _DEBUG
|
||||
# undef PYBIND11_DEBUG_MARKER
|
||||
#endif
|
||||
|
||||
// Python #defines overrides on all sorts of core functions, which
|
||||
// tends to wreak havok in C++ codebases that expect these to work
|
||||
// like regular functions (potentially with several overloads).
|
||||
#if defined(isalnum)
|
||||
# undef isalnum
|
||||
# undef isalpha
|
||||
# undef islower
|
||||
# undef isspace
|
||||
# undef isupper
|
||||
# undef tolower
|
||||
# undef toupper
|
||||
#endif
|
||||
|
||||
#if defined(copysign)
|
||||
# undef copysign
|
||||
#endif
|
|
@ -312,31 +312,7 @@ inline void traverse_offset_bases(void *valueptr,
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
inline void enable_try_inc_ref(PyObject *obj) {
|
||||
// TODO: Replace with PyUnstable_Object_EnableTryIncRef when available.
|
||||
// See https://github.com/python/cpython/issues/128844
|
||||
if (_Py_IsImmortal(obj)) {
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
|
||||
if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) {
|
||||
// Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states.
|
||||
return;
|
||||
}
|
||||
if (_Py_atomic_compare_exchange_ssize(
|
||||
&obj->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool register_instance_impl(void *ptr, instance *self) {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
enable_try_inc_ref(reinterpret_cast<PyObject *>(self));
|
||||
#endif
|
||||
with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); });
|
||||
return true; // unused, but gives the same signature as the deregister func
|
||||
}
|
||||
|
@ -457,8 +433,6 @@ inline void clear_instance(PyObject *self) {
|
|||
if (instance->owned || v_h.holder_constructed()) {
|
||||
v_h.type->dealloc(v_h);
|
||||
}
|
||||
} else if (v_h.holder_constructed()) {
|
||||
v_h.type->dealloc(v_h); // Disowned instance.
|
||||
}
|
||||
}
|
||||
// Deallocate the value/holder layout internals:
|
||||
|
@ -494,9 +468,19 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) {
|
|||
|
||||
type->tp_free(self);
|
||||
|
||||
#if PY_VERSION_HEX < 0x03080000
|
||||
// `type->tp_dealloc != pybind11_object_dealloc` means that we're being called
|
||||
// as part of a derived type's dealloc, in which case we're not allowed to decref
|
||||
// the type here. For cross-module compatibility, we shouldn't compare directly
|
||||
// with `pybind11_object_dealloc`, but with the common one stashed in internals.
|
||||
auto pybind11_object_type = (PyTypeObject *) get_internals().instance_base;
|
||||
if (type->tp_dealloc == pybind11_object_type->tp_dealloc)
|
||||
Py_DECREF(type);
|
||||
#else
|
||||
// This was not needed before Python 3.8 (Python issue 35810)
|
||||
// https://github.com/pybind/pybind11/issues/1946
|
||||
Py_DECREF(type);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string error_string();
|
||||
|
@ -576,7 +560,7 @@ extern "C" inline int pybind11_clear(PyObject *self) {
|
|||
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
|
||||
auto *type = &heap_type->ht_type;
|
||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
||||
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
|
||||
#if PY_VERSION_HEX < 0x030B0000
|
||||
type->tp_dictoffset = type->tp_basicsize; // place dict at the end
|
||||
type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
|
||||
#else
|
||||
|
@ -609,9 +593,9 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
|
|||
return -1;
|
||||
}
|
||||
std::memset(view, 0, sizeof(Py_buffer));
|
||||
std::unique_ptr<buffer_info> info = nullptr;
|
||||
buffer_info *info = nullptr;
|
||||
try {
|
||||
info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data));
|
||||
info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
|
||||
} catch (...) {
|
||||
try_translate_exceptions();
|
||||
raise_from(PyExc_BufferError, "Error getting buffer");
|
||||
|
@ -622,72 +606,29 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
|
|||
}
|
||||
|
||||
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
|
||||
delete info;
|
||||
// view->obj = nullptr; // Was just memset to 0, so not necessary
|
||||
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Fill in all the information, and then downgrade as requested by the caller, or raise an
|
||||
// error if that's not possible.
|
||||
view->obj = obj;
|
||||
view->ndim = 1;
|
||||
view->internal = info;
|
||||
view->buf = info->ptr;
|
||||
view->itemsize = info->itemsize;
|
||||
view->len = view->itemsize;
|
||||
for (auto s : info->shape) {
|
||||
view->len *= s;
|
||||
}
|
||||
view->ndim = static_cast<int>(info->ndim);
|
||||
view->shape = info->shape.data();
|
||||
view->strides = info->strides.data();
|
||||
view->readonly = static_cast<int>(info->readonly);
|
||||
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
|
||||
view->format = const_cast<char *>(info->format.c_str());
|
||||
}
|
||||
|
||||
// Note, all contiguity flags imply PyBUF_STRIDES and lower.
|
||||
if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) {
|
||||
if (PyBuffer_IsContiguous(view, 'C') == 0) {
|
||||
std::memset(view, 0, sizeof(Py_buffer));
|
||||
set_error(PyExc_BufferError,
|
||||
"C-contiguous buffer requested for discontiguous storage");
|
||||
return -1;
|
||||
}
|
||||
} else if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) {
|
||||
if (PyBuffer_IsContiguous(view, 'F') == 0) {
|
||||
std::memset(view, 0, sizeof(Py_buffer));
|
||||
set_error(PyExc_BufferError,
|
||||
"Fortran-contiguous buffer requested for discontiguous storage");
|
||||
return -1;
|
||||
}
|
||||
} else if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS) {
|
||||
if (PyBuffer_IsContiguous(view, 'A') == 0) {
|
||||
std::memset(view, 0, sizeof(Py_buffer));
|
||||
set_error(PyExc_BufferError, "Contiguous buffer requested for discontiguous storage");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES) {
|
||||
// If no strides are requested, the buffer must be C-contiguous.
|
||||
// https://docs.python.org/3/c-api/buffer.html#contiguity-requests
|
||||
if (PyBuffer_IsContiguous(view, 'C') == 0) {
|
||||
std::memset(view, 0, sizeof(Py_buffer));
|
||||
set_error(PyExc_BufferError,
|
||||
"C-contiguous buffer requested for discontiguous storage");
|
||||
return -1;
|
||||
}
|
||||
|
||||
view->strides = nullptr;
|
||||
|
||||
// Since this is a contiguous buffer, it can also pretend to be 1D.
|
||||
if ((flags & PyBUF_ND) != PyBUF_ND) {
|
||||
view->shape = nullptr;
|
||||
view->ndim = 0;
|
||||
}
|
||||
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
|
||||
view->ndim = (int) info->ndim;
|
||||
view->strides = info->strides.data();
|
||||
view->shape = info->shape.data();
|
||||
}
|
||||
|
||||
// Set these after all checks so they don't leak out into the caller, and can be automatically
|
||||
// cleaned up on error.
|
||||
view->buf = info->ptr;
|
||||
view->internal = info.release();
|
||||
view->obj = obj;
|
||||
Py_INCREF(view->obj);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -9,18 +9,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/conduit/wrap_include_python_h.h>
|
||||
#if PY_VERSION_HEX < 0x03080000
|
||||
# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7."
|
||||
#endif
|
||||
|
||||
#define PYBIND11_VERSION_MAJOR 3
|
||||
#define PYBIND11_VERSION_MINOR 0
|
||||
#define PYBIND11_VERSION_PATCH 0.dev1
|
||||
#define PYBIND11_VERSION_MAJOR 2
|
||||
#define PYBIND11_VERSION_MINOR 13
|
||||
#define PYBIND11_VERSION_PATCH 6
|
||||
|
||||
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
|
||||
// Additional convention: 0xD = dev
|
||||
#define PYBIND11_VERSION_HEX 0x030000D1
|
||||
#define PYBIND11_VERSION_HEX 0x020D0600
|
||||
|
||||
// Define some generic pybind11 helper macros for warning management.
|
||||
//
|
||||
|
@ -46,7 +41,7 @@
|
|||
# define PYBIND11_COMPILER_CLANG
|
||||
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
||||
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push)
|
||||
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic pop)
|
||||
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic push)
|
||||
#elif defined(__GNUC__)
|
||||
# define PYBIND11_COMPILER_GCC
|
||||
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
||||
|
@ -169,6 +164,14 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(PYBIND11_EXPORT_EXCEPTION)
|
||||
# if defined(__apple_build_version__)
|
||||
# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT
|
||||
# else
|
||||
# define PYBIND11_EXPORT_EXCEPTION
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// For CUDA, GCC7, GCC8:
|
||||
// PYBIND11_NOINLINE_FORCED is incompatible with `-Wattributes -Werror`.
|
||||
// When defining PYBIND11_NOINLINE_FORCED, it is best to also use `-Wno-attributes`.
|
||||
|
@ -209,6 +212,31 @@
|
|||
# define PYBIND11_MAYBE_UNUSED __attribute__((__unused__))
|
||||
#endif
|
||||
|
||||
/* Don't let Python.h #define (v)snprintf as macro because they are implemented
|
||||
properly in Visual Studio since 2015. */
|
||||
#if defined(_MSC_VER)
|
||||
# define HAVE_SNPRINTF 1
|
||||
#endif
|
||||
|
||||
/// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode
|
||||
#if defined(_MSC_VER)
|
||||
PYBIND11_WARNING_PUSH
|
||||
PYBIND11_WARNING_DISABLE_MSVC(4505)
|
||||
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only)
|
||||
# if defined(_DEBUG) && !defined(Py_DEBUG)
|
||||
// Workaround for a VS 2022 issue.
|
||||
// NOTE: This workaround knowingly violates the Python.h include order requirement:
|
||||
// https://docs.python.org/3/c-api/intro.html#include-files
|
||||
// See https://github.com/pybind/pybind11/pull/3497 for full context.
|
||||
# include <yvals.h>
|
||||
# if _MSVC_STL_VERSION >= 143
|
||||
# include <crtdefs.h>
|
||||
# endif
|
||||
# define PYBIND11_DEBUG_MARKER
|
||||
# undef _DEBUG
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// https://en.cppreference.com/w/c/chrono/localtime
|
||||
#if defined(__STDC_LIB_EXT1__) && !defined(__STDC_WANT_LIB_EXT1__)
|
||||
# define __STDC_WANT_LIB_EXT1__
|
||||
|
@ -243,14 +271,46 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
#if PY_VERSION_HEX < 0x03070000
|
||||
# error "PYTHON < 3.7 IS UNSUPPORTED. pybind11 v2.12 was the last to support Python 3.6."
|
||||
#endif
|
||||
#include <frameobject.h>
|
||||
#include <pythread.h>
|
||||
|
||||
/* Python #defines overrides on all sorts of core functions, which
|
||||
tends to weak havok in C++ codebases that expect these to work
|
||||
like regular functions (potentially with several overloads) */
|
||||
#if defined(isalnum)
|
||||
# undef isalnum
|
||||
# undef isalpha
|
||||
# undef islower
|
||||
# undef isspace
|
||||
# undef isupper
|
||||
# undef tolower
|
||||
# undef toupper
|
||||
#endif
|
||||
|
||||
#if defined(copysign)
|
||||
# undef copysign
|
||||
#endif
|
||||
|
||||
#if defined(PYBIND11_NUMPY_1_ONLY)
|
||||
# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED
|
||||
#endif
|
||||
|
||||
#if (defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
|
||||
#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
|
||||
# define PYBIND11_SIMPLE_GIL_MANAGEMENT
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# if defined(PYBIND11_DEBUG_MARKER)
|
||||
# define _DEBUG
|
||||
# undef PYBIND11_DEBUG_MARKER
|
||||
# endif
|
||||
PYBIND11_WARNING_POP
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
|
@ -269,17 +329,6 @@
|
|||
# endif
|
||||
#endif
|
||||
|
||||
// For libc++, the exceptions should be exported,
|
||||
// otherwise, the exception translation would be incorrect.
|
||||
// IMPORTANT: This code block must stay BELOW the #include <exception> above (see PR #5390).
|
||||
#if !defined(PYBIND11_EXPORT_EXCEPTION)
|
||||
# if defined(_LIBCPP_EXCEPTION)
|
||||
# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT
|
||||
# else
|
||||
# define PYBIND11_EXPORT_EXCEPTION
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Must be after including <version> or one of the other headers specified by the standard
|
||||
#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L
|
||||
# define PYBIND11_HAS_U8STRING
|
||||
|
@ -338,20 +387,6 @@
|
|||
#define PYBIND11_CONCAT(first, second) first##second
|
||||
#define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals();
|
||||
|
||||
#if !defined(GRAALVM_PYTHON)
|
||||
# define PYBIND11_PYCFUNCTION_GET_DOC(func) ((func)->m_ml->ml_doc)
|
||||
# define PYBIND11_PYCFUNCTION_SET_DOC(func, doc) \
|
||||
do { \
|
||||
(func)->m_ml->ml_doc = (doc); \
|
||||
} while (0)
|
||||
#else
|
||||
# define PYBIND11_PYCFUNCTION_GET_DOC(func) (GraalPyCFunction_GetDoc((PyObject *) (func)))
|
||||
# define PYBIND11_PYCFUNCTION_SET_DOC(func, doc) \
|
||||
do { \
|
||||
GraalPyCFunction_SetDoc((PyObject *) (func), (doc)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define PYBIND11_CHECK_PYTHON_VERSION \
|
||||
{ \
|
||||
const char *compiled_ver \
|
||||
|
@ -605,8 +640,6 @@ struct instance {
|
|||
bool simple_instance_registered : 1;
|
||||
/// If true, get_internals().patients has an entry for this object
|
||||
bool has_patients : 1;
|
||||
/// If true, this Python object needs to be kept alive for the lifetime of the C++ value.
|
||||
bool is_alias : 1;
|
||||
|
||||
/// Initializes all of the above type/values/holders data (but not the instance values
|
||||
/// themselves)
|
||||
|
@ -629,14 +662,6 @@ struct instance {
|
|||
static_assert(std::is_standard_layout<instance>::value,
|
||||
"Internal error: `pybind11::detail::instance` is not standard layout!");
|
||||
|
||||
// Some older compilers (e.g. gcc 9.4.0) require
|
||||
// static_assert(always_false<T>::value, "...");
|
||||
// instead of
|
||||
// static_assert(false, "...");
|
||||
// to trigger the static_assert() in a template only if it is actually instantiated.
|
||||
template <typename>
|
||||
struct always_false : std::false_type {};
|
||||
|
||||
/// from __cpp_future__ import (convenient aliases from C++14/17)
|
||||
#if defined(PYBIND11_CPP14)
|
||||
using std::conditional_t;
|
||||
|
@ -1103,14 +1128,14 @@ struct overload_cast_impl {
|
|||
}
|
||||
|
||||
template <typename Return, typename Class>
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept
|
||||
-> decltype(pmf) {
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...),
|
||||
std::false_type = {}) const noexcept -> decltype(pmf) {
|
||||
return pmf;
|
||||
}
|
||||
|
||||
template <typename Return, typename Class>
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept
|
||||
-> decltype(pmf) {
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...) const,
|
||||
std::true_type) const noexcept -> decltype(pmf) {
|
||||
return pmf;
|
||||
}
|
||||
};
|
||||
|
@ -1258,10 +1283,5 @@ constexpr
|
|||
# define PYBIND11_DETAILED_ERROR_MESSAGES
|
||||
#endif
|
||||
|
||||
// CPython 3.11+ provides Py_TPFLAGS_MANAGED_DICT, but PyPy3.11 does not, see PR #5508.
|
||||
#if PY_VERSION_HEX < 0x030B0000 || defined(PYPY_VERSION)
|
||||
# define PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
|
|
@ -99,13 +99,6 @@ constexpr descr<1, Type> const_name() {
|
|||
return {'%'};
|
||||
}
|
||||
|
||||
// Use a different name based on whether the parameter is used as input or output
|
||||
template <size_t N1, size_t N2>
|
||||
constexpr descr<N1 + N2 + 1> io_name(char const (&text1)[N1], char const (&text2)[N2]) {
|
||||
return const_name("@") + const_name(text1) + const_name("@") + const_name(text2)
|
||||
+ const_name("@");
|
||||
}
|
||||
|
||||
// If "_" is defined as a macro, py::detail::_ cannot be provided.
|
||||
// It is therefore best to use py::detail::const_name universally.
|
||||
// This block is for backward compatibility only.
|
||||
|
@ -163,8 +156,9 @@ constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
|
|||
}
|
||||
#else
|
||||
template <size_t N, typename... Ts, typename... Args>
|
||||
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
|
||||
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
|
||||
constexpr auto concat(const descr<N, Ts...> &d,
|
||||
const Args &...args) -> decltype(std::declval<descr<N + 2, Ts...>>()
|
||||
+ concat(args...)) {
|
||||
return d + const_name(", ") + concat(args...);
|
||||
}
|
||||
#endif
|
||||
|
@ -174,15 +168,5 @@ constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {
|
|||
return const_name("{") + descr + const_name("}");
|
||||
}
|
||||
|
||||
template <size_t N, typename... Ts>
|
||||
constexpr descr<N + 4, Ts...> arg_descr(const descr<N, Ts...> &descr) {
|
||||
return const_name("@^") + descr + const_name("@!");
|
||||
}
|
||||
|
||||
template <size_t N, typename... Ts>
|
||||
constexpr descr<N + 4, Ts...> return_descr(const descr<N, Ts...> &descr) {
|
||||
return const_name("@$") + descr + const_name("@!");
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
|
|
@ -1,39 +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 <type_traits>
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename To, typename From, typename SFINAE = void>
|
||||
struct dynamic_raw_ptr_cast_is_possible : std::false_type {};
|
||||
|
||||
template <typename To, typename From>
|
||||
struct dynamic_raw_ptr_cast_is_possible<
|
||||
To,
|
||||
From,
|
||||
detail::enable_if_t<!std::is_same<To, void>::value && std::is_polymorphic<From>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename To,
|
||||
typename From,
|
||||
detail::enable_if_t<!dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
|
||||
To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename To,
|
||||
typename From,
|
||||
detail::enable_if_t<dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
|
||||
To *dynamic_raw_ptr_cast_if_possible(From *ptr) {
|
||||
return dynamic_cast<To *>(ptr);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
|
@ -50,17 +50,17 @@ inline void try_translate_exceptions() {
|
|||
- delegate translation to the next translator by throwing a new type of exception.
|
||||
*/
|
||||
|
||||
bool handled = with_exception_translators(
|
||||
[&](std::forward_list<ExceptionTranslator> &exception_translators,
|
||||
std::forward_list<ExceptionTranslator> &local_exception_translators) {
|
||||
if (detail::apply_exception_translators(local_exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
if (detail::apply_exception_translators(exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
bool handled = with_internals([&](internals &internals) {
|
||||
auto &local_exception_translators = get_local_internals().registered_exception_translators;
|
||||
if (detail::apply_exception_translators(local_exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
auto &exception_translators = internals.registered_exception_translators;
|
||||
if (detail::apply_exception_translators(exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!handled) {
|
||||
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "class.h"
|
||||
#include "using_smart_holder.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
|
@ -156,7 +155,7 @@ void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
|
|||
// holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
|
||||
// derived type (through those holder's implicit conversion from derived class holder
|
||||
// constructors).
|
||||
template <typename Class, detail::enable_if_t<!is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
template <typename Class>
|
||||
void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||
auto *ptr = holder_helper<Holder<Class>>::get(holder);
|
||||
|
@ -198,74 +197,6 @@ void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
|
|||
v_h.value_ptr() = new Alias<Class>(std::move(result));
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
|
||||
bool void_cast_raw_ptr) {
|
||||
void *void_ptr = void_cast_raw_ptr ? static_cast<void *>(unq_ptr.get()) : nullptr;
|
||||
return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr);
|
||||
}
|
||||
|
||||
template <typename Class,
|
||||
typename D = std::default_delete<Cpp<Class>>,
|
||||
detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, D> &&unq_ptr, bool need_alias) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||
auto *ptr = unq_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
|
||||
throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee "
|
||||
"is not an alias instance");
|
||||
}
|
||||
// Here and below: if the new object is a trampoline, the shared_from_this mechanism needs
|
||||
// to be prevented from accessing the smart_holder vptr, because it does not keep the
|
||||
// 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 = init_smart_holder_from_unique_ptr(
|
||||
std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias<Class>(ptr));
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
template <typename Class,
|
||||
typename D = std::default_delete<Alias<Class>>,
|
||||
detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h,
|
||||
std::unique_ptr<Alias<Class>, D> &&unq_ptr,
|
||||
bool /*need_alias*/) {
|
||||
auto *ptr = unq_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
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 <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&shd_ptr, bool need_alias) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||
auto *ptr = shd_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
|
||||
throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee "
|
||||
"is not an alias instance");
|
||||
}
|
||||
auto smhldr = smart_holder::from_shared_ptr(shd_ptr);
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h,
|
||||
std::shared_ptr<Alias<Class>> &&shd_ptr,
|
||||
bool /*need_alias*/) {
|
||||
auto *ptr = shd_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
auto smhldr = smart_holder::from_shared_ptr(shd_ptr);
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
// Implementing class for py::init<...>()
|
||||
template <typename... Args>
|
||||
struct constructor {
|
||||
|
@ -479,7 +410,7 @@ struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> {
|
|||
|
||||
template <typename Class, typename... Extra>
|
||||
void execute(Class &cl, const Extra &...extra) && {
|
||||
cl.def("__getstate__", std::move(get), pos_only());
|
||||
cl.def("__getstate__", std::move(get));
|
||||
|
||||
#if defined(PYBIND11_CPP14)
|
||||
cl.def(
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
# include <pybind11/gil.h>
|
||||
#endif
|
||||
|
||||
#include <pybind11/conduit/pybind11_platform_abi_id.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
|
||||
#include <exception>
|
||||
|
@ -37,12 +36,18 @@
|
|||
/// further ABI-incompatible changes may be made before the ABI is officially
|
||||
/// changed to the new version.
|
||||
#ifndef PYBIND11_INTERNALS_VERSION
|
||||
# define PYBIND11_INTERNALS_VERSION 7
|
||||
# if 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
|
||||
# else
|
||||
# define PYBIND11_INTERNALS_VERSION 4
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if PYBIND11_INTERNALS_VERSION < 7
|
||||
# error "PYBIND11_INTERNALS_VERSION 7 is the minimum for all platforms for pybind11v3."
|
||||
#endif
|
||||
// This requirement is mainly to reduce the support burden (see PR #4570).
|
||||
static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5,
|
||||
"pybind11 ABI version 5 is the minimum for Python 3.12+");
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
|
@ -61,29 +66,40 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
|
|||
// Thread Specific Storage (TSS) API.
|
||||
// Avoid unnecessary allocation of `Py_tss_t`, since we cannot use
|
||||
// `Py_LIMITED_API` anyway.
|
||||
#define PYBIND11_TLS_KEY_REF Py_tss_t &
|
||||
#if defined(__clang__)
|
||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||
_Pragma("clang diagnostic push") /**/ \
|
||||
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||
Py_tss_t var \
|
||||
= Py_tss_NEEDS_INIT; \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||
_Pragma("GCC diagnostic push") /**/ \
|
||||
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||
Py_tss_t var \
|
||||
= Py_tss_NEEDS_INIT; \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
# define PYBIND11_TLS_KEY_REF Py_tss_t &
|
||||
# if defined(__clang__)
|
||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||
_Pragma("clang diagnostic push") /**/ \
|
||||
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||
Py_tss_t var \
|
||||
= Py_tss_NEEDS_INIT; \
|
||||
_Pragma("clang diagnostic pop")
|
||||
# elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||
_Pragma("GCC diagnostic push") /**/ \
|
||||
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||
Py_tss_t var \
|
||||
= Py_tss_NEEDS_INIT; \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
# else
|
||||
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
|
||||
# endif
|
||||
# define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0)
|
||||
# define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key))
|
||||
# define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value))
|
||||
# define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr)
|
||||
# define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key))
|
||||
#else
|
||||
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t var = Py_tss_NEEDS_INIT;
|
||||
# define PYBIND11_TLS_KEY_REF Py_tss_t *
|
||||
# define PYBIND11_TLS_KEY_INIT(var) Py_tss_t *var = nullptr;
|
||||
# define PYBIND11_TLS_KEY_CREATE(var) \
|
||||
(((var) = PyThread_tss_alloc()) != nullptr && (PyThread_tss_create((var)) == 0))
|
||||
# define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get((key))
|
||||
# define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set((key), (value))
|
||||
# define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set((key), nullptr)
|
||||
# define PYBIND11_TLS_FREE(key) PyThread_tss_free(key)
|
||||
#endif
|
||||
#define PYBIND11_TLS_KEY_CREATE(var) (PyThread_tss_create(&(var)) == 0)
|
||||
#define PYBIND11_TLS_GET_VALUE(key) PyThread_tss_get(&(key))
|
||||
#define PYBIND11_TLS_REPLACE_VALUE(key, value) PyThread_tss_set(&(key), (value))
|
||||
#define PYBIND11_TLS_DELETE_VALUE(key) PyThread_tss_set(&(key), nullptr)
|
||||
#define PYBIND11_TLS_FREE(key) PyThread_tss_delete(&(key))
|
||||
|
||||
// Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly
|
||||
// other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module
|
||||
|
@ -91,7 +107,8 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
|
|||
// libstdc++, this doesn't happen: equality and the type_index hash are based on the type name,
|
||||
// which works. If not under a known-good stl, provide our own name-based hash and equality
|
||||
// functions that use the type name.
|
||||
#if !defined(_LIBCPP_VERSION)
|
||||
#if (PYBIND11_INTERNALS_VERSION <= 4 && defined(__GLIBCXX__)) \
|
||||
|| (PYBIND11_INTERNALS_VERSION >= 5 && !defined(_LIBCPP_VERSION))
|
||||
inline bool same_type(const std::type_info &lhs, const std::type_info &rhs) { return lhs == rhs; }
|
||||
using type_hash = std::hash<std::type_index>;
|
||||
using type_equal_to = std::equal_to<std::type_index>;
|
||||
|
@ -160,7 +177,6 @@ static_assert(sizeof(instance_map_shard) % 64 == 0,
|
|||
struct internals {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
pymutex mutex;
|
||||
pymutex exception_translator_mutex;
|
||||
#endif
|
||||
// std::type_index -> pybind11's type information
|
||||
type_map<type_info *> registered_types_cpp;
|
||||
|
@ -179,26 +195,35 @@ struct internals {
|
|||
std::forward_list<ExceptionTranslator> registered_exception_translators;
|
||||
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
|
||||
// extensions
|
||||
std::forward_list<std::string> static_strings; // Stores the std::strings backing
|
||||
// detail::c_str()
|
||||
#if PYBIND11_INTERNALS_VERSION == 4
|
||||
std::vector<PyObject *> unused_loader_patient_stack_remove_at_v5;
|
||||
#endif
|
||||
std::forward_list<std::string> static_strings; // Stores the std::strings backing
|
||||
// detail::c_str()
|
||||
PyTypeObject *static_property_type;
|
||||
PyTypeObject *default_metaclass;
|
||||
PyObject *instance_base;
|
||||
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
|
||||
PYBIND11_TLS_KEY_INIT(tstate)
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
|
||||
#endif // PYBIND11_INTERNALS_VERSION > 4
|
||||
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
|
||||
PyInterpreterState *istate = nullptr;
|
||||
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
// Note that we have to use a std::string to allocate memory to ensure a unique address
|
||||
// We want unique addresses since we use pointer equality to compare function records
|
||||
std::string function_record_capsule_name = internals_function_record_capsule_name;
|
||||
#endif
|
||||
|
||||
internals() = default;
|
||||
internals(const internals &other) = delete;
|
||||
internals &operator=(const internals &other) = delete;
|
||||
~internals() {
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
PYBIND11_TLS_FREE(loader_life_support_tls_key);
|
||||
#endif // PYBIND11_INTERNALS_VERSION > 4
|
||||
|
||||
// This destructor is called *after* Py_Finalize() in finalize_interpreter().
|
||||
// That *SHOULD BE* fine. The following details what happens when PyThread_tss_free is
|
||||
|
@ -211,17 +236,6 @@ struct internals {
|
|||
}
|
||||
};
|
||||
|
||||
// For backwards compatibility (i.e. #ifdef guards):
|
||||
#define PYBIND11_HAS_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,
|
||||
};
|
||||
|
||||
/// Additional type information which does not fit into the PyTypeObject.
|
||||
/// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
|
||||
struct type_info {
|
||||
|
@ -237,7 +251,6 @@ struct type_info {
|
|||
buffer_info *(*get_buffer)(PyObject *, void *) = nullptr;
|
||||
void *get_buffer_data = nullptr;
|
||||
void *(*module_local_load)(PyObject *, const type_info *) = nullptr;
|
||||
holder_enum_t holder_enum_v = holder_enum_t::undefined;
|
||||
/* A simple type never occurs as a (direct or indirect) parent
|
||||
* of a class that makes use of multiple inheritance.
|
||||
* A type can be simple even if it has non-simple ancestors as long as it has no descendants.
|
||||
|
@ -245,17 +258,80 @@ struct type_info {
|
|||
bool simple_type : 1;
|
||||
/* True if there is no multiple inheritance in this type's inheritance tree */
|
||||
bool simple_ancestors : 1;
|
||||
/* for base vs derived holder_type checks */
|
||||
bool default_holder : 1;
|
||||
/* true if this is a type registered with py::module_local */
|
||||
bool module_local : 1;
|
||||
};
|
||||
|
||||
/// On MSVC, debug and release builds are not ABI-compatible!
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
# define PYBIND11_BUILD_TYPE "_debug"
|
||||
#else
|
||||
# define PYBIND11_BUILD_TYPE ""
|
||||
#endif
|
||||
|
||||
/// Let's assume that different compilers are ABI-incompatible.
|
||||
/// A user can manually set this string if they know their
|
||||
/// compiler is compatible.
|
||||
#ifndef PYBIND11_COMPILER_TYPE
|
||||
# if defined(_MSC_VER)
|
||||
# define PYBIND11_COMPILER_TYPE "_msvc"
|
||||
# elif defined(__INTEL_COMPILER)
|
||||
# define PYBIND11_COMPILER_TYPE "_icc"
|
||||
# elif defined(__clang__)
|
||||
# define PYBIND11_COMPILER_TYPE "_clang"
|
||||
# elif defined(__PGI)
|
||||
# define PYBIND11_COMPILER_TYPE "_pgi"
|
||||
# elif defined(__MINGW32__)
|
||||
# define PYBIND11_COMPILER_TYPE "_mingw"
|
||||
# elif defined(__CYGWIN__)
|
||||
# define PYBIND11_COMPILER_TYPE "_gcc_cygwin"
|
||||
# elif defined(__GNUC__)
|
||||
# define PYBIND11_COMPILER_TYPE "_gcc"
|
||||
# else
|
||||
# define PYBIND11_COMPILER_TYPE "_unknown"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/// Also standard libs
|
||||
#ifndef PYBIND11_STDLIB
|
||||
# if defined(_LIBCPP_VERSION)
|
||||
# define PYBIND11_STDLIB "_libcpp"
|
||||
# elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
|
||||
# define PYBIND11_STDLIB "_libstdcpp"
|
||||
# else
|
||||
# define PYBIND11_STDLIB ""
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility.
|
||||
/// On MSVC, changes in _MSC_VER may indicate ABI incompatibility (#2898).
|
||||
#ifndef PYBIND11_BUILD_ABI
|
||||
# if defined(__GXX_ABI_VERSION)
|
||||
# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_TOSTRING(__GXX_ABI_VERSION)
|
||||
# elif defined(_MSC_VER)
|
||||
# define PYBIND11_BUILD_ABI "_mscver" PYBIND11_TOSTRING(_MSC_VER)
|
||||
# else
|
||||
# define PYBIND11_BUILD_ABI ""
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef PYBIND11_INTERNALS_KIND
|
||||
# define PYBIND11_INTERNALS_KIND ""
|
||||
#endif
|
||||
|
||||
#define PYBIND11_PLATFORM_ABI_ID \
|
||||
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \
|
||||
PYBIND11_BUILD_TYPE
|
||||
|
||||
#define PYBIND11_INTERNALS_ID \
|
||||
"__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
|
||||
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
|
||||
PYBIND11_PLATFORM_ABI_ID "__"
|
||||
|
||||
#define PYBIND11_MODULE_LOCAL_ID \
|
||||
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
|
||||
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
|
||||
PYBIND11_PLATFORM_ABI_ID "__"
|
||||
|
||||
/// Each module locally stores a pointer to the `internals` data. The data
|
||||
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.
|
||||
|
@ -373,7 +449,7 @@ inline void translate_local_exception(std::exception_ptr p) {
|
|||
|
||||
inline object get_python_state_dict() {
|
||||
object state_dict;
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION)
|
||||
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
|
||||
#else
|
||||
# if PY_VERSION_HEX < 0x03090000
|
||||
|
@ -471,12 +547,13 @@ PYBIND11_NOINLINE internals &get_internals() {
|
|||
}
|
||||
PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate);
|
||||
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
|
||||
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) {
|
||||
pybind11_fail("get_internals: could not successfully initialize the "
|
||||
"loader_life_support TSS key!");
|
||||
}
|
||||
|
||||
#endif
|
||||
internals_ptr->istate = tstate->interp;
|
||||
state_dict[PYBIND11_INTERNALS_ID] = capsule(reinterpret_cast<void *>(internals_pp));
|
||||
internals_ptr->registered_exception_translators.push_front(&translate_exception);
|
||||
|
@ -506,6 +583,40 @@ PYBIND11_NOINLINE internals &get_internals() {
|
|||
struct local_internals {
|
||||
type_map<type_info *> registered_types_cpp;
|
||||
std::forward_list<ExceptionTranslator> registered_exception_translators;
|
||||
#if PYBIND11_INTERNALS_VERSION == 4
|
||||
|
||||
// For ABI compatibility, we can't store the loader_life_support TLS key in
|
||||
// the `internals` struct directly. Instead, we store it in `shared_data` and
|
||||
// cache a copy in `local_internals`. If we allocated a separate TLS key for
|
||||
// each instance of `local_internals`, we could end up allocating hundreds of
|
||||
// TLS keys if hundreds of different pybind11 modules are loaded (which is a
|
||||
// plausible number).
|
||||
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
|
||||
|
||||
// Holds the shared TLS key for the loader_life_support stack.
|
||||
struct shared_loader_life_support_data {
|
||||
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
|
||||
shared_loader_life_support_data() {
|
||||
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
|
||||
if (!PYBIND11_TLS_KEY_CREATE(loader_life_support_tls_key)) {
|
||||
pybind11_fail("local_internals: could not successfully initialize the "
|
||||
"loader_life_support TLS key!");
|
||||
}
|
||||
}
|
||||
// We can't help but leak the TLS key, because Python never unloads extension modules.
|
||||
};
|
||||
|
||||
local_internals() {
|
||||
auto &internals = get_internals();
|
||||
// Get or create the `loader_life_support_stack_key`.
|
||||
auto &ptr = internals.shared_data["_life_support"];
|
||||
if (!ptr) {
|
||||
ptr = new shared_loader_life_support_data;
|
||||
}
|
||||
loader_life_support_tls_key
|
||||
= static_cast<shared_loader_life_support_data *>(ptr)->loader_life_support_tls_key;
|
||||
}
|
||||
#endif // PYBIND11_INTERNALS_VERSION == 4
|
||||
};
|
||||
|
||||
/// Works like `get_internals`, but for things which are locally registered.
|
||||
|
@ -532,19 +643,6 @@ inline auto with_internals(const F &cb) -> decltype(cb(get_internals())) {
|
|||
return cb(internals);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline auto with_exception_translators(const F &cb)
|
||||
-> decltype(cb(get_internals().registered_exception_translators,
|
||||
get_local_internals().registered_exception_translators)) {
|
||||
auto &internals = get_internals();
|
||||
#ifdef Py_GIL_DISABLED
|
||||
std::unique_lock<pymutex> lock((internals).exception_translator_mutex);
|
||||
#endif
|
||||
auto &local_internals = get_local_internals();
|
||||
return cb(internals.registered_exception_translators,
|
||||
local_internals.registered_exception_translators);
|
||||
}
|
||||
|
||||
inline std::uint64_t mix64(std::uint64_t z) {
|
||||
// David Stafford's variant 13 of the MurmurHash3 finalizer popularized
|
||||
// by the SplitMix PRNG.
|
||||
|
@ -555,8 +653,8 @@ inline std::uint64_t mix64(std::uint64_t z) {
|
|||
}
|
||||
|
||||
template <typename F>
|
||||
inline auto with_instance_map(const void *ptr, const F &cb)
|
||||
-> decltype(cb(std::declval<instance_map &>())) {
|
||||
inline auto with_instance_map(const void *ptr,
|
||||
const F &cb) -> decltype(cb(std::declval<instance_map &>())) {
|
||||
auto &internals = get_internals();
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
|
@ -611,8 +709,7 @@ const char *c_str(Args &&...args) {
|
|||
}
|
||||
|
||||
inline const char *get_function_record_capsule_name() {
|
||||
// On GraalPy, pointer equality of the names is currently not guaranteed
|
||||
#if !defined(GRAALVM_PYTHON)
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
return get_internals().function_record_capsule_name.c_str();
|
||||
#else
|
||||
return nullptr;
|
||||
|
|
|
@ -1,349 +0,0 @@
|
|||
// Copyright (c) 2020-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.
|
||||
|
||||
/* Proof-of-Concept for smart pointer interoperability.
|
||||
|
||||
High-level aspects:
|
||||
|
||||
* Support all `unique_ptr`, `shared_ptr` interops that are feasible.
|
||||
|
||||
* Cleanly and clearly report all interops that are infeasible.
|
||||
|
||||
* Meant to fit into a `PyObject`, as a holder for C++ objects.
|
||||
|
||||
* Support a system design that makes it impossible to trigger
|
||||
C++ Undefined Behavior, especially from Python.
|
||||
|
||||
* Support a system design with clean runtime inheritance casting. From this
|
||||
it follows that the `smart_holder` needs to be type-erased (`void*`).
|
||||
|
||||
* Handling of RTTI for the type-erased held pointer is NOT implemented here.
|
||||
It is the responsibility of the caller to ensure that `static_cast<T *>`
|
||||
is well-formed when calling `as_*` member functions. Inheritance casting
|
||||
needs to be handled in a different layer (similar to the code organization
|
||||
in boost/python/object/inheritance.hpp).
|
||||
|
||||
Details:
|
||||
|
||||
* The "root holder" chosen here is a `shared_ptr<void>` (named `vptr` in this
|
||||
implementation). This choice is practically inevitable because `shared_ptr`
|
||||
has only very limited support for inspecting and accessing its deleter.
|
||||
|
||||
* If created from a raw pointer, or a `unique_ptr` without a custom deleter,
|
||||
`vptr` always uses a custom deleter, to support `unique_ptr`-like disowning.
|
||||
The custom deleters could be extended to included life-time management for
|
||||
external objects (e.g. `PyObject`).
|
||||
|
||||
* If created from an external `shared_ptr`, or a `unique_ptr` with a custom
|
||||
deleter, including life-time management for external objects is infeasible.
|
||||
|
||||
* By choice, the smart_holder is movable but not copyable, to keep the design
|
||||
simple, and to guard against accidental copying overhead.
|
||||
|
||||
* The `void_cast_raw_ptr` option is needed to make the `smart_holder` `vptr`
|
||||
member invisible to the `shared_from_this` mechanism, in case the lifetime
|
||||
of a `PyObject` is tied to the pointee.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
// pybindit = Python Bindings Innovation Track.
|
||||
// Currently not in pybind11 namespace to signal that this POC does not depend
|
||||
// on any existing pybind11 functionality.
|
||||
namespace pybindit {
|
||||
namespace memory {
|
||||
|
||||
static constexpr bool type_has_shared_from_this(...) { return false; }
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this<T> *) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct guarded_delete {
|
||||
std::weak_ptr<void> released_ptr; // Trick to keep the smart_holder memory footprint small.
|
||||
std::function<void(void *)> del_fun; // Rare case.
|
||||
void (*del_ptr)(void *); // Common case.
|
||||
bool use_del_fun;
|
||||
bool armed_flag;
|
||||
guarded_delete(std::function<void(void *)> &&del_fun, bool armed_flag)
|
||||
: del_fun{std::move(del_fun)}, del_ptr{nullptr}, use_del_fun{true},
|
||||
armed_flag{armed_flag} {}
|
||||
guarded_delete(void (*del_ptr)(void *), bool armed_flag)
|
||||
: del_ptr{del_ptr}, use_del_fun{false}, armed_flag{armed_flag} {}
|
||||
void operator()(void *raw_ptr) const {
|
||||
if (armed_flag) {
|
||||
if (use_del_fun) {
|
||||
del_fun(raw_ptr);
|
||||
} else {
|
||||
del_ptr(raw_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename std::enable_if<std::is_destructible<T>::value, int>::type = 0>
|
||||
inline void builtin_delete_if_destructible(void *raw_ptr) {
|
||||
std::default_delete<T>{}(static_cast<T *>(raw_ptr));
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<!std::is_destructible<T>::value, int>::type = 0>
|
||||
inline void builtin_delete_if_destructible(void *) {
|
||||
// This noop operator is needed to avoid a compilation error (for `delete raw_ptr;`), but
|
||||
// throwing an exception from a destructor will std::terminate the process. Therefore the
|
||||
// runtime check for lifetime-management correctness is implemented elsewhere (in
|
||||
// ensure_pointee_is_destructible()).
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
guarded_delete make_guarded_builtin_delete(bool armed_flag) {
|
||||
return guarded_delete(builtin_delete_if_destructible<T>, armed_flag);
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
struct custom_deleter {
|
||||
D deleter;
|
||||
explicit custom_deleter(D &&deleter) : deleter{std::forward<D>(deleter)} {}
|
||||
void operator()(void *raw_ptr) { deleter(static_cast<T *>(raw_ptr)); }
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) {
|
||||
return guarded_delete(
|
||||
std::function<void(void *)>(custom_deleter<T, D>(std::forward<D>(uqp_del))), armed_flag);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
|
||||
return rtti_deleter == typeid(std::default_delete<T>)
|
||||
|| rtti_deleter == typeid(std::default_delete<T const>);
|
||||
}
|
||||
|
||||
struct smart_holder {
|
||||
const std::type_info *rtti_uqp_del = nullptr;
|
||||
std::shared_ptr<void> vptr;
|
||||
bool vptr_is_using_noop_deleter : 1;
|
||||
bool vptr_is_using_builtin_delete : 1;
|
||||
bool vptr_is_external_shared_ptr : 1;
|
||||
bool is_populated : 1;
|
||||
bool is_disowned : 1;
|
||||
|
||||
// Design choice: smart_holder is movable but not copyable.
|
||||
smart_holder(smart_holder &&) = default;
|
||||
smart_holder(const smart_holder &) = delete;
|
||||
smart_holder &operator=(smart_holder &&) = delete;
|
||||
smart_holder &operator=(const smart_holder &) = delete;
|
||||
|
||||
smart_holder()
|
||||
: vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
|
||||
vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {}
|
||||
|
||||
bool has_pointee() const { return vptr != nullptr; }
|
||||
|
||||
template <typename T>
|
||||
static void ensure_pointee_is_destructible(const char *context) {
|
||||
if (!std::is_destructible<T>::value) {
|
||||
throw std::invalid_argument(std::string("Pointee is not destructible (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_is_populated(const char *context) const {
|
||||
if (!is_populated) {
|
||||
throw std::runtime_error(std::string("Unpopulated holder (") + context + ").");
|
||||
}
|
||||
}
|
||||
void ensure_is_not_disowned(const char *context) const {
|
||||
if (is_disowned) {
|
||||
throw std::runtime_error(std::string("Holder was disowned already (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_vptr_is_using_builtin_delete(const char *context) const {
|
||||
if (vptr_is_external_shared_ptr) {
|
||||
throw std::invalid_argument(std::string("Cannot disown external shared_ptr (")
|
||||
+ context + ").");
|
||||
}
|
||||
if (vptr_is_using_noop_deleter) {
|
||||
throw std::invalid_argument(std::string("Cannot disown non-owning holder (") + context
|
||||
+ ").");
|
||||
}
|
||||
if (!vptr_is_using_builtin_delete) {
|
||||
throw std::invalid_argument(std::string("Cannot disown custom deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
void ensure_compatible_rtti_uqp_del(const char *context) const {
|
||||
const std::type_info *rtti_requested = &typeid(D);
|
||||
if (!rtti_uqp_del) {
|
||||
if (!is_std_default_delete<T>(*rtti_requested)) {
|
||||
throw std::invalid_argument(std::string("Missing unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
} else if (!(*rtti_requested == *rtti_uqp_del)
|
||||
&& !(vptr_is_using_builtin_delete
|
||||
&& is_std_default_delete<T>(*rtti_requested))) {
|
||||
throw std::invalid_argument(std::string("Incompatible unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_has_pointee(const char *context) const {
|
||||
if (!has_pointee()) {
|
||||
throw std::invalid_argument(std::string("Disowned holder (") + context + ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_use_count_1(const char *context) const {
|
||||
if (vptr == nullptr) {
|
||||
throw std::invalid_argument(std::string("Cannot disown nullptr (") + context + ").");
|
||||
}
|
||||
// In multithreaded environments accessing use_count can lead to
|
||||
// race conditions, but in the context of Python it is a bug (elsewhere)
|
||||
// if the Global Interpreter Lock (GIL) is not being held when this code
|
||||
// is reached.
|
||||
// PYBIND11:REMINDER: This may need to be protected by a mutex in free-threaded Python.
|
||||
if (vptr.use_count() != 1) {
|
||||
throw std::invalid_argument(std::string("Cannot disown use_count != 1 (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void reset_vptr_deleter_armed_flag(bool armed_flag) const {
|
||||
auto *vptr_del_ptr = std::get_deleter<guarded_delete>(vptr);
|
||||
if (vptr_del_ptr == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"smart_holder::reset_vptr_deleter_armed_flag() called in an invalid context.");
|
||||
}
|
||||
vptr_del_ptr->armed_flag = armed_flag;
|
||||
}
|
||||
|
||||
// Caller is responsible for precondition: ensure_compatible_rtti_uqp_del<T, D>() must succeed.
|
||||
template <typename T, typename D>
|
||||
std::unique_ptr<D> extract_deleter(const char *context) const {
|
||||
const auto *gd = std::get_deleter<guarded_delete>(vptr);
|
||||
if (gd && gd->use_del_fun) {
|
||||
const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter<T, D>>();
|
||||
if (custom_deleter_ptr == nullptr) {
|
||||
throw std::runtime_error(
|
||||
std::string("smart_holder::extract_deleter() precondition failure (") + context
|
||||
+ ").");
|
||||
}
|
||||
static_assert(std::is_copy_constructible<D>::value,
|
||||
"Required for compatibility with smart_holder functionality.");
|
||||
return std::unique_ptr<D>(new D(custom_deleter_ptr->deleter));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static smart_holder from_raw_ptr_unowned(void *raw_ptr) {
|
||||
smart_holder hld;
|
||||
hld.vptr.reset(raw_ptr, [](void *) {});
|
||||
hld.vptr_is_using_noop_deleter = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *as_raw_ptr_unowned() const {
|
||||
return static_cast<T *>(vptr.get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) {
|
||||
ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership");
|
||||
smart_holder hld;
|
||||
auto gd = make_guarded_builtin_delete<T>(true);
|
||||
if (void_cast_raw_ptr) {
|
||||
hld.vptr.reset(static_cast<void *>(raw_ptr), std::move(gd));
|
||||
} else {
|
||||
hld.vptr.reset(raw_ptr, std::move(gd));
|
||||
}
|
||||
hld.vptr_is_using_builtin_delete = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void disown() {
|
||||
reset_vptr_deleter_armed_flag(false);
|
||||
is_disowned = true;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void reclaim_disowned() {
|
||||
reset_vptr_deleter_armed_flag(true);
|
||||
is_disowned = false;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void release_disowned() { vptr.reset(); }
|
||||
|
||||
void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") const {
|
||||
ensure_is_not_disowned(context);
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
ensure_use_count_1(context);
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void release_ownership() {
|
||||
reset_vptr_deleter_armed_flag(false);
|
||||
release_disowned();
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
|
||||
void *void_ptr = nullptr) {
|
||||
smart_holder hld;
|
||||
hld.rtti_uqp_del = &typeid(D);
|
||||
hld.vptr_is_using_builtin_delete = is_std_default_delete<T>(*hld.rtti_uqp_del);
|
||||
guarded_delete gd{nullptr, false};
|
||||
if (hld.vptr_is_using_builtin_delete) {
|
||||
gd = make_guarded_builtin_delete<T>(true);
|
||||
} else {
|
||||
gd = make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
|
||||
}
|
||||
if (void_ptr != nullptr) {
|
||||
hld.vptr.reset(void_ptr, std::move(gd));
|
||||
} else {
|
||||
hld.vptr.reset(unq_ptr.get(), std::move(gd));
|
||||
}
|
||||
(void) unq_ptr.release();
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static smart_holder from_shared_ptr(std::shared_ptr<T> shd_ptr) {
|
||||
smart_holder hld;
|
||||
hld.vptr = std::static_pointer_cast<void>(shd_ptr);
|
||||
hld.vptr_is_external_shared_ptr = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> as_shared_ptr() const {
|
||||
return std::static_pointer_cast<T>(vptr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memory
|
||||
} // namespace pybindit
|
|
@ -9,17 +9,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/gil.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
#include <pybind11/trampoline_self_life_support.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "cpp_conduit.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 <cstdint>
|
||||
|
@ -47,7 +43,11 @@ private:
|
|||
|
||||
// Store stack pointer in thread-local storage.
|
||||
static PYBIND11_TLS_KEY_REF get_stack_tls_key() {
|
||||
#if PYBIND11_INTERNALS_VERSION == 4
|
||||
return get_local_internals().loader_life_support_tls_key;
|
||||
#else
|
||||
return get_internals().loader_life_support_tls_key;
|
||||
#endif
|
||||
}
|
||||
static loader_life_support *get_stack_top() {
|
||||
return static_cast<loader_life_support *>(PYBIND11_TLS_GET_VALUE(get_stack_tls_key()));
|
||||
|
@ -117,6 +117,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
|
|||
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
|
||||
check.push_back((PyTypeObject *) parent.ptr());
|
||||
}
|
||||
|
||||
auto const &type_dict = get_internals().registered_types_py;
|
||||
for (size_t i = 0; i < check.size(); i++) {
|
||||
auto *type = check[i];
|
||||
|
@ -175,7 +176,13 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
|
|||
* The value is cached for the lifetime of the Python type.
|
||||
*/
|
||||
inline const std::vector<detail::type_info *> &all_type_info(PyTypeObject *type) {
|
||||
return all_type_info_get_cache(type).first->second;
|
||||
auto ins = all_type_info_get_cache(type);
|
||||
if (ins.second) {
|
||||
// New cache entry: populate it
|
||||
all_type_info_populate(type, ins.first->second);
|
||||
}
|
||||
|
||||
return ins.first->second;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,49 +248,6 @@ PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if
|
|||
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
|
||||
}
|
||||
|
||||
inline bool try_incref(PyObject *obj) {
|
||||
// Tries to increment the reference count of an object if it's not zero.
|
||||
// TODO: Use PyUnstable_TryIncref when available.
|
||||
// See https://github.com/python/cpython/issues/128844
|
||||
#ifdef Py_GIL_DISABLED
|
||||
// See
|
||||
// https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761
|
||||
uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local);
|
||||
local += 1;
|
||||
if (local == 0) {
|
||||
// immortal
|
||||
return true;
|
||||
}
|
||||
if (_Py_IsOwnedByCurrentThread(obj)) {
|
||||
_Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local);
|
||||
# ifdef Py_REF_DEBUG
|
||||
_Py_INCREF_IncRefTotal();
|
||||
# endif
|
||||
return true;
|
||||
}
|
||||
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
|
||||
for (;;) {
|
||||
// If the shared refcount is zero and the object is either merged
|
||||
// or may not have weak references, then we cannot incref it.
|
||||
if (shared == 0 || shared == _Py_REF_MERGED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_Py_atomic_compare_exchange_ssize(
|
||||
&obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) {
|
||||
# ifdef Py_REF_DEBUG
|
||||
_Py_INCREF_IncRefTotal();
|
||||
# endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
assert(Py_REFCNT(obj) > 0);
|
||||
Py_INCREF(obj);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Searches the inheritance graph for a registered Python instance, using all_type_info().
|
||||
PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
|
||||
const detail::type_info *tinfo) {
|
||||
|
@ -292,10 +256,7 @@ PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
|
|||
for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) {
|
||||
for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) {
|
||||
if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) {
|
||||
auto *wrapper = reinterpret_cast<PyObject *>(it_i->second);
|
||||
if (try_incref(wrapper)) {
|
||||
return handle(wrapper);
|
||||
}
|
||||
return handle((PyObject *) it_i->second).inc_ref();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -498,7 +459,7 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
|
|||
}
|
||||
|
||||
inline PyThreadState *get_thread_state_unchecked() {
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
#if defined(PYPY_VERSION)
|
||||
return PyThreadState_GET();
|
||||
#elif PY_VERSION_HEX < 0x030D0000
|
||||
return _PyThreadState_UncheckedGet();
|
||||
|
@ -511,361 +472,6 @@ inline PyThreadState *get_thread_state_unchecked() {
|
|||
void keep_alive_impl(handle nurse, handle patient);
|
||||
inline PyObject *make_new_instance(PyTypeObject *type);
|
||||
|
||||
// PYBIND11:REMINDER: 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<smart_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<pybindit::memory::guarded_delete>(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<void>();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &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<void *>(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<trampoline_self_life_support>(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<smart_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<object>(make_new_instance(tinfo->type));
|
||||
auto *inst_raw_ptr = reinterpret_cast<instance *>(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<void *>(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). 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<const void *>(&smhldr));
|
||||
|
||||
if (policy == return_value_policy::reference_internal) {
|
||||
keep_alive_impl(inst, parent);
|
||||
}
|
||||
|
||||
return inst.release();
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
handle smart_holder_from_unique_ptr(std::unique_ptr<T const, D> &&src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &st) {
|
||||
return smart_holder_from_unique_ptr(
|
||||
std::unique_ptr<T, D>(const_cast<T *>(src.release()),
|
||||
std::move(src.get_deleter())), // Const2Mutbl
|
||||
policy,
|
||||
parent,
|
||||
st);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &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<void *>(src_raw_ptr);
|
||||
const detail::type_info *tinfo = st.second;
|
||||
if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
|
||||
// PYBIND11:REMINDER: MISSING: Enforcement of consistency with existing smart_holder.
|
||||
// PYBIND11:REMINDER: MISSING: keep_alive.
|
||||
return existing_inst;
|
||||
}
|
||||
|
||||
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
|
||||
auto *inst_raw_ptr = reinterpret_cast<instance *>(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<void>(src, const_cast<void *>(st.first)));
|
||||
tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr));
|
||||
|
||||
if (policy == return_value_policy::reference_internal) {
|
||||
keep_alive_impl(inst, parent);
|
||||
}
|
||||
|
||||
return inst.release();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
handle smart_holder_from_shared_ptr(const std::shared_ptr<T const> &src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &st) {
|
||||
return smart_holder_from_shared_ptr(std::const_pointer_cast<T>(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<PyObject *>(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 <typename T,
|
||||
typename D,
|
||||
typename std::enable_if<std::is_default_constructible<D>::value, int>::type = 0>
|
||||
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
|
||||
if (deleter == nullptr) {
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename D,
|
||||
typename std::enable_if<!std::is_default_constructible<D>::value, int>::type = 0>
|
||||
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&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<T, D>(raw_ptr, std::move(*deleter));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct load_helper : value_and_holder_helper {
|
||||
bool was_populated = false;
|
||||
bool python_instance_is_alias = false;
|
||||
|
||||
void maybe_set_python_instance_is_alias(handle src) {
|
||||
if (was_populated) {
|
||||
python_instance_is_alias = reinterpret_cast<instance *>(src.ptr())->is_alias;
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<T> make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) {
|
||||
return std::shared_ptr<T>(raw_ptr, shared_ptr_parent_life_support(parent.ptr()));
|
||||
}
|
||||
|
||||
std::shared_ptr<T> 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<T *>(void_raw_ptr),
|
||||
responsible_parent);
|
||||
}
|
||||
throw std::runtime_error("Non-owning holder (load_as_shared_ptr).");
|
||||
}
|
||||
auto *type_raw_ptr = static_cast<T *>(void_raw_ptr);
|
||||
if (python_instance_is_alias) {
|
||||
auto *vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr);
|
||||
if (vptr_gd_ptr != nullptr) {
|
||||
std::shared_ptr<void> released_ptr = vptr_gd_ptr->released_ptr.lock();
|
||||
if (released_ptr) {
|
||||
return std::shared_ptr<T>(released_ptr, type_raw_ptr);
|
||||
}
|
||||
std::shared_ptr<T> 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<shared_ptr_trampoline_self_life_support>(hld.vptr);
|
||||
if (sptsls_ptr != nullptr) {
|
||||
// This code is reachable only if there are multiple registered_instances for the
|
||||
// same pointee.
|
||||
if (reinterpret_cast<PyObject *>(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<T>(
|
||||
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> void_shd_ptr = hld.template as_shared_ptr<void>();
|
||||
return std::shared_ptr<T>(void_shd_ptr, type_raw_ptr);
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D> load_as_unique_ptr(void *raw_void_ptr,
|
||||
const char *context = "load_as_unique_ptr") {
|
||||
if (!have_holder()) {
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
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<T, D>(context);
|
||||
holder().ensure_use_count_1(context);
|
||||
|
||||
T *raw_type_ptr = static_cast<T *>(raw_void_ptr);
|
||||
|
||||
auto *self_life_support
|
||||
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(raw_type_ptr);
|
||||
if (self_life_support == nullptr && python_instance_is_alias) {
|
||||
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++.");
|
||||
}
|
||||
|
||||
std::unique_ptr<D> extracted_deleter = holder().template extract_deleter<T, D>(context);
|
||||
|
||||
// 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<T, D>(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;
|
||||
}
|
||||
|
||||
// This assumes load_as_shared_ptr succeeded(), and the returned shared_ptr is still alive.
|
||||
// The returned unique_ptr is meant to never expire (the behavior is undefined otherwise).
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D>
|
||||
load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") {
|
||||
if (!have_holder()) {
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
return unique_with_deleter<T, D>(
|
||||
raw_type_ptr, std::move(holder().template extract_deleter<T, D>(context)));
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(smart_holder_type_caster_support)
|
||||
|
||||
class type_caster_generic {
|
||||
public:
|
||||
PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info)
|
||||
|
@ -970,15 +576,6 @@ public:
|
|||
|
||||
// Base methods for generic caster; there are overridden in copyable_holder_caster
|
||||
void load_value(value_and_holder &&v_h) {
|
||||
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<void>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto *&vptr = v_h.value_ptr();
|
||||
// Lazy allocation for unallocated values:
|
||||
if (vptr == nullptr) {
|
||||
|
@ -1564,14 +1161,14 @@ protected:
|
|||
does not have a private operator new implementation. A comma operator is used in the
|
||||
decltype argument to apply SFINAE to the public copy/move constructors.*/
|
||||
template <typename T, typename = enable_if_t<is_copy_constructible<T>::value>>
|
||||
static auto make_copy_constructor(const T *)
|
||||
-> decltype(new T(std::declval<const T>()), Constructor{}) {
|
||||
static auto make_copy_constructor(const T *) -> decltype(new T(std::declval<const T>()),
|
||||
Constructor{}) {
|
||||
return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); };
|
||||
}
|
||||
|
||||
template <typename T, typename = enable_if_t<is_move_constructible<T>::value>>
|
||||
static auto make_move_constructor(const T *)
|
||||
-> decltype(new T(std::declval<T &&>()), Constructor{}) {
|
||||
static auto make_move_constructor(const T *) -> decltype(new T(std::declval<T &&>()),
|
||||
Constructor{}) {
|
||||
return [](const void *arg) -> void * {
|
||||
return new T(std::move(*const_cast<T *>(reinterpret_cast<const T *>(arg))));
|
||||
};
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
// 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 "struct_smart_holder.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
using pybindit::memory::smart_holder;
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename H>
|
||||
using is_smart_holder = std::is_same<H, smart_holder>;
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
|
@ -7,7 +7,6 @@
|
|||
#include "common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <typeinfo>
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
|
|
@ -225,22 +225,19 @@ struct EigenProps {
|
|||
= !show_c_contiguous && show_order && requires_col_major;
|
||||
|
||||
static constexpr auto descriptor
|
||||
= const_name("typing.Annotated[")
|
||||
+ io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
|
||||
+ npy_format_descriptor<Scalar>::name + io_name("", "]") + const_name(", \"[")
|
||||
= const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + const_name("[")
|
||||
+ const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + const_name(", ")
|
||||
+ const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n"))
|
||||
+ const_name("]\"")
|
||||
+ const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + const_name("]")
|
||||
+
|
||||
// For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to
|
||||
// be satisfied: writeable=True (for a mutable reference), and, depending on the map's
|
||||
// stride options, possibly f_contiguous or c_contiguous. We include them in the
|
||||
// descriptor output to provide some hint as to why a TypeError is occurring (otherwise
|
||||
// it can be confusing to see that a function accepts a
|
||||
// 'typing.Annotated[numpy.typing.NDArray[numpy.float64], "[3,2]"]' and an error message
|
||||
// that you *gave* a numpy.ndarray of the right type and dimensions.
|
||||
+ const_name<show_writeable>(", \"flags.writeable\"", "")
|
||||
+ const_name<show_c_contiguous>(", \"flags.c_contiguous\"", "")
|
||||
+ const_name<show_f_contiguous>(", \"flags.f_contiguous\"", "") + const_name("]");
|
||||
// it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and
|
||||
// an error message that you *gave* a numpy.ndarray of the right type and dimensions.
|
||||
const_name<show_writeable>(", flags.writeable", "")
|
||||
+ const_name<show_c_contiguous>(", flags.c_contiguous", "")
|
||||
+ const_name<show_f_contiguous>(", flags.f_contiguous", "") + const_name("]");
|
||||
};
|
||||
|
||||
// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data,
|
||||
|
@ -319,11 +316,8 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
|
|||
return false;
|
||||
}
|
||||
|
||||
PYBIND11_WARNING_PUSH
|
||||
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // See PR #5516
|
||||
// Allocate the new type, then build a numpy reference into it
|
||||
value = Type(fits.rows, fits.cols);
|
||||
PYBIND11_WARNING_POP
|
||||
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
|
||||
if (dims == 1) {
|
||||
ref = ref.squeeze();
|
||||
|
@ -444,9 +438,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
// return_descr forces the use of NDArray instead of ArrayLike in args
|
||||
// since Ref<...> args can only accept arrays.
|
||||
static constexpr auto name = return_descr(props::descriptor);
|
||||
static constexpr auto name = props::descriptor;
|
||||
|
||||
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
|
||||
// types but not bound arguments). We still provide them (with an explicitly delete) so that
|
||||
|
|
|
@ -124,16 +124,13 @@ struct eigen_tensor_helper<
|
|||
template <typename Type, bool ShowDetails, bool NeedsWriteable = false>
|
||||
struct get_tensor_descriptor {
|
||||
static constexpr auto details
|
||||
= const_name<NeedsWriteable>(", \"flags.writeable\"", "") + const_name
|
||||
< static_cast<int>(Type::Layout)
|
||||
== static_cast<int>(Eigen::RowMajor)
|
||||
> (", \"flags.c_contiguous\"", ", \"flags.f_contiguous\"");
|
||||
= const_name<NeedsWriteable>(", flags.writeable", "")
|
||||
+ const_name<static_cast<int>(Type::Layout) == static_cast<int>(Eigen::RowMajor)>(
|
||||
", flags.c_contiguous", ", flags.f_contiguous");
|
||||
static constexpr auto value
|
||||
= const_name("typing.Annotated[")
|
||||
+ io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
|
||||
+ npy_format_descriptor<typename Type::Scalar>::name + io_name("", "]")
|
||||
+ const_name(", \"[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor
|
||||
+ const_name("]\"") + const_name<ShowDetails>(details, const_name("")) + const_name("]");
|
||||
= const_name("numpy.ndarray[") + npy_format_descriptor<typename Type::Scalar>::name
|
||||
+ const_name("[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor
|
||||
+ const_name("]") + const_name<ShowDetails>(details, const_name("")) + const_name("]");
|
||||
};
|
||||
|
||||
// When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes<T, 0> does not have the begin() member
|
||||
|
@ -505,10 +502,7 @@ protected:
|
|||
std::unique_ptr<MapType> value;
|
||||
|
||||
public:
|
||||
// return_descr forces the use of NDArray instead of ArrayLike since refs can only reference
|
||||
// arrays
|
||||
static constexpr auto name
|
||||
= return_descr(get_tensor_descriptor<Type, true, needs_writeable>::value);
|
||||
static constexpr auto name = get_tensor_descriptor<Type, true, needs_writeable>::value;
|
||||
explicit operator MapType *() { return value.get(); }
|
||||
explicit operator MapType &() { return *value; }
|
||||
explicit operator MapType &&() && { return std::move(*value); }
|
||||
|
|
|
@ -104,13 +104,23 @@ inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers,
|
|||
detail::precheck_interpreter();
|
||||
Py_InitializeEx(init_signal_handlers ? 1 : 0);
|
||||
|
||||
// Before it was special-cased in python 3.8, passing an empty or null argv
|
||||
// caused a segfault, so we have to reimplement the special case ourselves.
|
||||
bool special_case = (argv == nullptr || argc <= 0);
|
||||
|
||||
const char *const empty_argv[]{"\0"};
|
||||
const char *const *safe_argv = special_case ? empty_argv : argv;
|
||||
if (special_case) {
|
||||
argc = 1;
|
||||
}
|
||||
|
||||
auto argv_size = static_cast<size_t>(argc);
|
||||
// SetArgv* on python 3 takes wchar_t, so we have to convert.
|
||||
std::unique_ptr<wchar_t *[]> widened_argv(new wchar_t *[argv_size]);
|
||||
std::vector<std::unique_ptr<wchar_t[], detail::wide_char_arg_deleter>> widened_argv_entries;
|
||||
widened_argv_entries.reserve(argv_size);
|
||||
for (size_t ii = 0; ii < argv_size; ++ii) {
|
||||
widened_argv_entries.emplace_back(detail::widen_chars(argv[ii]));
|
||||
widened_argv_entries.emplace_back(detail::widen_chars(safe_argv[ii]));
|
||||
if (!widened_argv_entries.back()) {
|
||||
// A null here indicates a character-encoding failure or the python
|
||||
// interpreter out of memory. Give up.
|
||||
|
|
|
@ -19,7 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
|||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
inline void ensure_builtins_in_globals(object &global) {
|
||||
#if defined(PYPY_VERSION)
|
||||
#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x03080000
|
||||
// Running exec and eval adds `builtins` module under `__builtins__` key to
|
||||
// globals if not yet present. Python 3.8 made PyRun_String behave
|
||||
// similarly. Let's also do that for older versions, for consistency. This
|
||||
|
@ -94,18 +94,18 @@ void exec(const char (&s)[N], object global = globals(), object local = object()
|
|||
eval<eval_statements>(s, std::move(global), std::move(local));
|
||||
}
|
||||
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
#if defined(PYPY_VERSION)
|
||||
template <eval_mode mode = eval_statements>
|
||||
object eval_file(str, object, object) {
|
||||
pybind11_fail("eval_file not supported in this interpreter. Use eval");
|
||||
pybind11_fail("eval_file not supported in PyPy3. Use eval");
|
||||
}
|
||||
template <eval_mode mode = eval_statements>
|
||||
object eval_file(str, object) {
|
||||
pybind11_fail("eval_file not supported in this interpreter. Use eval");
|
||||
pybind11_fail("eval_file not supported in PyPy3. Use eval");
|
||||
}
|
||||
template <eval_mode mode = eval_statements>
|
||||
object eval_file(str) {
|
||||
pybind11_fail("eval_file not supported in this interpreter. Use eval");
|
||||
pybind11_fail("eval_file not supported in PyPy3. Use eval");
|
||||
}
|
||||
#else
|
||||
template <eval_mode mode = eval_statements>
|
||||
|
|
|
@ -147,7 +147,9 @@ public:
|
|||
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
|
||||
tstate = PyEval_SaveThread();
|
||||
if (disassoc) {
|
||||
auto key = internals.tstate; // NOLINT(readability-qualified-auto)
|
||||
// Python >= 3.7 can remove this, it's an int before 3.7
|
||||
// NOLINTNEXTLINE(readability-qualified-auto)
|
||||
auto key = internals.tstate;
|
||||
PYBIND11_TLS_DELETE_VALUE(key);
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +173,9 @@ public:
|
|||
PyEval_RestoreThread(tstate);
|
||||
}
|
||||
if (disassoc) {
|
||||
auto key = detail::get_internals().tstate; // NOLINT(readability-qualified-auto)
|
||||
// Python >= 3.7 can remove this, it's an int before 3.7
|
||||
// NOLINTNEXTLINE(readability-qualified-auto)
|
||||
auto key = detail::get_internals().tstate;
|
||||
PYBIND11_TLS_REPLACE_VALUE(key, tstate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,8 +46,6 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
|||
// get processed only when it is the main thread's turn again and it is running
|
||||
// normal Python code. However, this will be unnoticeable for quick call-once
|
||||
// functions, which is usually the case.
|
||||
//
|
||||
// For in-depth background, see docs/advanced/deadlock.md
|
||||
template <typename T>
|
||||
class gil_safe_call_once_and_store {
|
||||
public:
|
||||
|
|
|
@ -175,6 +175,7 @@ inline numpy_internals &get_numpy_internals() {
|
|||
PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) {
|
||||
module_ numpy = module_::import("numpy");
|
||||
str version_string = numpy.attr("__version__");
|
||||
|
||||
module_ numpy_lib = module_::import("numpy.lib");
|
||||
object numpy_version = numpy_lib.attr("NumpyVersion")(version_string);
|
||||
int major_version = numpy_version.attr("major").cast<int>();
|
||||
|
@ -211,7 +212,6 @@ constexpr int platform_lookup(int I, Ints... Is) {
|
|||
}
|
||||
|
||||
struct npy_api {
|
||||
// If you change this code, please review `normalized_dtype_num` below.
|
||||
enum constants {
|
||||
NPY_ARRAY_C_CONTIGUOUS_ = 0x0001,
|
||||
NPY_ARRAY_F_CONTIGUOUS_ = 0x0002,
|
||||
|
@ -384,74 +384,6 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
// This table normalizes typenums by mapping NPY_INT_, NPY_LONG, ... to NPY_INT32_, NPY_INT64, ...
|
||||
// This is needed to correctly handle situations where multiple typenums map to the same type,
|
||||
// e.g. NPY_LONG_ may be equivalent to NPY_INT_ or NPY_LONGLONG_ despite having a different
|
||||
// typenum. The normalized typenum should always match the values used in npy_format_descriptor.
|
||||
// If you change this code, please review `enum constants` above.
|
||||
static constexpr int normalized_dtype_num[npy_api::NPY_VOID_ + 1] = {
|
||||
// NPY_BOOL_ =>
|
||||
npy_api::NPY_BOOL_,
|
||||
// NPY_BYTE_ =>
|
||||
npy_api::NPY_BYTE_,
|
||||
// NPY_UBYTE_ =>
|
||||
npy_api::NPY_UBYTE_,
|
||||
// NPY_SHORT_ =>
|
||||
npy_api::NPY_INT16_,
|
||||
// NPY_USHORT_ =>
|
||||
npy_api::NPY_UINT16_,
|
||||
// NPY_INT_ =>
|
||||
sizeof(int) == sizeof(std::int16_t) ? npy_api::NPY_INT16_
|
||||
: sizeof(int) == sizeof(std::int32_t) ? npy_api::NPY_INT32_
|
||||
: sizeof(int) == sizeof(std::int64_t) ? npy_api::NPY_INT64_
|
||||
: npy_api::NPY_INT_,
|
||||
// NPY_UINT_ =>
|
||||
sizeof(unsigned int) == sizeof(std::uint16_t) ? npy_api::NPY_UINT16_
|
||||
: sizeof(unsigned int) == sizeof(std::uint32_t) ? npy_api::NPY_UINT32_
|
||||
: sizeof(unsigned int) == sizeof(std::uint64_t) ? npy_api::NPY_UINT64_
|
||||
: npy_api::NPY_UINT_,
|
||||
// NPY_LONG_ =>
|
||||
sizeof(long) == sizeof(std::int16_t) ? npy_api::NPY_INT16_
|
||||
: sizeof(long) == sizeof(std::int32_t) ? npy_api::NPY_INT32_
|
||||
: sizeof(long) == sizeof(std::int64_t) ? npy_api::NPY_INT64_
|
||||
: npy_api::NPY_LONG_,
|
||||
// NPY_ULONG_ =>
|
||||
sizeof(unsigned long) == sizeof(std::uint16_t) ? npy_api::NPY_UINT16_
|
||||
: sizeof(unsigned long) == sizeof(std::uint32_t) ? npy_api::NPY_UINT32_
|
||||
: sizeof(unsigned long) == sizeof(std::uint64_t) ? npy_api::NPY_UINT64_
|
||||
: npy_api::NPY_ULONG_,
|
||||
// NPY_LONGLONG_ =>
|
||||
sizeof(long long) == sizeof(std::int16_t) ? npy_api::NPY_INT16_
|
||||
: sizeof(long long) == sizeof(std::int32_t) ? npy_api::NPY_INT32_
|
||||
: sizeof(long long) == sizeof(std::int64_t) ? npy_api::NPY_INT64_
|
||||
: npy_api::NPY_LONGLONG_,
|
||||
// NPY_ULONGLONG_ =>
|
||||
sizeof(unsigned long long) == sizeof(std::uint16_t) ? npy_api::NPY_UINT16_
|
||||
: sizeof(unsigned long long) == sizeof(std::uint32_t) ? npy_api::NPY_UINT32_
|
||||
: sizeof(unsigned long long) == sizeof(std::uint64_t) ? npy_api::NPY_UINT64_
|
||||
: npy_api::NPY_ULONGLONG_,
|
||||
// NPY_FLOAT_ =>
|
||||
npy_api::NPY_FLOAT_,
|
||||
// NPY_DOUBLE_ =>
|
||||
npy_api::NPY_DOUBLE_,
|
||||
// NPY_LONGDOUBLE_ =>
|
||||
npy_api::NPY_LONGDOUBLE_,
|
||||
// NPY_CFLOAT_ =>
|
||||
npy_api::NPY_CFLOAT_,
|
||||
// NPY_CDOUBLE_ =>
|
||||
npy_api::NPY_CDOUBLE_,
|
||||
// NPY_CLONGDOUBLE_ =>
|
||||
npy_api::NPY_CLONGDOUBLE_,
|
||||
// NPY_OBJECT_ =>
|
||||
npy_api::NPY_OBJECT_,
|
||||
// NPY_STRING_ =>
|
||||
npy_api::NPY_STRING_,
|
||||
// NPY_UNICODE_ =>
|
||||
npy_api::NPY_UNICODE_,
|
||||
// NPY_VOID_ =>
|
||||
npy_api::NPY_VOID_,
|
||||
};
|
||||
|
||||
inline PyArray_Proxy *array_proxy(void *ptr) { return reinterpret_cast<PyArray_Proxy *>(ptr); }
|
||||
|
||||
inline const PyArray_Proxy *array_proxy(const void *ptr) {
|
||||
|
@ -752,13 +684,6 @@ public:
|
|||
return detail::npy_format_descriptor<typename std::remove_cv<T>::type>::dtype();
|
||||
}
|
||||
|
||||
/// Return the type number associated with a C++ type.
|
||||
/// This is the constexpr equivalent of `dtype::of<T>().num()`.
|
||||
template <typename T>
|
||||
static constexpr int num_of() {
|
||||
return detail::npy_format_descriptor<typename std::remove_cv<T>::type>::value;
|
||||
}
|
||||
|
||||
/// Size of the data type in bytes.
|
||||
#ifdef PYBIND11_NUMPY_1_ONLY
|
||||
ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; }
|
||||
|
@ -800,9 +725,7 @@ public:
|
|||
return detail::array_descriptor_proxy(m_ptr)->type;
|
||||
}
|
||||
|
||||
/// Type number of dtype. Note that different values may be returned for equivalent types,
|
||||
/// e.g. even though ``long`` may be equivalent to ``int`` or ``long long``, they still have
|
||||
/// different type numbers. Consider using `normalized_num` to avoid this.
|
||||
/// type number of dtype.
|
||||
int num() const {
|
||||
// Note: The signature, `dtype::num` follows the naming of NumPy's public
|
||||
// Python API (i.e., ``dtype.num``), rather than its internal
|
||||
|
@ -810,17 +733,6 @@ public:
|
|||
return detail::array_descriptor_proxy(m_ptr)->type_num;
|
||||
}
|
||||
|
||||
/// Type number of dtype, normalized to match the return value of `num_of` for equivalent
|
||||
/// types. This function can be used to write switch statements that correctly handle
|
||||
/// equivalent types with different type numbers.
|
||||
int normalized_num() const {
|
||||
int value = num();
|
||||
if (value >= 0 && value <= detail::npy_api::NPY_VOID_) {
|
||||
return detail::normalized_dtype_num[value];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Single character for byteorder
|
||||
char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; }
|
||||
|
||||
|
@ -1516,11 +1428,7 @@ public:
|
|||
};
|
||||
|
||||
template <typename T>
|
||||
struct npy_format_descriptor<
|
||||
T,
|
||||
enable_if_t<is_same_ignoring_cvref<T, PyObject *>::value
|
||||
|| ((std::is_same<T, handle>::value || std::is_same<T, object>::value)
|
||||
&& sizeof(T) == sizeof(PyObject *))>> {
|
||||
struct npy_format_descriptor<T, enable_if_t<is_same_ignoring_cvref<T, PyObject *>::value>> {
|
||||
static constexpr auto name = const_name("object");
|
||||
|
||||
static constexpr int value = npy_api::NPY_OBJECT_;
|
||||
|
@ -2182,8 +2090,7 @@ vectorize_helper<Func, Return, Args...> vectorize_extractor(const Func &f, Retur
|
|||
template <typename T, int Flags>
|
||||
struct handle_type_name<array_t<T, Flags>> {
|
||||
static constexpr auto name
|
||||
= io_name("typing.Annotated[numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
|
||||
+ npy_format_descriptor<T>::name + const_name("]");
|
||||
= const_name("numpy.ndarray[") + npy_format_descriptor<T>::name + const_name("]");
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
|
|
@ -10,10 +10,8 @@
|
|||
|
||||
#pragma once
|
||||
#include "detail/class.h"
|
||||
#include "detail/dynamic_raw_ptr_cast_if_possible.h"
|
||||
#include "detail/exception_translation.h"
|
||||
#include "detail/init.h"
|
||||
#include "detail/using_smart_holder.h"
|
||||
#include "attr.h"
|
||||
#include "gil.h"
|
||||
#include "gil_safe_call_once.h"
|
||||
|
@ -24,17 +22,10 @@
|
|||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// See PR #5448. This warning suppression is needed for the PYBIND11_OVERRIDE macro family.
|
||||
// NOTE that this is NOT embedded in a push/pop pair because that is very difficult to achieve.
|
||||
#if defined(__clang_major__) && __clang_major__ < 14
|
||||
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914))
|
||||
# define PYBIND11_STD_LAUNDER std::launder
|
||||
# define PYBIND11_HAS_STD_LAUNDER 1
|
||||
|
@ -310,20 +301,9 @@ protected:
|
|||
constexpr bool has_kw_only_args = any_of<std::is_same<kw_only, Extra>...>::value,
|
||||
has_pos_only_args = any_of<std::is_same<pos_only, Extra>...>::value,
|
||||
has_arg_annotations = any_of<is_keyword<Extra>...>::value;
|
||||
constexpr bool has_is_method = any_of<std::is_same<is_method, Extra>...>::value;
|
||||
// The implicit `self` argument is not present and not counted in method definitions.
|
||||
constexpr bool has_args = cast_in::args_pos >= 0;
|
||||
constexpr bool is_method_with_self_arg_only = has_is_method && !has_args;
|
||||
static_assert(has_arg_annotations || !has_kw_only_args,
|
||||
"py::kw_only requires the use of argument annotations");
|
||||
static_assert(((/* Need `py::arg("arg_name")` annotation in function/method. */
|
||||
has_arg_annotations)
|
||||
|| (/* Allow methods with no arguments `def method(self, /): ...`.
|
||||
* A method has at least one argument `self`. There can be no
|
||||
* `py::arg` annotation. E.g. `class.def("method", py::pos_only())`.
|
||||
*/
|
||||
is_method_with_self_arg_only))
|
||||
|| !has_pos_only_args,
|
||||
static_assert(has_arg_annotations || !has_pos_only_args,
|
||||
"py::pos_only requires the use of argument annotations (for docstrings "
|
||||
"and aligning the annotations to the argument)");
|
||||
|
||||
|
@ -443,13 +423,6 @@ protected:
|
|||
std::string signature;
|
||||
size_t type_index = 0, arg_index = 0;
|
||||
bool is_starred = false;
|
||||
// `is_return_value.top()` is true if we are currently inside the return type of the
|
||||
// signature. Using `@^`/`@$` we can force types to be arg/return types while `@!` pops
|
||||
// back to the previous state.
|
||||
std::stack<bool> is_return_value({false});
|
||||
// The following characters have special meaning in the signature parsing. Literals
|
||||
// containing these are escaped with `!`.
|
||||
std::string special_chars("!@%{}-");
|
||||
for (const auto *pc = text; *pc != '\0'; ++pc) {
|
||||
const auto c = *pc;
|
||||
|
||||
|
@ -503,57 +476,7 @@ protected:
|
|||
} else {
|
||||
signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
|
||||
}
|
||||
} else if (c == '!' && special_chars.find(*(pc + 1)) != std::string::npos) {
|
||||
// typing::Literal escapes special characters with !
|
||||
signature += *++pc;
|
||||
} else if (c == '@') {
|
||||
// `@^ ... @!` and `@$ ... @!` are used to force arg/return value type (see
|
||||
// typing::Callable/detail::arg_descr/detail::return_descr)
|
||||
if (*(pc + 1) == '^') {
|
||||
is_return_value.emplace(false);
|
||||
++pc;
|
||||
continue;
|
||||
}
|
||||
if (*(pc + 1) == '$') {
|
||||
is_return_value.emplace(true);
|
||||
++pc;
|
||||
continue;
|
||||
}
|
||||
if (*(pc + 1) == '!') {
|
||||
is_return_value.pop();
|
||||
++pc;
|
||||
continue;
|
||||
}
|
||||
// Handle types that differ depending on whether they appear
|
||||
// in an argument or a return value position (see io_name<text1, text2>).
|
||||
// For named arguments (py::arg()) with noconvert set, return value type is used.
|
||||
++pc;
|
||||
if (!is_return_value.top()
|
||||
&& !(arg_index < rec->args.size() && !rec->args[arg_index].convert)) {
|
||||
while (*pc != '\0' && *pc != '@') {
|
||||
signature += *pc++;
|
||||
}
|
||||
if (*pc == '@') {
|
||||
++pc;
|
||||
}
|
||||
while (*pc != '\0' && *pc != '@') {
|
||||
++pc;
|
||||
}
|
||||
} else {
|
||||
while (*pc != '\0' && *pc != '@') {
|
||||
++pc;
|
||||
}
|
||||
if (*pc == '@') {
|
||||
++pc;
|
||||
}
|
||||
while (*pc != '\0' && *pc != '@') {
|
||||
signature += *pc++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (c == '-' && *(pc + 1) == '>') {
|
||||
is_return_value.emplace(true);
|
||||
}
|
||||
signature += c;
|
||||
}
|
||||
}
|
||||
|
@ -650,7 +573,8 @@ protected:
|
|||
// chain.
|
||||
chain_start = rec;
|
||||
rec->next = chain;
|
||||
auto rec_capsule = reinterpret_borrow<capsule>(PyCFunction_GET_SELF(m_ptr));
|
||||
auto rec_capsule
|
||||
= reinterpret_borrow<capsule>(((PyCFunctionObject *) m_ptr)->m_self);
|
||||
rec_capsule.set_pointer(unique_rec.release());
|
||||
guarded_strdup.release();
|
||||
} else {
|
||||
|
@ -710,11 +634,12 @@ protected:
|
|||
}
|
||||
}
|
||||
|
||||
/* Install docstring */
|
||||
auto *func = (PyCFunctionObject *) m_ptr;
|
||||
std::free(const_cast<char *>(func->m_ml->ml_doc));
|
||||
// Install docstring if it's non-empty (when at least one option is enabled)
|
||||
auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
|
||||
std::free(const_cast<char *>(PYBIND11_PYCFUNCTION_GET_DOC(func)));
|
||||
PYBIND11_PYCFUNCTION_SET_DOC(func, doc);
|
||||
func->m_ml->ml_doc
|
||||
= signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
|
||||
|
||||
if (rec->is_method) {
|
||||
m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr());
|
||||
|
@ -1382,7 +1307,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
|
|||
|
||||
template <>
|
||||
struct handle_type_name<module_> {
|
||||
static constexpr auto name = const_name("types.ModuleType");
|
||||
static constexpr auto name = const_name("module");
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
@ -1446,8 +1371,8 @@ protected:
|
|||
tinfo->dealloc = rec.dealloc;
|
||||
tinfo->simple_type = true;
|
||||
tinfo->simple_ancestors = true;
|
||||
tinfo->default_holder = rec.default_holder;
|
||||
tinfo->module_local = rec.module_local;
|
||||
tinfo->holder_enum_v = rec.holder_enum_v;
|
||||
|
||||
with_internals([&](internals &internals) {
|
||||
auto tindex = std::type_index(*rec.type);
|
||||
|
@ -1457,17 +1382,7 @@ protected:
|
|||
} else {
|
||||
internals.registered_types_cpp[tindex] = tinfo;
|
||||
}
|
||||
|
||||
PYBIND11_WARNING_PUSH
|
||||
#if defined(__GNUC__) && __GNUC__ == 12
|
||||
// When using GCC 12 these warnings are disabled as they trigger
|
||||
// false positive warnings. Discussed here:
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115824.
|
||||
PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds")
|
||||
PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread")
|
||||
#endif
|
||||
internals.registered_types_py[(PyTypeObject *) m_ptr] = {tinfo};
|
||||
PYBIND11_WARNING_POP
|
||||
});
|
||||
|
||||
if (rec.bases.size() > 1 || rec.multiple_inheritance) {
|
||||
|
@ -1620,239 +1535,6 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
|
|||
return pmf;
|
||||
}
|
||||
|
||||
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.
|
||||
// In this context, the PM template parameter is certain to be a Pointer to a Member.
|
||||
// The main purpose of must_be_member_function_pointer is to make this obvious, and to guard
|
||||
// against accidents. As a side-effect, it also explains why the syntactical overhead for
|
||||
// perfect forwarding is not needed.
|
||||
template <typename PM>
|
||||
using must_be_member_function_pointer = enable_if_t<std::is_member_pointer<PM>::value, int>;
|
||||
|
||||
// Note that property_cpp_function is intentionally in the main pybind11 namespace,
|
||||
// because user-defined specializations could be useful.
|
||||
|
||||
// Classic (non-smart_holder) implementations for .def_readonly and .def_readwrite
|
||||
// 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
|
||||
// However, this implementation works as-is (and safely) for smart_holder std::shared_ptr members.
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function_classic {
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 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));
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &hdl) {
|
||||
return readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 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));
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
template <typename T, typename D, typename SFINAE = void>
|
||||
struct property_cpp_function : detail::property_cpp_function_classic<T, D> {};
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename T, typename D, typename SFINAE = void>
|
||||
struct both_t_and_d_use_type_caster_base : std::false_type {};
|
||||
|
||||
// `T` is assumed to be equivalent to `intrinsic_t<T>`.
|
||||
// `D` is may or may not be equivalent to `intrinsic_t<D>`.
|
||||
template <typename T, typename D>
|
||||
struct both_t_and_d_use_type_caster_base<
|
||||
T,
|
||||
D,
|
||||
enable_if_t<all_of<std::is_base_of<type_caster_base<T>, type_caster<T>>,
|
||||
std::is_base_of<type_caster_base<intrinsic_t<D>>, make_caster<D>>>::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 <typename T, typename D>
|
||||
struct property_cpp_function_sh_raw_ptr_member {
|
||||
using drp = typename std::remove_pointer<D>::type;
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM pm, const handle &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<drp> {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
D ptr = (*c_sp).*pm;
|
||||
return std::shared_ptr<drp>(c_sp, ptr);
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &hdl) {
|
||||
return readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &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<D>(std::move(value)); },
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::write(pm, hdl);
|
||||
}
|
||||
};
|
||||
|
||||
// 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 <typename T, typename D>
|
||||
struct property_cpp_function_sh_member_held_by_value {
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM pm, const handle &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<typename std::add_const<D>::type> {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
return std::shared_ptr<typename std::add_const<D>::type>(c_sp,
|
||||
&(c_sp.get()->*pm));
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &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<D> {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
return std::shared_ptr<D>(c_sp, &(c_sp.get()->*pm));
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::read(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &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<T, D>::write(pm, hdl);
|
||||
}
|
||||
};
|
||||
|
||||
// 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 <typename T, typename D>
|
||||
struct property_cpp_function_sh_unique_ptr_member {
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM, const handle &) {
|
||||
static_assert(!is_instantiation<std::unique_ptr, D>::value,
|
||||
"def_readonly cannot be used for std::unique_ptr members.");
|
||||
return cpp_function{}; // Unreachable.
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &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<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
return D{std::move(c_sp.get()->*pm)};
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::read(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &hdl) {
|
||||
return cpp_function([pm](T &c, D &&value) { c.*pm = std::move(value); }, is_method(hdl));
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function<
|
||||
T,
|
||||
D,
|
||||
detail::enable_if_t<detail::all_of<std::is_pointer<D>,
|
||||
detail::both_t_and_d_use_type_caster_base<T, D>>::value>>
|
||||
: detail::property_cpp_function_sh_raw_ptr_member<T, D> {};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function<T,
|
||||
D,
|
||||
detail::enable_if_t<detail::all_of<
|
||||
detail::none_of<std::is_pointer<D>,
|
||||
std::is_array<D>,
|
||||
detail::is_instantiation<std::unique_ptr, D>,
|
||||
detail::is_instantiation<std::shared_ptr, D>>,
|
||||
detail::both_t_and_d_use_type_caster_base<T, D>>::value>>
|
||||
: detail::property_cpp_function_sh_member_held_by_value<T, D> {};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function<
|
||||
T,
|
||||
D,
|
||||
detail::enable_if_t<detail::all_of<
|
||||
detail::is_instantiation<std::unique_ptr, D>,
|
||||
detail::both_t_and_d_use_type_caster_base<T, typename D::element_type>>::value>>
|
||||
: detail::property_cpp_function_sh_unique_ptr_member<T, D> {};
|
||||
|
||||
#ifdef PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
|
||||
// NOTE: THIS IS MEANT FOR STRESS-TESTING OR TRIAGING ONLY!
|
||||
// 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.
|
||||
// Be careful not to link translation units compiled with different default holders, because
|
||||
// this will cause ODR violations (https://en.wikipedia.org/wiki/One_Definition_Rule).
|
||||
template <typename>
|
||||
using default_holder_type = smart_holder;
|
||||
#else
|
||||
template <typename T>
|
||||
using default_holder_type = std::unique_ptr<T>;
|
||||
#endif
|
||||
|
||||
template <typename type_, typename... options>
|
||||
class class_ : public detail::generic_type {
|
||||
template <typename T>
|
||||
|
@ -1869,7 +1551,7 @@ public:
|
|||
using type = type_;
|
||||
using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
|
||||
constexpr static bool has_alias = !std::is_void<type_alias>::value;
|
||||
using holder_type = detail::exactly_one_t<is_holder, default_holder_type<type>, options...>;
|
||||
using holder_type = detail::exactly_one_t<is_holder, std::unique_ptr<type>, options...>;
|
||||
|
||||
static_assert(detail::all_of<is_valid_class_option<options>...>::value,
|
||||
"Unknown/invalid class_ template parameters provided");
|
||||
|
@ -1900,16 +1582,8 @@ public:
|
|||
record.type_align = alignof(conditional_t<has_alias, type_alias, type> &);
|
||||
record.holder_size = sizeof(holder_type);
|
||||
record.init_instance = init_instance;
|
||||
|
||||
if (detail::is_instantiation<std::unique_ptr, holder_type>::value) {
|
||||
record.holder_enum_v = detail::holder_enum_t::std_unique_ptr;
|
||||
} else if (detail::is_instantiation<std::shared_ptr, holder_type>::value) {
|
||||
record.holder_enum_v = detail::holder_enum_t::std_shared_ptr;
|
||||
} else if (std::is_same<holder_type, smart_holder>::value) {
|
||||
record.holder_enum_v = detail::holder_enum_t::smart_holder;
|
||||
} else {
|
||||
record.holder_enum_v = detail::holder_enum_t::custom_holder;
|
||||
}
|
||||
record.dealloc = dealloc;
|
||||
record.default_holder = detail::is_instantiation<std::unique_ptr, holder_type>::value;
|
||||
|
||||
set_operator_new<type>(&record);
|
||||
|
||||
|
@ -1919,12 +1593,6 @@ public:
|
|||
/* Process optional arguments, if any */
|
||||
process_attributes<Extra...>::init(extra..., &record);
|
||||
|
||||
if (record.release_gil_before_calling_cpp_dtor) {
|
||||
record.dealloc = dealloc_release_gil_before_calling_cpp_dtor;
|
||||
} else {
|
||||
record.dealloc = dealloc_without_manipulating_gil;
|
||||
}
|
||||
|
||||
generic_type::initialize(record);
|
||||
|
||||
if (has_alias) {
|
||||
|
@ -2048,11 +1716,9 @@ public:
|
|||
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
|
||||
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
|
||||
"def_readwrite() requires a class member (or base class member)");
|
||||
def_property(name,
|
||||
property_cpp_function<type, D>::read(pm, *this),
|
||||
property_cpp_function<type, D>::write(pm, *this),
|
||||
return_value_policy::reference_internal,
|
||||
extra...);
|
||||
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)),
|
||||
fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
|
||||
def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -2060,10 +1726,8 @@ public:
|
|||
class_ &def_readonly(const char *name, const D C::*pm, const Extra &...extra) {
|
||||
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
|
||||
"def_readonly() requires a class member (or base class member)");
|
||||
def_property_readonly(name,
|
||||
property_cpp_function<type, D>::readonly(pm, *this),
|
||||
return_value_policy::reference_internal,
|
||||
extra...);
|
||||
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this));
|
||||
def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -2240,8 +1904,6 @@ private:
|
|||
/// 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 <typename H = holder_type,
|
||||
detail::enable_if_t<!detail::is_smart_holder<H>::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()) {
|
||||
|
@ -2251,73 +1913,15 @@ private:
|
|||
init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
|
||||
}
|
||||
|
||||
template <typename WrappedType>
|
||||
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 <typename WrappedType, typename SomeBaseOfWrappedType>
|
||||
static bool try_initialization_using_shared_from_this(
|
||||
holder_type *uninitialized_location,
|
||||
WrappedType *value_ptr_w_t,
|
||||
const std::enable_shared_from_this<SomeBaseOfWrappedType> *) {
|
||||
auto shd_ptr = std::dynamic_pointer_cast<WrappedType>(
|
||||
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 <typename H = holder_type,
|
||||
detail::enable_if_t<detail::is_smart_holder<H>::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<void *>(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<holder_type>());
|
||||
auto *value_ptr_w_t = v_h.value_ptr<type>();
|
||||
// Try downcast from `type` to `type_alias`:
|
||||
inst->is_alias
|
||||
= detail::dynamic_raw_ptr_cast_if_possible<type_alias>(value_ptr_w_t) != nullptr;
|
||||
if (holder_void_ptr) {
|
||||
// Note: inst->owned ignored.
|
||||
auto *holder_ptr = static_cast<holder_type *>(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*/ inst->is_alias));
|
||||
} else {
|
||||
new (uninitialized_location)
|
||||
holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t));
|
||||
}
|
||||
}
|
||||
v_h.set_holder_constructed();
|
||||
}
|
||||
|
||||
// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
|
||||
// NOTE: The Python error indicator needs to cleared BEFORE this function is called.
|
||||
// This is because we could be deallocating while cleaning up after a Python exception.
|
||||
// If the error indicator is not cleared but the C++ destructor code makes Python C API
|
||||
// calls, those calls are likely to generate a new exception, and pybind11 will then
|
||||
// throw `error_already_set` from the C++ destructor. This is forbidden and will
|
||||
// trigger std::terminate().
|
||||
static void dealloc_impl(detail::value_and_holder &v_h) {
|
||||
/// 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.
|
||||
// If so, the Python error indicator will be set. We need to clear that before
|
||||
// running the destructor, in case the destructor code calls more Python.
|
||||
// If we don't, the Python API will exit with an exception, and pybind11 will
|
||||
// throw error_already_set from the C++ destructor which is forbidden and triggers
|
||||
// std::terminate().
|
||||
error_scope scope;
|
||||
if (v_h.holder_constructed()) {
|
||||
v_h.holder<holder_type>().~holder_type();
|
||||
v_h.set_holder_constructed(false);
|
||||
|
@ -2328,32 +1932,6 @@ private:
|
|||
v_h.value_ptr() = nullptr;
|
||||
}
|
||||
|
||||
static void dealloc_without_manipulating_gil(detail::value_and_holder &v_h) {
|
||||
error_scope scope;
|
||||
dealloc_impl(v_h);
|
||||
}
|
||||
|
||||
static void dealloc_release_gil_before_calling_cpp_dtor(detail::value_and_holder &v_h) {
|
||||
error_scope scope;
|
||||
// Intentionally not using `gil_scoped_release` because the non-simple
|
||||
// version unconditionally calls `get_internals()`.
|
||||
// `Py_BEGIN_ALLOW_THREADS`, `Py_END_ALLOW_THREADS` cannot be used
|
||||
// because those macros include `{` and `}`.
|
||||
PyThreadState *py_ts = PyEval_SaveThread();
|
||||
try {
|
||||
dealloc_impl(v_h);
|
||||
} catch (...) {
|
||||
// This code path is expected to be unreachable unless there is a
|
||||
// bug in pybind11 itself.
|
||||
// An alternative would be to mark this function, or
|
||||
// `dealloc_impl()`, with `nothrow`, but that would be a subtle
|
||||
// behavior change and could make debugging more difficult.
|
||||
PyEval_RestoreThread(py_ts);
|
||||
throw;
|
||||
}
|
||||
PyEval_RestoreThread(py_ts);
|
||||
}
|
||||
|
||||
static detail::function_record *get_function_record(handle h) {
|
||||
h = detail::get_function(h);
|
||||
if (!h) {
|
||||
|
@ -2375,11 +1953,6 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
// Supports easier switching between py::class_<T> and py::class_<T, py::smart_holder>:
|
||||
// users can simply replace the `_` in `class_` with `h` or vice versa.
|
||||
template <typename type_, typename... options>
|
||||
using classh = class_<type_, smart_holder, options...>;
|
||||
|
||||
/// Binds an existing constructor taking arguments Args...
|
||||
template <typename... Args>
|
||||
detail::initimpl::constructor<Args...> init() {
|
||||
|
@ -2441,11 +2014,9 @@ struct enum_base {
|
|||
.format(std::move(type_name), enum_name(arg), int_(arg));
|
||||
},
|
||||
name("__repr__"),
|
||||
is_method(m_base),
|
||||
pos_only());
|
||||
is_method(m_base));
|
||||
|
||||
m_base.attr("name")
|
||||
= property(cpp_function(&enum_name, name("name"), is_method(m_base), pos_only()));
|
||||
m_base.attr("name") = property(cpp_function(&enum_name, name("name"), is_method(m_base)));
|
||||
|
||||
m_base.attr("__str__") = cpp_function(
|
||||
[](handle arg) -> str {
|
||||
|
@ -2453,8 +2024,7 @@ struct enum_base {
|
|||
return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg));
|
||||
},
|
||||
name("__str__"),
|
||||
is_method(m_base),
|
||||
pos_only());
|
||||
is_method(m_base));
|
||||
|
||||
if (options::show_enum_members_docstring()) {
|
||||
m_base.attr("__doc__") = static_property(
|
||||
|
@ -2509,8 +2079,7 @@ struct enum_base {
|
|||
}, \
|
||||
name(op), \
|
||||
is_method(m_base), \
|
||||
arg("other"), \
|
||||
pos_only())
|
||||
arg("other"))
|
||||
|
||||
#define PYBIND11_ENUM_OP_CONV(op, expr) \
|
||||
m_base.attr(op) = cpp_function( \
|
||||
|
@ -2520,8 +2089,7 @@ struct enum_base {
|
|||
}, \
|
||||
name(op), \
|
||||
is_method(m_base), \
|
||||
arg("other"), \
|
||||
pos_only())
|
||||
arg("other"))
|
||||
|
||||
#define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \
|
||||
m_base.attr(op) = cpp_function( \
|
||||
|
@ -2531,8 +2099,7 @@ struct enum_base {
|
|||
}, \
|
||||
name(op), \
|
||||
is_method(m_base), \
|
||||
arg("other"), \
|
||||
pos_only())
|
||||
arg("other"))
|
||||
|
||||
if (is_convertible) {
|
||||
PYBIND11_ENUM_OP_CONV_LHS("__eq__", !b.is_none() && a.equal(b));
|
||||
|
@ -2552,8 +2119,7 @@ struct enum_base {
|
|||
m_base.attr("__invert__")
|
||||
= cpp_function([](const object &arg) { return ~(int_(arg)); },
|
||||
name("__invert__"),
|
||||
is_method(m_base),
|
||||
pos_only());
|
||||
is_method(m_base));
|
||||
}
|
||||
} else {
|
||||
PYBIND11_ENUM_OP_STRICT("__eq__", int_(a).equal(int_(b)), return false);
|
||||
|
@ -2573,15 +2139,11 @@ struct enum_base {
|
|||
#undef PYBIND11_ENUM_OP_CONV
|
||||
#undef PYBIND11_ENUM_OP_STRICT
|
||||
|
||||
m_base.attr("__getstate__") = cpp_function([](const object &arg) { return int_(arg); },
|
||||
name("__getstate__"),
|
||||
is_method(m_base),
|
||||
pos_only());
|
||||
m_base.attr("__getstate__") = cpp_function(
|
||||
[](const object &arg) { return int_(arg); }, name("__getstate__"), is_method(m_base));
|
||||
|
||||
m_base.attr("__hash__") = cpp_function([](const object &arg) { return int_(arg); },
|
||||
name("__hash__"),
|
||||
is_method(m_base),
|
||||
pos_only());
|
||||
m_base.attr("__hash__") = cpp_function(
|
||||
[](const object &arg) { return int_(arg); }, name("__hash__"), is_method(m_base));
|
||||
}
|
||||
|
||||
PYBIND11_NOINLINE void value(char const *name_, object value, const char *doc = nullptr) {
|
||||
|
@ -2673,9 +2235,9 @@ public:
|
|||
m_base.init(is_arithmetic, is_convertible);
|
||||
|
||||
def(init([](Scalar i) { return static_cast<Type>(i); }), arg("value"));
|
||||
def_property_readonly("value", [](Type value) { return (Scalar) value; }, pos_only());
|
||||
def("__int__", [](Type value) { return (Scalar) value; }, pos_only());
|
||||
def("__index__", [](Type value) { return (Scalar) value; }, pos_only());
|
||||
def_property_readonly("value", [](Type value) { return (Scalar) value; });
|
||||
def("__int__", [](Type value) { return (Scalar) value; });
|
||||
def("__index__", [](Type value) { return (Scalar) value; });
|
||||
attr("__setstate__") = cpp_function(
|
||||
[](detail::value_and_holder &v_h, Scalar arg) {
|
||||
detail::initimpl::setstate<Base>(
|
||||
|
@ -2684,8 +2246,7 @@ public:
|
|||
detail::is_new_style_constructor(),
|
||||
pybind11::name("__setstate__"),
|
||||
is_method(*this),
|
||||
arg("state"),
|
||||
pos_only());
|
||||
arg("state"));
|
||||
}
|
||||
|
||||
/// Export enumeration entries into the parent scope
|
||||
|
@ -2757,20 +2318,13 @@ keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) {
|
|||
inline std::pair<decltype(internals::registered_types_py)::iterator, bool>
|
||||
all_type_info_get_cache(PyTypeObject *type) {
|
||||
auto res = with_internals([type](internals &internals) {
|
||||
auto ins = internals
|
||||
.registered_types_py
|
||||
return internals
|
||||
.registered_types_py
|
||||
#ifdef __cpp_lib_unordered_map_try_emplace
|
||||
.try_emplace(type);
|
||||
.try_emplace(type);
|
||||
#else
|
||||
.emplace(type, std::vector<detail::type_info *>());
|
||||
.emplace(type, std::vector<detail::type_info *>());
|
||||
#endif
|
||||
if (ins.second) {
|
||||
// For free-threading mode, this call must be under
|
||||
// the with_internals() mutex lock, to avoid that other threads
|
||||
// continue running with the empty ins.first->second.
|
||||
all_type_info_populate(type, ins.first->second);
|
||||
}
|
||||
return ins;
|
||||
});
|
||||
if (res.second) {
|
||||
// New cache entry created; set up a weak reference to automatically remove it if the type
|
||||
|
@ -2871,8 +2425,7 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) {
|
|||
|
||||
if (!detail::get_type_info(typeid(state), false)) {
|
||||
class_<state>(handle(), "iterator", pybind11::module_local())
|
||||
.def(
|
||||
"__iter__", [](state &s) -> state & { return s; }, pos_only())
|
||||
.def("__iter__", [](state &s) -> state & { return s; })
|
||||
.def(
|
||||
"__next__",
|
||||
[](state &s) -> ValueType {
|
||||
|
@ -2889,7 +2442,6 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) {
|
|||
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
|
||||
},
|
||||
std::forward<Extra>(extra)...,
|
||||
pos_only(),
|
||||
Policy);
|
||||
}
|
||||
|
||||
|
@ -3024,12 +2576,10 @@ void implicitly_convertible() {
|
|||
}
|
||||
|
||||
inline void register_exception_translator(ExceptionTranslator &&translator) {
|
||||
detail::with_exception_translators(
|
||||
[&](std::forward_list<ExceptionTranslator> &exception_translators,
|
||||
std::forward_list<ExceptionTranslator> &local_exception_translators) {
|
||||
(void) local_exception_translators;
|
||||
exception_translators.push_front(std::forward<ExceptionTranslator>(translator));
|
||||
});
|
||||
detail::with_internals([&](detail::internals &internals) {
|
||||
internals.registered_exception_translators.push_front(
|
||||
std::forward<ExceptionTranslator>(translator));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3039,12 +2589,11 @@ inline void register_exception_translator(ExceptionTranslator &&translator) {
|
|||
* the exception.
|
||||
*/
|
||||
inline void register_local_exception_translator(ExceptionTranslator &&translator) {
|
||||
detail::with_exception_translators(
|
||||
[&](std::forward_list<ExceptionTranslator> &exception_translators,
|
||||
std::forward_list<ExceptionTranslator> &local_exception_translators) {
|
||||
(void) exception_translators;
|
||||
local_exception_translators.push_front(std::forward<ExceptionTranslator>(translator));
|
||||
});
|
||||
detail::with_internals([&](detail::internals &internals) {
|
||||
(void) internals;
|
||||
detail::get_local_internals().registered_exception_translators.push_front(
|
||||
std::forward<ExceptionTranslator>(translator));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3218,8 +2767,8 @@ get_type_override(const void *this_ptr, const type_info *this_type, const char *
|
|||
}
|
||||
|
||||
/* Don't call dispatch code if invoked from overridden function.
|
||||
Unfortunately this doesn't work on PyPy and GraalPy. */
|
||||
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
|
||||
Unfortunately this doesn't work on PyPy. */
|
||||
#if !defined(PYPY_VERSION)
|
||||
# if PY_VERSION_HEX >= 0x03090000
|
||||
PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get());
|
||||
if (frame != nullptr) {
|
||||
|
|
|
@ -113,17 +113,6 @@ public:
|
|||
/// See above (the only difference is that the key is provided as a string literal)
|
||||
str_attr_accessor attr(const char *key) const;
|
||||
|
||||
/** \rst
|
||||
Similar to the above attr functions with the difference that the templated Type
|
||||
is used to set the `__annotations__` dict value to the corresponding key. Worth noting
|
||||
that attr_with_type_hint is implemented in cast.h.
|
||||
\endrst */
|
||||
template <typename T>
|
||||
obj_attr_accessor attr_with_type_hint(handle key) const;
|
||||
/// See above (the only difference is that the key is provided as a string literal)
|
||||
template <typename T>
|
||||
str_attr_accessor attr_with_type_hint(const char *key) const;
|
||||
|
||||
/** \rst
|
||||
Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple``
|
||||
or ``list`` for a function call. Applying another * to the result yields
|
||||
|
@ -193,9 +182,6 @@ public:
|
|||
/// Get or set the object's docstring, i.e. ``obj.__doc__``.
|
||||
str_attr_accessor doc() const;
|
||||
|
||||
/// Get or set the object's annotations, i.e. ``obj.__annotations__``.
|
||||
object annotations() const;
|
||||
|
||||
/// Return the object's current reference count
|
||||
ssize_t ref_count() const {
|
||||
#ifdef PYPY_VERSION
|
||||
|
@ -657,7 +643,7 @@ struct error_fetch_and_normalize {
|
|||
|
||||
bool have_trace = false;
|
||||
if (m_trace) {
|
||||
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
|
||||
#if !defined(PYPY_VERSION)
|
||||
auto *tb = reinterpret_cast<PyTracebackObject *>(m_trace.ptr());
|
||||
|
||||
// Get the deepest trace possible.
|
||||
|
@ -1370,7 +1356,7 @@ inline bool PyUnicode_Check_Permissive(PyObject *o) {
|
|||
# define PYBIND11_STR_CHECK_FUN PyUnicode_Check
|
||||
#endif
|
||||
|
||||
inline bool PyStaticMethod_Check(PyObject *o) { return Py_TYPE(o) == &PyStaticMethod_Type; }
|
||||
inline bool PyStaticMethod_Check(PyObject *o) { return o->ob_type == &PyStaticMethod_Type; }
|
||||
|
||||
class kwargs_proxy : public handle {
|
||||
public:
|
||||
|
@ -1484,17 +1470,11 @@ public:
|
|||
PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check)
|
||||
|
||||
iterator &operator++() {
|
||||
init();
|
||||
advance();
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
// Note: We must call init() first so that rv.value is
|
||||
// the same as this->value just before calling advance().
|
||||
// Otherwise, dereferencing the returned iterator may call
|
||||
// advance() again and return the 3rd item instead of the 1st.
|
||||
init();
|
||||
auto rv = *this;
|
||||
advance();
|
||||
return rv;
|
||||
|
@ -1502,12 +1482,15 @@ public:
|
|||
|
||||
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
|
||||
reference operator*() const {
|
||||
init();
|
||||
if (m_ptr && !value.ptr()) {
|
||||
auto &self = const_cast<iterator &>(*this);
|
||||
self.advance();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
pointer operator->() const {
|
||||
init();
|
||||
operator*();
|
||||
return &value;
|
||||
}
|
||||
|
||||
|
@ -1530,13 +1513,6 @@ public:
|
|||
friend bool operator!=(const iterator &a, const iterator &b) { return a->ptr() != b->ptr(); }
|
||||
|
||||
private:
|
||||
void init() const {
|
||||
if (m_ptr && !value.ptr()) {
|
||||
auto &self = const_cast<iterator &>(*this);
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
void advance() {
|
||||
value = reinterpret_steal<object>(PyIter_Next(m_ptr));
|
||||
if (value.ptr() == nullptr && PyErr_Occurred()) {
|
||||
|
@ -2240,18 +2216,6 @@ class kwargs : public dict {
|
|||
PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check)
|
||||
};
|
||||
|
||||
// Subclasses of args and kwargs to support type hinting
|
||||
// as defined in PEP 484. See #5357 for more info.
|
||||
template <typename T>
|
||||
class Args : public args {
|
||||
using args::args;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class KWArgs : public kwargs {
|
||||
using kwargs::kwargs;
|
||||
};
|
||||
|
||||
class anyset : public object {
|
||||
public:
|
||||
PYBIND11_OBJECT(anyset, object, PyAnySet_Check)
|
||||
|
@ -2572,19 +2536,6 @@ str_attr_accessor object_api<D>::doc() const {
|
|||
return attr("__doc__");
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
object object_api<D>::annotations() const {
|
||||
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9
|
||||
// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
||||
if (!hasattr(derived(), "__annotations__")) {
|
||||
setattr(derived(), "__annotations__", dict());
|
||||
}
|
||||
return attr("__annotations__");
|
||||
#else
|
||||
return getattr(derived(), "__annotations__", dict());
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
handle object_api<D>::get_type() const {
|
||||
return type::handle_of(derived());
|
||||
|
|
|
@ -11,14 +11,10 @@
|
|||
|
||||
#include "pybind11.h"
|
||||
#include "detail/common.h"
|
||||
#include "detail/descr.h"
|
||||
#include "detail/type_caster_base.h"
|
||||
|
||||
#include <deque>
|
||||
#include <initializer_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
@ -39,89 +35,6 @@
|
|||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
//
|
||||
// Begin: Equivalent of
|
||||
// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438
|
||||
/*
|
||||
The three `PyObjectTypeIsConvertibleTo*()` functions below are
|
||||
the result of converging the behaviors of pybind11 and PyCLIF
|
||||
(http://github.com/google/clif).
|
||||
|
||||
Originally PyCLIF was extremely far on the permissive side of the spectrum,
|
||||
while pybind11 was very far on the strict side. Originally PyCLIF accepted any
|
||||
Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as
|
||||
the elements were convertible. The obvious (in hindsight) problem was that
|
||||
any empty Python iterable could be passed to any of these C++ types, e.g. `{}`
|
||||
was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments.
|
||||
|
||||
The functions below strike a practical permissive-vs-strict compromise,
|
||||
informed by tens of thousands of use cases in the wild. A main objective is
|
||||
to prevent accidents and improve readability:
|
||||
|
||||
- Python literals must match the C++ types.
|
||||
|
||||
- For C++ `set`: The potentially reducing conversion from a Python sequence
|
||||
(e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going
|
||||
through a Python `set`.
|
||||
|
||||
- However, a Python `set` can still be passed to a C++ `vector`. The rationale
|
||||
is that this conversion is not reducing. Implicit conversions of this kind
|
||||
are also fairly commonly used, therefore enforcing explicit conversions
|
||||
would have an unfavorable cost : benefit ratio; more sloppily speaking,
|
||||
such an enforcement would be more annoying than helpful.
|
||||
*/
|
||||
|
||||
inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj,
|
||||
std::initializer_list<const char *> tp_names) {
|
||||
if (PyType_Check(obj)) {
|
||||
return false;
|
||||
}
|
||||
const char *obj_tp_name = Py_TYPE(obj)->tp_name;
|
||||
for (const auto *tp_name : tp_names) {
|
||||
if (std::strcmp(obj_tp_name, tp_name) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool PyObjectTypeIsConvertibleToStdVector(PyObject *obj) {
|
||||
if (PySequence_Check(obj) != 0) {
|
||||
return !PyUnicode_Check(obj) && !PyBytes_Check(obj);
|
||||
}
|
||||
return (PyGen_Check(obj) != 0) || (PyAnySet_Check(obj) != 0)
|
||||
|| PyObjectIsInstanceWithOneOfTpNames(
|
||||
obj, {"dict_keys", "dict_values", "dict_items", "map", "zip"});
|
||||
}
|
||||
|
||||
inline bool PyObjectTypeIsConvertibleToStdSet(PyObject *obj) {
|
||||
return (PyAnySet_Check(obj) != 0) || PyObjectIsInstanceWithOneOfTpNames(obj, {"dict_keys"});
|
||||
}
|
||||
|
||||
inline bool PyObjectTypeIsConvertibleToStdMap(PyObject *obj) {
|
||||
if (PyDict_Check(obj)) {
|
||||
return true;
|
||||
}
|
||||
// Implicit requirement in the conditions below:
|
||||
// A type with `.__getitem__()` & `.items()` methods must implement these
|
||||
// to be compatible with https://docs.python.org/3/c-api/mapping.html
|
||||
if (PyMapping_Check(obj) == 0) {
|
||||
return false;
|
||||
}
|
||||
PyObject *items = PyObject_GetAttrString(obj, "items");
|
||||
if (items == nullptr) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
bool is_convertible = (PyCallable_Check(items) != 0);
|
||||
Py_DECREF(items);
|
||||
return is_convertible;
|
||||
}
|
||||
|
||||
//
|
||||
// End: Equivalent of clif/python/runtime.cc
|
||||
//
|
||||
|
||||
/// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for
|
||||
/// forwarding a container element). Typically used indirect via forwarded_type(), below.
|
||||
template <typename T, typename U>
|
||||
|
@ -153,10 +66,17 @@ private:
|
|||
}
|
||||
void reserve_maybe(const anyset &, void *) {}
|
||||
|
||||
bool convert_iterable(const iterable &itbl, bool convert) {
|
||||
for (const auto &it : itbl) {
|
||||
public:
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<anyset>(src)) {
|
||||
return false;
|
||||
}
|
||||
auto s = reinterpret_borrow<anyset>(src);
|
||||
value.clear();
|
||||
reserve_maybe(s, &value);
|
||||
for (auto entry : s) {
|
||||
key_conv conv;
|
||||
if (!conv.load(it, convert)) {
|
||||
if (!conv.load(entry, convert)) {
|
||||
return false;
|
||||
}
|
||||
value.insert(cast_op<Key &&>(std::move(conv)));
|
||||
|
@ -164,29 +84,6 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool convert_anyset(anyset s, bool convert) {
|
||||
value.clear();
|
||||
reserve_maybe(s, &value);
|
||||
return convert_iterable(s, convert);
|
||||
}
|
||||
|
||||
public:
|
||||
bool load(handle src, bool convert) {
|
||||
if (!PyObjectTypeIsConvertibleToStdSet(src.ptr())) {
|
||||
return false;
|
||||
}
|
||||
if (isinstance<anyset>(src)) {
|
||||
value.clear();
|
||||
return convert_anyset(reinterpret_borrow<anyset>(src), convert);
|
||||
}
|
||||
if (!convert) {
|
||||
return false;
|
||||
}
|
||||
assert(isinstance<iterable>(src));
|
||||
value.clear();
|
||||
return convert_iterable(reinterpret_borrow<iterable>(src), convert);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
if (!std::is_lvalue_reference<T>::value) {
|
||||
|
@ -218,10 +115,15 @@ private:
|
|||
}
|
||||
void reserve_maybe(const dict &, void *) {}
|
||||
|
||||
bool convert_elements(const dict &d, bool convert) {
|
||||
public:
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<dict>(src)) {
|
||||
return false;
|
||||
}
|
||||
auto d = reinterpret_borrow<dict>(src);
|
||||
value.clear();
|
||||
reserve_maybe(d, &value);
|
||||
for (const auto &it : d) {
|
||||
for (auto it : d) {
|
||||
key_conv kconv;
|
||||
value_conv vconv;
|
||||
if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
|
||||
|
@ -232,25 +134,6 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
bool load(handle src, bool convert) {
|
||||
if (!PyObjectTypeIsConvertibleToStdMap(src.ptr())) {
|
||||
return false;
|
||||
}
|
||||
if (isinstance<dict>(src)) {
|
||||
return convert_elements(reinterpret_borrow<dict>(src), convert);
|
||||
}
|
||||
if (!convert) {
|
||||
return false;
|
||||
}
|
||||
auto items = reinterpret_steal<object>(PyMapping_Items(src.ptr()));
|
||||
if (!items) {
|
||||
throw error_already_set();
|
||||
}
|
||||
assert(isinstance<iterable>(items));
|
||||
return convert_elements(dict(reinterpret_borrow<iterable>(items)), convert);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
dict d;
|
||||
|
@ -283,35 +166,13 @@ struct list_caster {
|
|||
using value_conv = make_caster<Value>;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) {
|
||||
if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
|
||||
return false;
|
||||
}
|
||||
if (isinstance<sequence>(src)) {
|
||||
return convert_elements(src, convert);
|
||||
}
|
||||
if (!convert) {
|
||||
return false;
|
||||
}
|
||||
// Designed to be behavior-equivalent to passing tuple(src) from Python:
|
||||
// The conversion to a tuple will first exhaust the generator object, to ensure that
|
||||
// the generator is not left in an unpredictable (to the caller) partially-consumed
|
||||
// state.
|
||||
assert(isinstance<iterable>(src));
|
||||
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
|
||||
void reserve_maybe(const sequence &s, Type *) {
|
||||
value.reserve(s.size());
|
||||
}
|
||||
void reserve_maybe(const sequence &, void *) {}
|
||||
|
||||
bool convert_elements(handle seq, bool convert) {
|
||||
auto s = reinterpret_borrow<sequence>(seq);
|
||||
auto s = reinterpret_borrow<sequence>(src);
|
||||
value.clear();
|
||||
reserve_maybe(s, &value);
|
||||
for (const auto &it : seq) {
|
||||
for (const auto &it : s) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert)) {
|
||||
return false;
|
||||
|
@ -321,6 +182,13 @@ private:
|
|||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T = Type, enable_if_t<has_reserve_method<T>::value, int> = 0>
|
||||
void reserve_maybe(const sequence &s, Type *) {
|
||||
value.reserve(s.size());
|
||||
}
|
||||
void reserve_maybe(const sequence &, void *) {}
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
|
@ -352,87 +220,43 @@ struct type_caster<std::deque<Type, Alloc>> : list_caster<std::deque<Type, Alloc
|
|||
template <typename Type, typename Alloc>
|
||||
struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {};
|
||||
|
||||
template <typename ArrayType, typename V, size_t... I>
|
||||
ArrayType vector_to_array_impl(V &&v, index_sequence<I...>) {
|
||||
return {{std::move(v[I])...}};
|
||||
}
|
||||
|
||||
// Based on https://en.cppreference.com/w/cpp/container/array/to_array
|
||||
template <typename ArrayType, size_t N, typename V>
|
||||
ArrayType vector_to_array(V &&v) {
|
||||
return vector_to_array_impl<ArrayType, V>(std::forward<V>(v), make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0>
|
||||
struct array_caster {
|
||||
using value_conv = make_caster<Value>;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ArrayType> value;
|
||||
template <bool R = Resizable>
|
||||
bool require_size(enable_if_t<R, size_t> size) {
|
||||
if (value.size() != size) {
|
||||
value.resize(size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
template <bool R = Resizable>
|
||||
bool require_size(enable_if_t<!R, size_t> size) {
|
||||
return size == Size;
|
||||
}
|
||||
|
||||
template <bool R = Resizable, enable_if_t<R, int> = 0>
|
||||
bool convert_elements(handle seq, bool convert) {
|
||||
auto l = reinterpret_borrow<sequence>(seq);
|
||||
value.reset(new ArrayType{});
|
||||
// Using `resize` to preserve the behavior exactly as it was before PR #5305
|
||||
// For the `resize` to work, `Value` must be default constructible.
|
||||
// For `std::valarray`, this is a requirement:
|
||||
// https://en.cppreference.com/w/cpp/named_req/NumericType
|
||||
value->resize(l.size());
|
||||
public:
|
||||
bool load(handle src, bool convert) {
|
||||
if (!isinstance<sequence>(src)) {
|
||||
return false;
|
||||
}
|
||||
auto l = reinterpret_borrow<sequence>(src);
|
||||
if (!require_size(l.size())) {
|
||||
return false;
|
||||
}
|
||||
size_t ctr = 0;
|
||||
for (const auto &it : l) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert)) {
|
||||
return false;
|
||||
}
|
||||
(*value)[ctr++] = cast_op<Value &&>(std::move(conv));
|
||||
value[ctr++] = cast_op<Value &&>(std::move(conv));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool R = Resizable, enable_if_t<!R, int> = 0>
|
||||
bool convert_elements(handle seq, bool convert) {
|
||||
auto l = reinterpret_borrow<sequence>(seq);
|
||||
if (l.size() != Size) {
|
||||
return false;
|
||||
}
|
||||
// The `temp` storage is needed to support `Value` types that are not
|
||||
// default-constructible.
|
||||
// Deliberate choice: no template specializations, for simplicity, and
|
||||
// because the compile time overhead for the specializations is deemed
|
||||
// more significant than the runtime overhead for the `temp` storage.
|
||||
std::vector<Value> temp;
|
||||
temp.reserve(l.size());
|
||||
for (auto it : l) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert)) {
|
||||
return false;
|
||||
}
|
||||
temp.emplace_back(cast_op<Value &&>(std::move(conv)));
|
||||
}
|
||||
value.reset(new ArrayType(vector_to_array<ArrayType, Size>(std::move(temp))));
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
bool load(handle src, bool convert) {
|
||||
if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) {
|
||||
return false;
|
||||
}
|
||||
if (isinstance<sequence>(src)) {
|
||||
return convert_elements(src, convert);
|
||||
}
|
||||
if (!convert) {
|
||||
return false;
|
||||
}
|
||||
// Designed to be behavior-equivalent to passing tuple(src) from Python:
|
||||
// The conversion to a tuple will first exhaust the generator object, to ensure that
|
||||
// the generator is not left in an unpredictable (to the caller) partially-consumed
|
||||
// state.
|
||||
assert(isinstance<iterable>(src));
|
||||
return convert_elements(tuple(reinterpret_borrow<iterable>(src)), convert);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||
list l(src.size());
|
||||
|
@ -448,36 +272,12 @@ public:
|
|||
return l.release();
|
||||
}
|
||||
|
||||
// Code copied from PYBIND11_TYPE_CASTER macro.
|
||||
// Intentionally preserving the behavior exactly as it was before PR #5305
|
||||
template <typename T_, enable_if_t<std::is_same<ArrayType, remove_cv_t<T_>>::value, int> = 0>
|
||||
static handle cast(T_ *src, return_value_policy policy, handle parent) {
|
||||
if (!src) {
|
||||
return none().release();
|
||||
}
|
||||
if (policy == return_value_policy::take_ownership) {
|
||||
auto h = cast(std::move(*src), policy, parent);
|
||||
delete src; // WARNING: Assumes `src` was allocated with `new`.
|
||||
return h;
|
||||
}
|
||||
return cast(*src, policy, parent);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator ArrayType *() { return &(*value); }
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator ArrayType &() { return *value; }
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator ArrayType &&() && { return std::move(*value); }
|
||||
|
||||
template <typename T_>
|
||||
using cast_op_type = movable_cast_op_type<T_>;
|
||||
|
||||
static constexpr auto name
|
||||
= const_name<Resizable>(const_name(""), const_name("Annotated[")) + const_name("list[")
|
||||
+ value_conv::name + const_name("]")
|
||||
+ const_name<Resizable>(
|
||||
const_name(""), const_name(", FixedSize(") + const_name<Size>() + const_name(")]"));
|
||||
PYBIND11_TYPE_CASTER(ArrayType,
|
||||
const_name<Resizable>(const_name(""), const_name("Annotated["))
|
||||
+ const_name("list[") + value_conv::name + const_name("]")
|
||||
+ const_name<Resizable>(const_name(""),
|
||||
const_name(", FixedSize(")
|
||||
+ const_name<Size>() + const_name(")]")));
|
||||
};
|
||||
|
||||
template <typename Type, size_t Size>
|
||||
|
|
|
@ -106,7 +106,7 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
PYBIND11_TYPE_CASTER(T, io_name("Union[os.PathLike, str, bytes]", "pathlib.Path"));
|
||||
PYBIND11_TYPE_CASTER(T, const_name("os.PathLike"));
|
||||
};
|
||||
|
||||
#endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
|
||||
|
|
|
@ -487,7 +487,7 @@ PYBIND11_NAMESPACE_END(detail)
|
|||
//
|
||||
// std::vector
|
||||
//
|
||||
template <typename Vector, typename holder_type = default_holder_type<Vector>, typename... Args>
|
||||
template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
|
||||
class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args &&...args) {
|
||||
using Class_ = class_<Vector, holder_type>;
|
||||
|
||||
|
@ -694,43 +694,9 @@ struct ItemsViewImpl : public detail::items_view {
|
|||
Map ↦
|
||||
};
|
||||
|
||||
inline str format_message_key_error_key_object(handle py_key) {
|
||||
str message = "pybind11::bind_map key";
|
||||
if (!py_key) {
|
||||
return message;
|
||||
}
|
||||
try {
|
||||
message = str(py_key);
|
||||
} catch (const std::exception &) {
|
||||
try {
|
||||
message = repr(py_key);
|
||||
} catch (const std::exception &) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
const ssize_t cut_length = 100;
|
||||
if (len(message) > 2 * cut_length + 3) {
|
||||
return str(message[slice(0, cut_length, 1)]) + str("✄✄✄")
|
||||
+ str(message[slice(-cut_length, static_cast<ssize_t>(len(message)), 1)]);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
template <typename KeyType>
|
||||
str format_message_key_error(const KeyType &key) {
|
||||
object py_key;
|
||||
try {
|
||||
py_key = cast(key);
|
||||
} catch (const std::exception &) {
|
||||
do { // Trick to avoid "empty catch" warning/error.
|
||||
} while (false);
|
||||
}
|
||||
return format_message_key_error_key_object(py_key);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
template <typename Map, typename holder_type = default_holder_type<Map>, typename... Args>
|
||||
template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
|
||||
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
|
||||
using KeyType = typename Map::key_type;
|
||||
using MappedType = typename Map::mapped_type;
|
||||
|
@ -819,8 +785,7 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
|
|||
[](Map &m, const KeyType &k) -> MappedType & {
|
||||
auto it = m.find(k);
|
||||
if (it == m.end()) {
|
||||
set_error(PyExc_KeyError, detail::format_message_key_error(k));
|
||||
throw error_already_set();
|
||||
throw key_error();
|
||||
}
|
||||
return it->second;
|
||||
},
|
||||
|
@ -843,8 +808,7 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
|
|||
cl.def("__delitem__", [](Map &m, const KeyType &k) {
|
||||
auto it = m.find(k);
|
||||
if (it == m.end()) {
|
||||
set_error(PyExc_KeyError, detail::format_message_key_error(k));
|
||||
throw error_already_set();
|
||||
throw key_error();
|
||||
}
|
||||
m.erase(it);
|
||||
});
|
||||
|
|
|
@ -1,60 +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 "detail/common.h"
|
||||
#include "detail/using_smart_holder.h"
|
||||
#include "detail/value_and_holder.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code.
|
||||
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
// The original core idea for this struct goes back to PyCLIF:
|
||||
// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
|
||||
// URL provided here mainly to give proper credit.
|
||||
struct trampoline_self_life_support {
|
||||
detail::value_and_holder v_h;
|
||||
|
||||
trampoline_self_life_support() = default;
|
||||
|
||||
void activate_life_support(const detail::value_and_holder &v_h_) {
|
||||
Py_INCREF((PyObject *) v_h_.inst);
|
||||
v_h = v_h_;
|
||||
}
|
||||
|
||||
void deactivate_life_support() {
|
||||
Py_DECREF((PyObject *) v_h.inst);
|
||||
v_h = detail::value_and_holder();
|
||||
}
|
||||
|
||||
~trampoline_self_life_support() {
|
||||
if (v_h.inst != nullptr && v_h.vh != nullptr) {
|
||||
void *value_void_ptr = v_h.value_ptr();
|
||||
if (value_void_ptr != nullptr) {
|
||||
PyGILState_STATE threadstate = PyGILState_Ensure();
|
||||
v_h.value_ptr() = nullptr;
|
||||
v_h.holder<smart_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the next two, the default implementations generate undefined behavior (ASAN failures
|
||||
// manually verified). The reason is that v_h needs to be kept default-initialized.
|
||||
trampoline_self_life_support(const trampoline_self_life_support &) {}
|
||||
trampoline_self_life_support(trampoline_self_life_support &&) noexcept {}
|
||||
|
||||
// These should never be needed (please provide test cases if you think they are).
|
||||
trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete;
|
||||
trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete;
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
|
@ -16,13 +16,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L
|
||||
# define PYBIND11_TYPING_H_HAS_STRING_LITERAL
|
||||
# include <numeric>
|
||||
# include <ranges>
|
||||
# include <string_view>
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(typing)
|
||||
|
||||
|
@ -89,18 +82,6 @@ class Optional : public object {
|
|||
using object::object;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Final : public object {
|
||||
PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type)
|
||||
using object::object;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ClassVar : public object {
|
||||
PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type)
|
||||
using object::object;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class TypeGuard : public bool_ {
|
||||
using bool_::bool_;
|
||||
|
@ -119,7 +100,8 @@ class Never : public none {
|
|||
using none::none;
|
||||
};
|
||||
|
||||
#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL)
|
||||
#if defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911L
|
||||
# define PYBIND11_TYPING_H_HAS_STRING_LITERAL
|
||||
template <size_t N>
|
||||
struct StringLiteral {
|
||||
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); }
|
||||
|
@ -194,19 +176,16 @@ template <typename Return, typename... Args>
|
|||
struct handle_type_name<typing::Callable<Return(Args...)>> {
|
||||
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
|
||||
static constexpr auto name
|
||||
= const_name("Callable[[")
|
||||
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
|
||||
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
|
||||
+ const_name("]");
|
||||
= const_name("Callable[[") + ::pybind11::detail::concat(make_caster<Args>::name...)
|
||||
+ const_name("], ") + make_caster<retval_type>::name + const_name("]");
|
||||
};
|
||||
|
||||
template <typename Return>
|
||||
struct handle_type_name<typing::Callable<Return(ellipsis)>> {
|
||||
// PEP 484 specifies this syntax for defining only return types of callables
|
||||
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
|
||||
static constexpr auto name = const_name("Callable[..., ")
|
||||
+ ::pybind11::detail::return_descr(make_caster<retval_type>::name)
|
||||
+ const_name("]");
|
||||
static constexpr auto name
|
||||
= const_name("Callable[..., ") + make_caster<retval_type>::name + const_name("]");
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
@ -226,16 +205,6 @@ struct handle_type_name<typing::Optional<T>> {
|
|||
static constexpr auto name = const_name("Optional[") + make_caster<T>::name + const_name("]");
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct handle_type_name<typing::Final<T>> {
|
||||
static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]");
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct handle_type_name<typing::ClassVar<T>> {
|
||||
static constexpr auto name = const_name("ClassVar[") + make_caster<T>::name + const_name("]");
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct handle_type_name<typing::TypeGuard<T>> {
|
||||
static constexpr auto name = const_name("TypeGuard[") + make_caster<T>::name + const_name("]");
|
||||
|
@ -257,35 +226,15 @@ struct handle_type_name<typing::Never> {
|
|||
};
|
||||
|
||||
#if defined(PYBIND11_TYPING_H_HAS_STRING_LITERAL)
|
||||
template <typing::StringLiteral StrLit>
|
||||
consteval auto sanitize_string_literal() {
|
||||
constexpr std::string_view v(StrLit.name);
|
||||
constexpr std::string_view special_chars("!@%{}-");
|
||||
constexpr auto num_special_chars = std::accumulate(
|
||||
special_chars.begin(), special_chars.end(), (size_t) 0, [&v](auto acc, const char &c) {
|
||||
return std::move(acc) + std::ranges::count(v, c);
|
||||
});
|
||||
char result[v.size() + num_special_chars + 1];
|
||||
size_t i = 0;
|
||||
for (auto c : StrLit.name) {
|
||||
if (special_chars.find(c) != std::string_view::npos) {
|
||||
result[i++] = '!';
|
||||
}
|
||||
result[i++] = c;
|
||||
}
|
||||
return typing::StringLiteral(result);
|
||||
}
|
||||
|
||||
template <typing::StringLiteral... Literals>
|
||||
struct handle_type_name<typing::Literal<Literals...>> {
|
||||
static constexpr auto name
|
||||
= const_name("Literal[")
|
||||
+ pybind11::detail::concat(const_name(sanitize_string_literal<Literals>().name)...)
|
||||
+ const_name("]");
|
||||
static constexpr auto name = const_name("Literal[")
|
||||
+ pybind11::detail::concat(const_name(Literals.name)...)
|
||||
+ const_name("]");
|
||||
};
|
||||
template <typing::StringLiteral StrLit>
|
||||
struct handle_type_name<typing::TypeVar<StrLit>> {
|
||||
static constexpr auto name = const_name(sanitize_string_literal<StrLit>().name);
|
||||
static constexpr auto name = const_name(StrLit.name);
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
pybind11/warnings.h: Python warnings wrappers.
|
||||
|
||||
Copyright (c) 2024 Jan Iwaszkiewicz <jiwaszkiewicz6@gmail.com>
|
||||
|
||||
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"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
inline bool PyWarning_Check(PyObject *obj) {
|
||||
int result = PyObject_IsSubclass(obj, PyExc_Warning);
|
||||
if (result == 1) {
|
||||
return true;
|
||||
}
|
||||
if (result == -1) {
|
||||
raise_from(PyExc_SystemError,
|
||||
"pybind11::detail::PyWarning_Check(): PyObject_IsSubclass() call failed.");
|
||||
throw error_already_set();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(warnings)
|
||||
|
||||
inline object
|
||||
new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarning) {
|
||||
if (!detail::PyWarning_Check(base.ptr())) {
|
||||
pybind11_fail("pybind11::warnings::new_warning_type(): cannot create custom warning, base "
|
||||
"must be a subclass of "
|
||||
"PyExc_Warning!");
|
||||
}
|
||||
if (hasattr(scope, name)) {
|
||||
pybind11_fail("pybind11::warnings::new_warning_type(): an attribute with name \""
|
||||
+ std::string(name) + "\" exists already.");
|
||||
}
|
||||
std::string full_name = scope.attr("__name__").cast<std::string>() + std::string(".") + name;
|
||||
handle h(PyErr_NewException(full_name.c_str(), base.ptr(), nullptr));
|
||||
if (!h) {
|
||||
raise_from(PyExc_SystemError,
|
||||
"pybind11::warnings::new_warning_type(): PyErr_NewException() call failed.");
|
||||
throw error_already_set();
|
||||
}
|
||||
auto obj = reinterpret_steal<object>(h);
|
||||
scope.attr(name) = obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Similar to Python `warnings.warn()`
|
||||
inline void
|
||||
warn(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) {
|
||||
if (!detail::PyWarning_Check(category.ptr())) {
|
||||
pybind11_fail(
|
||||
"pybind11::warnings::warn(): cannot raise warning, category must be a subclass of "
|
||||
"PyExc_Warning!");
|
||||
}
|
||||
|
||||
if (PyErr_WarnEx(category.ptr(), message, stack_level) == -1) {
|
||||
throw error_already_set();
|
||||
}
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(warnings)
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
|
@ -2,8 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 8): # noqa: UP036
|
||||
msg = "pybind11 does not support Python < 3.8. v2.13 was the last release supporting Python 3.7."
|
||||
if sys.version_info < (3, 7): # noqa: UP036
|
||||
msg = "pybind11 does not support Python < 3.7. v2.12 was the last release supporting Python 3.6."
|
||||
raise ImportError(msg)
|
||||
|
||||
|
||||
|
|
|
@ -71,11 +71,6 @@ def main() -> None:
|
|||
action="store_true",
|
||||
help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--extension-suffix",
|
||||
action="store_true",
|
||||
help="Print the extension for a Python module",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if not sys.argv[1:]:
|
||||
parser.print_help()
|
||||
|
@ -85,8 +80,6 @@ def main() -> None:
|
|||
print(quote(get_cmake_dir()))
|
||||
if args.pkgconfigdir:
|
||||
print(quote(get_pkgconfig_dir()))
|
||||
if args.extension_suffix:
|
||||
print(sysconfig.get_config_var("EXT_SUFFIX"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str:
|
|||
return s
|
||||
|
||||
|
||||
__version__ = "3.0.0.dev1"
|
||||
__version__ = "2.13.6"
|
||||
version_info = tuple(_to_int(s) for s in __version__.split("."))
|
||||
|
|
|
@ -249,7 +249,7 @@ def has_flag(compiler: Any, flag: str) -> bool:
|
|||
cpp_flag_cache = None
|
||||
|
||||
|
||||
@lru_cache
|
||||
@lru_cache()
|
||||
def auto_cpp_level(compiler: Any) -> str | int:
|
||||
"""
|
||||
Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows.
|
||||
|
|
|
@ -30,7 +30,7 @@ ignore_missing_imports = true
|
|||
|
||||
|
||||
[tool.pylint]
|
||||
master.py-version = "3.8"
|
||||
master.py-version = "3.7"
|
||||
reports.output-format = "colorized"
|
||||
messages_control.disable = [
|
||||
"design",
|
||||
|
@ -45,7 +45,7 @@ messages_control.disable = [
|
|||
]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py38"
|
||||
target-version = "py37"
|
||||
src = ["src"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
|
@ -71,6 +71,7 @@ ignore = [
|
|||
"PLR", # Design related pylint
|
||||
"E501", # Line too long (Black is enough)
|
||||
"PT011", # Too broad with raises in pytest
|
||||
"PT004", # Fixture that doesn't return needs underscore (no, it is fine)
|
||||
"SIM118", # iter(x) is not always the same as iter(x.keys())
|
||||
]
|
||||
unfixable = ["T20"]
|
||||
|
|
|
@ -14,6 +14,7 @@ classifiers =
|
|||
Topic :: Utilities
|
||||
Programming Language :: C++
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
|
@ -38,5 +39,5 @@ project_urls =
|
|||
Chat = https://gitter.im/pybind/Lobby
|
||||
|
||||
[options]
|
||||
python_requires = >=3.8
|
||||
python_requires = >=3.7
|
||||
zip_safe = False
|
||||
|
|
|
@ -144,10 +144,6 @@ with remove_output("pybind11/include", "pybind11/share"):
|
|||
stderr=sys.stderr,
|
||||
)
|
||||
|
||||
# pkgconf-pypi needs pybind11/share/pkgconfig to be importable
|
||||
Path("pybind11/share/__init__.py").touch()
|
||||
Path("pybind11/share/pkgconfig/__init__.py").touch()
|
||||
|
||||
txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd)
|
||||
code = compile(txt, setup_py, "exec")
|
||||
exec(code, {"SDist": SDist})
|
||||
|
|
|
@ -5,7 +5,16 @@
|
|||
# All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...3.30)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with
|
||||
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
|
||||
# the behavior using the following workaround:
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.29)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
else()
|
||||
cmake_policy(VERSION 3.29)
|
||||
endif()
|
||||
|
||||
# Filter out items; print an optional message if any items filtered. This ignores extensions.
|
||||
#
|
||||
|
@ -67,8 +76,8 @@ project(pybind11_tests CXX)
|
|||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../tools")
|
||||
|
||||
option(PYBIND11_WERROR "Report all warnings as errors" OFF)
|
||||
option(DOWNLOAD_EIGEN "Download EIGEN" OFF)
|
||||
option(PYBIND11_CUDA_TESTS "Enable building CUDA tests" OFF)
|
||||
option(DOWNLOAD_EIGEN "Download EIGEN (requires CMake 3.11+)" OFF)
|
||||
option(PYBIND11_CUDA_TESTS "Enable building CUDA tests (requires CMake 3.12+)" OFF)
|
||||
set(PYBIND11_TEST_OVERRIDE
|
||||
""
|
||||
CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests")
|
||||
|
@ -115,24 +124,6 @@ set(PYBIND11_TEST_FILES
|
|||
test_callbacks
|
||||
test_chrono
|
||||
test_class
|
||||
test_class_release_gil_before_calling_cpp_dtor
|
||||
test_class_sh_basic
|
||||
test_class_sh_disowning
|
||||
test_class_sh_disowning_mi
|
||||
test_class_sh_factory_constructors
|
||||
test_class_sh_inheritance
|
||||
test_class_sh_mi_thunks
|
||||
test_class_sh_property
|
||||
test_class_sh_property_non_owning
|
||||
test_class_sh_shared_ptr_copy_move
|
||||
test_class_sh_trampoline_basic
|
||||
test_class_sh_trampoline_self_life_support
|
||||
test_class_sh_trampoline_shared_from_this
|
||||
test_class_sh_trampoline_shared_ptr_cpp_arg
|
||||
test_class_sh_trampoline_unique_ptr
|
||||
test_class_sh_unique_ptr_custom_deleter
|
||||
test_class_sh_unique_ptr_member
|
||||
test_class_sh_virtual_py_cpp_mix
|
||||
test_const_name
|
||||
test_constants_and_functions
|
||||
test_copy_move
|
||||
|
@ -140,7 +131,6 @@ set(PYBIND11_TEST_FILES
|
|||
test_custom_type_casters
|
||||
test_custom_type_setup
|
||||
test_docstring_options
|
||||
test_docs_advanced_cast_custom
|
||||
test_eigen_matrix
|
||||
test_eigen_tensor
|
||||
test_enum
|
||||
|
@ -174,8 +164,7 @@ set(PYBIND11_TEST_FILES
|
|||
test_unnamed_namespace_a
|
||||
test_unnamed_namespace_b
|
||||
test_vector_unique_ptr_member
|
||||
test_virtual_functions
|
||||
test_warnings)
|
||||
test_virtual_functions)
|
||||
|
||||
# Invoking cmake with something like:
|
||||
# cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" ..
|
||||
|
@ -263,21 +252,25 @@ endif()
|
|||
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
|
||||
# Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake).
|
||||
# Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also
|
||||
# produces a fatal error if loaded from a pre-3.0 cmake.
|
||||
if(DOWNLOAD_EIGEN)
|
||||
if(CMAKE_VERSION VERSION_LESS 3.18)
|
||||
set(_opts)
|
||||
else()
|
||||
set(_opts SOURCE_SUBDIR no-cmake-build)
|
||||
if(CMAKE_VERSION VERSION_LESS 3.11)
|
||||
message(FATAL_ERROR "CMake 3.11+ required when using DOWNLOAD_EIGEN")
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
eigen
|
||||
GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}"
|
||||
GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}"
|
||||
${_opts})
|
||||
FetchContent_MakeAvailable(eigen)
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.18)
|
||||
set(EIGEN3_INCLUDE_DIR "${eigen_SOURCE_DIR}")
|
||||
GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}")
|
||||
|
||||
FetchContent_GetProperties(eigen)
|
||||
if(NOT eigen_POPULATED)
|
||||
message(
|
||||
STATUS
|
||||
"Downloading Eigen ${PYBIND11_EIGEN_VERSION_STRING} (${PYBIND11_EIGEN_VERSION_HASH}) from ${PYBIND11_EIGEN_REPO}"
|
||||
)
|
||||
FetchContent_Populate(eigen)
|
||||
endif()
|
||||
|
||||
set(EIGEN3_INCLUDE_DIR ${eigen_SOURCE_DIR})
|
||||
|
@ -325,7 +318,8 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
|
|||
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
|
||||
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
|
||||
endif()
|
||||
message(STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON to download")
|
||||
message(
|
||||
STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -510,19 +504,15 @@ foreach(target ${test_targets})
|
|||
if(SKBUILD)
|
||||
install(TARGETS ${target} LIBRARY DESTINATION .)
|
||||
endif()
|
||||
|
||||
if("${target}" STREQUAL "exo_planet_c_api")
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang|NVHPC)")
|
||||
target_compile_options(${target} PRIVATE -fno-exceptions)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Provide nice organisation in IDEs
|
||||
source_group(
|
||||
TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||
PREFIX "Header Files"
|
||||
FILES ${PYBIND11_HEADERS})
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.8)
|
||||
source_group(
|
||||
TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||
PREFIX "Header Files"
|
||||
FILES ${PYBIND11_HEADERS})
|
||||
endif()
|
||||
|
||||
# Make sure pytest is found or produce a warning
|
||||
pybind11_find_import(pytest VERSION 3.1)
|
||||
|
@ -606,9 +596,6 @@ add_custom_command(
|
|||
${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
|
||||
|
||||
if(NOT PYBIND11_CUDA_TESTS)
|
||||
# Test pure C++ code (not depending on Python). Provides the `test_pure_cpp` target.
|
||||
add_subdirectory(pure_cpp)
|
||||
|
||||
# Test embedding the interpreter. Provides the `cpptest` target.
|
||||
add_subdirectory(test_embed)
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ except Exception:
|
|||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def use_multiprocessing_forkserver_on_linux():
|
||||
if sys.platform != "linux" or sys.implementation.name == "graalpy":
|
||||
# The default on Windows, macOS and GraalPy is "spawn": If it's not broken, don't fix it.
|
||||
if sys.platform != "linux":
|
||||
# The default on Windows and macOS is "spawn": If it's not broken, don't fix it.
|
||||
return
|
||||
|
||||
# Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592
|
||||
|
@ -136,7 +136,7 @@ class Capture:
|
|||
return Output(self.err)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture()
|
||||
def capture(capsys):
|
||||
"""Extended `capsys` with context manager and custom equality operators"""
|
||||
return Capture(capsys)
|
||||
|
@ -172,7 +172,7 @@ def _sanitize_docstring(thing):
|
|||
return _sanitize_general(s)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture()
|
||||
def doc():
|
||||
"""Sanitize docstrings and add custom failure explanation"""
|
||||
return SanitizedString(_sanitize_docstring)
|
||||
|
@ -184,7 +184,7 @@ def _sanitize_message(thing):
|
|||
return _hexadecimal.sub("0", s)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture()
|
||||
def msg():
|
||||
"""Sanitize messages and add custom failure explanation"""
|
||||
return SanitizedString(_sanitize_message)
|
||||
|
@ -198,11 +198,10 @@ def pytest_assertrepr_compare(op, left, right): # noqa: ARG001
|
|||
|
||||
|
||||
def gc_collect():
|
||||
"""Run the garbage collector three times (needed when running
|
||||
"""Run the garbage collector twice (needed when running
|
||||
reference counting tests with PyPy)"""
|
||||
gc.collect()
|
||||
gc.collect()
|
||||
gc.collect()
|
||||
|
||||
|
||||
def pytest_configure():
|
||||
|
@ -212,9 +211,9 @@ def pytest_configure():
|
|||
|
||||
def pytest_report_header(config):
|
||||
del config # Unused.
|
||||
assert pybind11_tests.compiler_info is not None, (
|
||||
"Please update pybind11_tests.cpp if this assert fails."
|
||||
)
|
||||
assert (
|
||||
pybind11_tests.compiler_info is not None
|
||||
), "Please update pybind11_tests.cpp if this assert fails."
|
||||
return (
|
||||
"C++ Info:"
|
||||
f" {pybind11_tests.compiler_info}"
|
||||
|
|
|
@ -312,16 +312,8 @@ void print_created(T *inst, Values &&...values) {
|
|||
}
|
||||
template <class T, typename... Values>
|
||||
void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
|
||||
/*
|
||||
* On GraalPy, destructors can trigger anywhere and this can cause random
|
||||
* failures in unrelated tests.
|
||||
*/
|
||||
#if !defined(GRAALVM_PYTHON)
|
||||
print_constr_details(inst, "destroyed", values...);
|
||||
track_destroyed(inst);
|
||||
#else
|
||||
py::detail::silence_unused_warnings(inst, values...);
|
||||
#endif
|
||||
}
|
||||
template <class T, typename... Values>
|
||||
void print_values(T *inst, Values &&...values) {
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
class PythonMyException7(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self):
|
||||
return "[PythonMyException7]: " + self.message.a
|
|
@ -12,7 +12,6 @@ WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
|
|||
|
||||
CPYTHON = platform.python_implementation() == "CPython"
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
GRAALPY = sys.implementation.name == "graalpy"
|
||||
PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
||||
|
||||
|
||||
|
|
|
@ -1,33 +1,59 @@
|
|||
// Copyright (c) 2024 The pybind Community.
|
||||
|
||||
// In production situations it is totally fine to build with
|
||||
// C++ Exception Handling enabled. However, here we want to ensure that
|
||||
// C++ Exception Handling is not required.
|
||||
#if defined(_MSC_VER) || defined(__EMSCRIPTEN__)
|
||||
// Too much trouble making the required cmake changes (see PR #5375).
|
||||
#else
|
||||
# ifdef __cpp_exceptions
|
||||
// https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations#__cpp_exceptions
|
||||
# error This test is meant to be built with C++ Exception Handling disabled, but __cpp_exceptions is defined.
|
||||
# endif
|
||||
# ifdef __EXCEPTIONS
|
||||
// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
|
||||
# error This test is meant to be built with C++ Exception Handling disabled, but __EXCEPTIONS is defined.
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// THIS MUST STAY AT THE TOP!
|
||||
#include <pybind11/conduit/pybind11_conduit_v1.h> // VERY light-weight dependency.
|
||||
#include <pybind11/pybind11.h> // EXCLUSIVELY for PYBIND11_PLATFORM_ABI_ID
|
||||
// Potential future direction to maximize reusability:
|
||||
// (e.g. for use from SWIG, Cython, PyCLIF, nanobind):
|
||||
// #include <pybind11/compat/platform_abi_id.h>
|
||||
// This would only depend on:
|
||||
// 1. A C++ compiler, WITHOUT requiring -fexceptions.
|
||||
// 2. Python.h
|
||||
|
||||
#include "test_cpp_conduit_traveler_types.h"
|
||||
|
||||
#include <Python.h>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace {
|
||||
|
||||
void *get_cpp_conduit_void_ptr(PyObject *py_obj, const std::type_info *cpp_type_info) {
|
||||
PyObject *cpp_type_info_capsule
|
||||
= PyCapsule_New(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
|
||||
typeid(std::type_info).name(),
|
||||
nullptr);
|
||||
if (cpp_type_info_capsule == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
PyObject *cpp_conduit = PyObject_CallMethod(py_obj,
|
||||
"_pybind11_conduit_v1_",
|
||||
"yOy",
|
||||
PYBIND11_PLATFORM_ABI_ID,
|
||||
cpp_type_info_capsule,
|
||||
"raw_pointer_ephemeral");
|
||||
Py_DECREF(cpp_type_info_capsule);
|
||||
if (cpp_conduit == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
void *void_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name());
|
||||
Py_DECREF(cpp_conduit);
|
||||
if (PyErr_Occurred()) {
|
||||
return nullptr;
|
||||
}
|
||||
return void_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *get_cpp_conduit_type_ptr(PyObject *py_obj) {
|
||||
void *void_ptr = get_cpp_conduit_void_ptr(py_obj, &typeid(T));
|
||||
if (void_ptr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<T *>(void_ptr);
|
||||
}
|
||||
|
||||
extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) {
|
||||
const auto *cpp_traveler = pybind11_conduit_v1::get_type_pointer_ephemeral<
|
||||
pybind11_tests::test_cpp_conduit::Traveler>(traveler);
|
||||
const auto *cpp_traveler
|
||||
= get_cpp_conduit_type_ptr<pybind11_tests::test_cpp_conduit::Traveler>(traveler);
|
||||
if (cpp_traveler == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -35,8 +61,9 @@ extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) {
|
|||
}
|
||||
|
||||
extern "C" PyObject *wrapGetPoints(PyObject * /*self*/, PyObject *premium_traveler) {
|
||||
const auto *cpp_premium_traveler = pybind11_conduit_v1::get_type_pointer_ephemeral<
|
||||
pybind11_tests::test_cpp_conduit::PremiumTraveler>(premium_traveler);
|
||||
const auto *cpp_premium_traveler
|
||||
= get_cpp_conduit_type_ptr<pybind11_tests::test_cpp_conduit::PremiumTraveler>(
|
||||
premium_traveler);
|
||||
if (cpp_premium_traveler == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import tarfile
|
|||
import zipfile
|
||||
|
||||
# These tests must be run explicitly
|
||||
# They require CMake 3.15+ (--install)
|
||||
|
||||
DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
|
||||
|
@ -45,17 +46,8 @@ main_headers = {
|
|||
"include/pybind11/pytypes.h",
|
||||
"include/pybind11/stl.h",
|
||||
"include/pybind11/stl_bind.h",
|
||||
"include/pybind11/trampoline_self_life_support.h",
|
||||
"include/pybind11/type_caster_pyobject_ptr.h",
|
||||
"include/pybind11/typing.h",
|
||||
"include/pybind11/warnings.h",
|
||||
}
|
||||
|
||||
conduit_headers = {
|
||||
"include/pybind11/conduit/README.txt",
|
||||
"include/pybind11/conduit/pybind11_conduit_v1.h",
|
||||
"include/pybind11/conduit/pybind11_platform_abi_id.h",
|
||||
"include/pybind11/conduit/wrap_include_python_h.h",
|
||||
}
|
||||
|
||||
detail_headers = {
|
||||
|
@ -63,13 +55,10 @@ detail_headers = {
|
|||
"include/pybind11/detail/common.h",
|
||||
"include/pybind11/detail/cpp_conduit.h",
|
||||
"include/pybind11/detail/descr.h",
|
||||
"include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
|
||||
"include/pybind11/detail/init.h",
|
||||
"include/pybind11/detail/internals.h",
|
||||
"include/pybind11/detail/struct_smart_holder.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/detail/exception_translation.h",
|
||||
}
|
||||
|
@ -84,6 +73,16 @@ stl_headers = {
|
|||
"include/pybind11/stl/filesystem.h",
|
||||
}
|
||||
|
||||
eigen_headers = {
|
||||
"include/pybind11/eigen/common.h",
|
||||
"include/pybind11/eigen/matrix.h",
|
||||
"include/pybind11/eigen/tensor.h",
|
||||
}
|
||||
|
||||
stl_headers = {
|
||||
"include/pybind11/stl/filesystem.h",
|
||||
}
|
||||
|
||||
cmake_files = {
|
||||
"share/cmake/pybind11/FindPythonLibsNew.cmake",
|
||||
"share/cmake/pybind11/pybind11Common.cmake",
|
||||
|
@ -106,11 +105,9 @@ py_files = {
|
|||
"commands.py",
|
||||
"py.typed",
|
||||
"setup_helpers.py",
|
||||
"share/__init__.py",
|
||||
"share/pkgconfig/__init__.py",
|
||||
}
|
||||
|
||||
headers = main_headers | conduit_headers | detail_headers | eigen_headers | stl_headers
|
||||
headers = main_headers | detail_headers | eigen_headers | stl_headers
|
||||
src_files = headers | cmake_files | pkgconfig_files
|
||||
all_files = src_files | py_files
|
||||
|
||||
|
@ -119,7 +116,6 @@ sdist_files = {
|
|||
"pybind11",
|
||||
"pybind11/include",
|
||||
"pybind11/include/pybind11",
|
||||
"pybind11/include/pybind11/conduit",
|
||||
"pybind11/include/pybind11/detail",
|
||||
"pybind11/include/pybind11/eigen",
|
||||
"pybind11/include/pybind11/stl",
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
find_package(Catch 2.13.2)
|
||||
|
||||
if(CATCH_FOUND)
|
||||
message(STATUS "Building pure C++ tests (not depending on Python) using Catch v${CATCH_VERSION}")
|
||||
else()
|
||||
message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
|
||||
" manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(smart_holder_poc_test smart_holder_poc_test.cpp)
|
||||
pybind11_enable_warnings(smart_holder_poc_test)
|
||||
target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Catch2)
|
||||
|
||||
add_custom_target(
|
||||
test_pure_cpp
|
||||
COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
add_dependencies(check test_pure_cpp)
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (c) 2020-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/detail/struct_smart_holder.h"
|
||||
|
||||
namespace pybindit {
|
||||
namespace memory {
|
||||
namespace smart_holder_poc { // Proof-of-Concept implementations.
|
||||
|
||||
template <typename T>
|
||||
T &as_lvalue_ref(const smart_holder &hld) {
|
||||
static const char *context = "as_lvalue_ref";
|
||||
hld.ensure_is_populated(context);
|
||||
hld.ensure_has_pointee(context);
|
||||
return *hld.as_raw_ptr_unowned<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &&as_rvalue_ref(const smart_holder &hld) {
|
||||
static const char *context = "as_rvalue_ref";
|
||||
hld.ensure_is_populated(context);
|
||||
hld.ensure_has_pointee(context);
|
||||
return std::move(*hld.as_raw_ptr_unowned<T>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *as_raw_ptr_release_ownership(smart_holder &hld,
|
||||
const char *context = "as_raw_ptr_release_ownership") {
|
||||
hld.ensure_can_release_ownership(context);
|
||||
T *raw_ptr = hld.as_raw_ptr_unowned<T>();
|
||||
hld.release_ownership();
|
||||
return raw_ptr;
|
||||
}
|
||||
|
||||
template <typename T, typename D = std::default_delete<T>>
|
||||
std::unique_ptr<T, D> as_unique_ptr(smart_holder &hld) {
|
||||
static const char *context = "as_unique_ptr";
|
||||
hld.ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
hld.ensure_use_count_1(context);
|
||||
T *raw_ptr = hld.as_raw_ptr_unowned<T>();
|
||||
hld.release_ownership();
|
||||
// KNOWN DEFECT (see PR #4850): Does not copy the deleter.
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
|
||||
} // namespace smart_holder_poc
|
||||
} // namespace memory
|
||||
} // namespace pybindit
|
|
@ -1,415 +0,0 @@
|
|||
#include "smart_holder_poc.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// Catch uses _ internally, which breaks gettext style defines
|
||||
#ifdef _
|
||||
# undef _
|
||||
#endif
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
using pybindit::memory::smart_holder;
|
||||
namespace poc = pybindit::memory::smart_holder_poc;
|
||||
|
||||
namespace helpers {
|
||||
|
||||
struct movable_int {
|
||||
int valu;
|
||||
explicit movable_int(int v) : valu{v} {}
|
||||
movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct functor_builtin_delete {
|
||||
void operator()(T *ptr) { delete ptr; }
|
||||
#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8) \
|
||||
|| (defined(__clang_major__) && __clang_major__ == 3 && __clang_minor__ == 6)
|
||||
// Workaround for these errors:
|
||||
// gcc 4.8.5: too many initializers for 'helpers::functor_builtin_delete<int>'
|
||||
// clang 3.6: excess elements in struct initializer
|
||||
functor_builtin_delete() = default;
|
||||
functor_builtin_delete(const functor_builtin_delete &) {}
|
||||
functor_builtin_delete(functor_builtin_delete &&) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct functor_other_delete : functor_builtin_delete<T> {};
|
||||
|
||||
struct indestructible_int {
|
||||
int valu;
|
||||
explicit indestructible_int(int v) : valu{v} {}
|
||||
|
||||
private:
|
||||
~indestructible_int() = default;
|
||||
};
|
||||
|
||||
struct base {
|
||||
virtual int get() { return 10; }
|
||||
virtual ~base() = default;
|
||||
};
|
||||
|
||||
struct derived : public base {
|
||||
int get() override { return 100; }
|
||||
};
|
||||
|
||||
} // namespace helpers
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE(*hld.as_raw_ptr_unowned<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_lvalue_ref", "[S]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_rvalue_ref", "[S]") {
|
||||
helpers::movable_int orig(19);
|
||||
{
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&orig);
|
||||
helpers::movable_int othr(poc::as_rvalue_ref<helpers::movable_int>(hld));
|
||||
REQUIRE(othr.valu == 19);
|
||||
REQUIRE(orig.valu == 91);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_release_ownership", "[E]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"Cannot disown non-owning holder (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_unique_ptr", "[E]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
|
||||
"Cannot disown non-owning holder (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_unique_ptr_with_deleter", "[E]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"Missing unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_shared_ptr", "[S]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE(*hld.as_shared_ptr<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_lvalue_ref", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership1", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
auto new_owner = std::unique_ptr<int>(poc::as_raw_ptr_release_ownership<int>(hld));
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership2", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"Cannot disown use_count != 1 (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr1", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner = poc::as_unique_ptr<int>(hld);
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr2", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
|
||||
"Cannot disown use_count != 1 (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr_with_deleter", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"Missing unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(*new_owner == 19);
|
||||
hld.reclaim_disowned(); // Manually veriified: without this, clang++ -fsanitize=address reports
|
||||
// "detected memory leaks".
|
||||
// NOLINTNEXTLINE(bugprone-unused-return-value)
|
||||
(void) new_owner.release(); // Manually verified: without this, clang++ -fsanitize=address
|
||||
// reports "attempting double-free".
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(new_owner.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(*new_owner == 19);
|
||||
hld.release_disowned();
|
||||
REQUIRE(!hld.has_pointee());
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+ensure_is_not_disowned", "[E]") {
|
||||
const char *context = "test_case";
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
hld.ensure_is_not_disowned(context); // Does not throw.
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
REQUIRE_THROWS_WITH(hld.ensure_is_not_disowned(context),
|
||||
"Holder was disowned already (test_case).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_lvalue_ref", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership1", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
auto new_owner = std::unique_ptr<int>(poc::as_raw_ptr_release_ownership<int>(hld));
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership2", "[E]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"Cannot disown use_count != 1 (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_unique_ptr1", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::unique_ptr<int> new_owner = poc::as_unique_ptr<int>(hld);
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_unique_ptr2", "[E]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
|
||||
"Cannot disown use_count != 1 (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_unique_ptr_with_deleter", "[E]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_shared_ptr", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base", "[S]") {
|
||||
std::unique_ptr<helpers::derived> orig_owner(new helpers::derived());
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::unique_ptr<helpers::base> new_owner = poc::as_unique_ptr<helpers::base>(hld);
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(new_owner->get() == 100);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base2", "[E]") {
|
||||
std::unique_ptr<helpers::derived, helpers::functor_other_delete<helpers::derived>> orig_owner(
|
||||
new helpers::derived());
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH(
|
||||
(poc::as_unique_ptr<helpers::base, helpers::functor_builtin_delete<helpers::base>>(hld)),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_lvalue_ref", "[S]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") {
|
||||
std::unique_ptr<int, std::function<void(const int *)>> orig_owner(
|
||||
new int(19), [](const int *raw_ptr) { delete raw_ptr; });
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_raw_ptr_release_ownership", "[E]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"Cannot disown custom deleter (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr", "[E]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter1", "[S]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> new_owner
|
||||
= poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld);
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter2", "[E]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_other_delete<int>>(hld)),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr", "[S]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_lvalue_ref", "[S]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_raw_ptr_release_ownership", "[E]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"Cannot disown external shared_ptr (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_unique_ptr", "[E]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
|
||||
"Cannot disown external shared_ptr (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_unique_ptr_with_deleter", "[E]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"Missing unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_shared_ptr", "[S]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE(*hld.as_shared_ptr<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("error_unpopulated_holder", "[E]") {
|
||||
smart_holder hld;
|
||||
REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(hld), "Unpopulated holder (as_lvalue_ref).");
|
||||
}
|
||||
|
||||
TEST_CASE("error_disowned_holder", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
poc::as_unique_ptr<int>(hld);
|
||||
REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(hld), "Disowned holder (as_lvalue_ref).");
|
||||
}
|
||||
|
||||
TEST_CASE("error_cannot_disown_nullptr", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
poc::as_unique_ptr<int>(hld);
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld), "Cannot disown nullptr (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("indestructible_int-from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") {
|
||||
using zombie = helpers::indestructible_int;
|
||||
// Using placement new instead of plain new, to not trigger leak sanitizer errors.
|
||||
static std::aligned_storage<sizeof(zombie), alignof(zombie)>::type memory_block[1];
|
||||
auto *value = new (memory_block) zombie(19);
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(value);
|
||||
REQUIRE(hld.as_raw_ptr_unowned<zombie>()->valu == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("indestructible_int-from_raw_ptr_take_ownership", "[E]") {
|
||||
helpers::indestructible_int *value = nullptr;
|
||||
REQUIRE_THROWS_WITH(smart_holder::from_raw_ptr_take_ownership(value),
|
||||
"Pointee is not destructible (from_raw_ptr_take_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr-outliving_smart_holder", "[S]") {
|
||||
// Exercises guarded_builtin_delete flag_ptr validity past destruction of smart_holder.
|
||||
std::shared_ptr<int> longer_living;
|
||||
{
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
longer_living = hld.as_shared_ptr<int>();
|
||||
}
|
||||
REQUIRE(*longer_living == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr-outliving_smart_holder", "[S]") {
|
||||
// Exercises guarded_custom_deleter flag_ptr validity past destruction of smart_holder.
|
||||
std::shared_ptr<int> longer_living;
|
||||
{
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
longer_living = hld.as_shared_ptr<int>();
|
||||
}
|
||||
REQUIRE(*longer_living == 19);
|
||||
}
|
|
@ -128,9 +128,4 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
|
|||
for (const auto &initializer : initializers()) {
|
||||
initializer(m);
|
||||
}
|
||||
|
||||
py::class_<TestContext>(m, "TestContext")
|
||||
.def(py::init<>(&TestContext::createNewContextForInit))
|
||||
.def("__enter__", &TestContext::contextEnter)
|
||||
.def("__exit__", &TestContext::contextExit);
|
||||
}
|
||||
|
|
|
@ -96,24 +96,3 @@ void ignoreOldStyleInitWarnings(F &&body) {
|
|||
)",
|
||||
py::dict(py::arg("body") = py::cpp_function(body)));
|
||||
}
|
||||
|
||||
// See PR #5419 for background.
|
||||
class TestContext {
|
||||
public:
|
||||
TestContext() = delete;
|
||||
TestContext(const TestContext &) = delete;
|
||||
TestContext(TestContext &&) = delete;
|
||||
static TestContext *createNewContextForInit() { return new TestContext("new-context"); }
|
||||
|
||||
pybind11::object contextEnter() {
|
||||
py::object contextObj = py::cast(*this);
|
||||
return contextObj;
|
||||
}
|
||||
void contextExit(const pybind11::object & /*excType*/,
|
||||
const pybind11::object & /*excVal*/,
|
||||
const pybind11::object & /*excTb*/) {}
|
||||
|
||||
private:
|
||||
explicit TestContext(const std::string &context) : context(context) {}
|
||||
std::string context;
|
||||
};
|
||||
|
|
|
@ -10,6 +10,10 @@ name = "pybind11_tests"
|
|||
version = "0.0.1"
|
||||
dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"]
|
||||
|
||||
[tool.scikit-build]
|
||||
# Hide a warning while we also support CMake < 3.15
|
||||
cmake.version = ">=3.15"
|
||||
|
||||
[tool.scikit-build.cmake.define]
|
||||
PYBIND11_FINDPYTHON = true
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
--only-binary=:all:
|
||||
build~=1.0; python_version>="3.8"
|
||||
build~=1.0; python_version>="3.7"
|
||||
numpy~=1.20.0; python_version=="3.7" and platform_python_implementation=="PyPy"
|
||||
numpy~=1.23.0; python_version=="3.8" and platform_python_implementation=="PyPy"
|
||||
numpy~=1.25.0; python_version=="3.9" and platform_python_implementation=='PyPy'
|
||||
numpy~=1.26.0; platform_python_implementation=="GraalVM" and sys_platform=="linux"
|
||||
numpy~=1.21.5; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.8" and python_version<"3.10"
|
||||
numpy~=1.22.2; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version=="3.10"
|
||||
numpy~=1.26.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13"
|
||||
numpy~=1.21.5; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.10"
|
||||
numpy~=1.22.2; platform_python_implementation!="PyPy" and python_version=="3.10"
|
||||
numpy~=1.26.0; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13"
|
||||
pytest~=7.0
|
||||
pytest-timeout
|
||||
scipy~=1.5.4; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version<"3.10"
|
||||
scipy~=1.8.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version=="3.10" and sys_platform!='win32'
|
||||
scipy~=1.11.1; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13" and sys_platform!='win32'
|
||||
scipy~=1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10"
|
||||
scipy~=1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10" and sys_platform!='win32'
|
||||
scipy~=1.11.1; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13" and sys_platform!='win32'
|
||||
|
|
|
@ -167,125 +167,6 @@ TEST_SUBMODULE(buffers, m) {
|
|||
sizeof(float)});
|
||||
});
|
||||
|
||||
// A matrix that uses Fortran storage order.
|
||||
class FortranMatrix : public Matrix {
|
||||
public:
|
||||
FortranMatrix(py::ssize_t rows, py::ssize_t cols) : Matrix(cols, rows) {
|
||||
print_created(this,
|
||||
std::to_string(rows) + "x" + std::to_string(cols) + " Fortran matrix");
|
||||
}
|
||||
|
||||
float operator()(py::ssize_t i, py::ssize_t j) const { return Matrix::operator()(j, i); }
|
||||
|
||||
float &operator()(py::ssize_t i, py::ssize_t j) { return Matrix::operator()(j, i); }
|
||||
|
||||
using Matrix::data;
|
||||
|
||||
py::ssize_t rows() const { return Matrix::cols(); }
|
||||
py::ssize_t cols() const { return Matrix::rows(); }
|
||||
};
|
||||
py::class_<FortranMatrix, Matrix>(m, "FortranMatrix", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t, py::ssize_t>())
|
||||
|
||||
.def("rows", &FortranMatrix::rows)
|
||||
.def("cols", &FortranMatrix::cols)
|
||||
|
||||
/// Bare bones interface
|
||||
.def("__getitem__",
|
||||
[](const FortranMatrix &m, std::pair<py::ssize_t, py::ssize_t> i) {
|
||||
if (i.first >= m.rows() || i.second >= m.cols()) {
|
||||
throw py::index_error();
|
||||
}
|
||||
return m(i.first, i.second);
|
||||
})
|
||||
.def("__setitem__",
|
||||
[](FortranMatrix &m, std::pair<py::ssize_t, py::ssize_t> i, float v) {
|
||||
if (i.first >= m.rows() || i.second >= m.cols()) {
|
||||
throw py::index_error();
|
||||
}
|
||||
m(i.first, i.second) = v;
|
||||
})
|
||||
/// Provide buffer access
|
||||
.def_buffer([](FortranMatrix &m) -> py::buffer_info {
|
||||
return py::buffer_info(m.data(), /* Pointer to buffer */
|
||||
{m.rows(), m.cols()}, /* Buffer dimensions */
|
||||
/* Strides (in bytes) for each index */
|
||||
{sizeof(float), sizeof(float) * size_t(m.rows())});
|
||||
});
|
||||
|
||||
// A matrix that uses a discontiguous underlying memory block.
|
||||
class DiscontiguousMatrix : public Matrix {
|
||||
public:
|
||||
DiscontiguousMatrix(py::ssize_t rows,
|
||||
py::ssize_t cols,
|
||||
py::ssize_t row_factor,
|
||||
py::ssize_t col_factor)
|
||||
: Matrix(rows * row_factor, cols * col_factor), m_row_factor(row_factor),
|
||||
m_col_factor(col_factor) {
|
||||
print_created(this,
|
||||
std::to_string(rows) + "(*" + std::to_string(row_factor) + ")x"
|
||||
+ std::to_string(cols) + "(*" + std::to_string(col_factor)
|
||||
+ ") matrix");
|
||||
}
|
||||
|
||||
~DiscontiguousMatrix() {
|
||||
print_destroyed(this,
|
||||
std::to_string(rows() / m_row_factor) + "(*"
|
||||
+ std::to_string(m_row_factor) + ")x"
|
||||
+ std::to_string(cols() / m_col_factor) + "(*"
|
||||
+ std::to_string(m_col_factor) + ") matrix");
|
||||
}
|
||||
|
||||
float operator()(py::ssize_t i, py::ssize_t j) const {
|
||||
return Matrix::operator()(i * m_row_factor, j * m_col_factor);
|
||||
}
|
||||
|
||||
float &operator()(py::ssize_t i, py::ssize_t j) {
|
||||
return Matrix::operator()(i * m_row_factor, j * m_col_factor);
|
||||
}
|
||||
|
||||
using Matrix::data;
|
||||
|
||||
py::ssize_t rows() const { return Matrix::rows() / m_row_factor; }
|
||||
py::ssize_t cols() const { return Matrix::cols() / m_col_factor; }
|
||||
py::ssize_t row_factor() const { return m_row_factor; }
|
||||
py::ssize_t col_factor() const { return m_col_factor; }
|
||||
|
||||
private:
|
||||
py::ssize_t m_row_factor;
|
||||
py::ssize_t m_col_factor;
|
||||
};
|
||||
py::class_<DiscontiguousMatrix, Matrix>(m, "DiscontiguousMatrix", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t, py::ssize_t, py::ssize_t, py::ssize_t>())
|
||||
|
||||
.def("rows", &DiscontiguousMatrix::rows)
|
||||
.def("cols", &DiscontiguousMatrix::cols)
|
||||
|
||||
/// Bare bones interface
|
||||
.def("__getitem__",
|
||||
[](const DiscontiguousMatrix &m, std::pair<py::ssize_t, py::ssize_t> i) {
|
||||
if (i.first >= m.rows() || i.second >= m.cols()) {
|
||||
throw py::index_error();
|
||||
}
|
||||
return m(i.first, i.second);
|
||||
})
|
||||
.def("__setitem__",
|
||||
[](DiscontiguousMatrix &m, std::pair<py::ssize_t, py::ssize_t> i, float v) {
|
||||
if (i.first >= m.rows() || i.second >= m.cols()) {
|
||||
throw py::index_error();
|
||||
}
|
||||
m(i.first, i.second) = v;
|
||||
})
|
||||
/// Provide buffer access
|
||||
.def_buffer([](DiscontiguousMatrix &m) -> py::buffer_info {
|
||||
return py::buffer_info(m.data(), /* Pointer to buffer */
|
||||
{m.rows(), m.cols()}, /* Buffer dimensions */
|
||||
/* Strides (in bytes) for each index */
|
||||
{size_t(m.col_factor()) * sizeof(float) * size_t(m.cols())
|
||||
* size_t(m.row_factor()),
|
||||
size_t(m.col_factor()) * sizeof(float)});
|
||||
});
|
||||
|
||||
class BrokenMatrix : public Matrix {
|
||||
public:
|
||||
BrokenMatrix(py::ssize_t rows, py::ssize_t cols) : Matrix(rows, cols) {}
|
||||
|
@ -387,56 +268,4 @@ TEST_SUBMODULE(buffers, m) {
|
|||
});
|
||||
|
||||
m.def("get_buffer_info", [](const py::buffer &buffer) { return buffer.request(); });
|
||||
|
||||
// Expose Py_buffer for testing.
|
||||
m.attr("PyBUF_FORMAT") = PyBUF_FORMAT;
|
||||
m.attr("PyBUF_SIMPLE") = PyBUF_SIMPLE;
|
||||
m.attr("PyBUF_ND") = PyBUF_ND;
|
||||
m.attr("PyBUF_STRIDES") = PyBUF_STRIDES;
|
||||
m.attr("PyBUF_INDIRECT") = PyBUF_INDIRECT;
|
||||
m.attr("PyBUF_C_CONTIGUOUS") = PyBUF_C_CONTIGUOUS;
|
||||
m.attr("PyBUF_F_CONTIGUOUS") = PyBUF_F_CONTIGUOUS;
|
||||
m.attr("PyBUF_ANY_CONTIGUOUS") = PyBUF_ANY_CONTIGUOUS;
|
||||
|
||||
m.def("get_py_buffer", [](const py::object &object, int flags) {
|
||||
Py_buffer buffer;
|
||||
memset(&buffer, 0, sizeof(Py_buffer));
|
||||
if (PyObject_GetBuffer(object.ptr(), &buffer, flags) == -1) {
|
||||
throw py::error_already_set();
|
||||
}
|
||||
|
||||
auto SimpleNamespace = py::module_::import("types").attr("SimpleNamespace");
|
||||
py::object result = SimpleNamespace("len"_a = buffer.len,
|
||||
"readonly"_a = buffer.readonly,
|
||||
"itemsize"_a = buffer.itemsize,
|
||||
"format"_a = buffer.format,
|
||||
"ndim"_a = buffer.ndim,
|
||||
"shape"_a = py::none(),
|
||||
"strides"_a = py::none(),
|
||||
"suboffsets"_a = py::none());
|
||||
if (buffer.shape != nullptr) {
|
||||
py::list l;
|
||||
for (auto i = 0; i < buffer.ndim; i++) {
|
||||
l.append(buffer.shape[i]);
|
||||
}
|
||||
py::setattr(result, "shape", l);
|
||||
}
|
||||
if (buffer.strides != nullptr) {
|
||||
py::list l;
|
||||
for (auto i = 0; i < buffer.ndim; i++) {
|
||||
l.append(buffer.strides[i]);
|
||||
}
|
||||
py::setattr(result, "strides", l);
|
||||
}
|
||||
if (buffer.suboffsets != nullptr) {
|
||||
py::list l;
|
||||
for (auto i = 0; i < buffer.ndim; i++) {
|
||||
l.append(buffer.suboffsets[i]);
|
||||
}
|
||||
py::setattr(result, "suboffsets", l);
|
||||
}
|
||||
|
||||
PyBuffer_Release(&buffer);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -82,8 +82,6 @@ def test_from_python():
|
|||
for j in range(m4.cols()):
|
||||
assert m3[i, j] == m4[i, j]
|
||||
|
||||
if env.GRAALPY:
|
||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
||||
cstats = ConstructorStats.get(m.Matrix)
|
||||
assert cstats.alive() == 1
|
||||
del m3, m4
|
||||
|
@ -120,8 +118,6 @@ def test_to_python():
|
|||
mat2[2, 3] = 5
|
||||
assert mat2[2, 3] == 5
|
||||
|
||||
if env.GRAALPY:
|
||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
||||
cstats = ConstructorStats.get(m.Matrix)
|
||||
assert cstats.alive() == 1
|
||||
del mat
|
||||
|
@ -239,163 +235,3 @@ def test_buffer_exception():
|
|||
memoryview(m.BrokenMatrix(1, 1))
|
||||
assert isinstance(excinfo.value.__cause__, RuntimeError)
|
||||
assert "for context" in str(excinfo.value.__cause__)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
|
||||
def test_c_contiguous_to_pybuffer(type):
|
||||
if type == "pybind11":
|
||||
mat = m.Matrix(5, 4)
|
||||
elif type == "numpy":
|
||||
mat = np.empty((5, 4), dtype=np.float32)
|
||||
else:
|
||||
raise ValueError(f"Unknown parametrization {type}")
|
||||
|
||||
info = m.get_py_buffer(mat, m.PyBUF_SIMPLE)
|
||||
assert info.format is None
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 0 # See discussion on PR #5407.
|
||||
assert info.shape is None
|
||||
assert info.strides is None
|
||||
assert info.suboffsets is None
|
||||
assert not info.readonly
|
||||
info = m.get_py_buffer(mat, m.PyBUF_SIMPLE | m.PyBUF_FORMAT)
|
||||
assert info.format == "f"
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 0 # See discussion on PR #5407.
|
||||
assert info.shape is None
|
||||
assert info.strides is None
|
||||
assert info.suboffsets is None
|
||||
assert not info.readonly
|
||||
info = m.get_py_buffer(mat, m.PyBUF_ND)
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 2
|
||||
assert info.shape == [5, 4]
|
||||
assert info.strides is None
|
||||
assert info.suboffsets is None
|
||||
assert not info.readonly
|
||||
info = m.get_py_buffer(mat, m.PyBUF_STRIDES)
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 2
|
||||
assert info.shape == [5, 4]
|
||||
assert info.strides == [4 * info.itemsize, info.itemsize]
|
||||
assert info.suboffsets is None
|
||||
assert not info.readonly
|
||||
info = m.get_py_buffer(mat, m.PyBUF_INDIRECT)
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 2
|
||||
assert info.shape == [5, 4]
|
||||
assert info.strides == [4 * info.itemsize, info.itemsize]
|
||||
assert info.suboffsets is None # Should be filled in here, but we don't use it.
|
||||
assert not info.readonly
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
|
||||
def test_fortran_contiguous_to_pybuffer(type):
|
||||
if type == "pybind11":
|
||||
mat = m.FortranMatrix(5, 4)
|
||||
elif type == "numpy":
|
||||
mat = np.empty((5, 4), dtype=np.float32, order="F")
|
||||
else:
|
||||
raise ValueError(f"Unknown parametrization {type}")
|
||||
|
||||
# A Fortran-shaped buffer can only be accessed at PyBUF_STRIDES level or higher.
|
||||
info = m.get_py_buffer(mat, m.PyBUF_STRIDES)
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 2
|
||||
assert info.shape == [5, 4]
|
||||
assert info.strides == [info.itemsize, 5 * info.itemsize]
|
||||
assert info.suboffsets is None
|
||||
assert not info.readonly
|
||||
info = m.get_py_buffer(mat, m.PyBUF_INDIRECT)
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 2
|
||||
assert info.shape == [5, 4]
|
||||
assert info.strides == [info.itemsize, 5 * info.itemsize]
|
||||
assert info.suboffsets is None # Should be filled in here, but we don't use it.
|
||||
assert not info.readonly
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
|
||||
def test_discontiguous_to_pybuffer(type):
|
||||
if type == "pybind11":
|
||||
mat = m.DiscontiguousMatrix(5, 4, 2, 3)
|
||||
elif type == "numpy":
|
||||
mat = np.empty((5 * 2, 4 * 3), dtype=np.float32)[::2, ::3]
|
||||
else:
|
||||
raise ValueError(f"Unknown parametrization {type}")
|
||||
|
||||
info = m.get_py_buffer(mat, m.PyBUF_STRIDES)
|
||||
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
|
||||
assert info.len == 5 * 4 * info.itemsize
|
||||
assert info.ndim == 2
|
||||
assert info.shape == [5, 4]
|
||||
assert info.strides == [2 * 4 * 3 * info.itemsize, 3 * info.itemsize]
|
||||
assert info.suboffsets is None
|
||||
assert not info.readonly
|
||||
|
||||
|
||||
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
|
||||
def test_to_pybuffer_contiguity(type):
|
||||
def check_strides(mat):
|
||||
# The full block is memset to 0, so fill it with non-zero in real spots.
|
||||
expected = np.arange(1, 5 * 4 + 1).reshape((5, 4))
|
||||
for i in range(5):
|
||||
for j in range(4):
|
||||
mat[i, j] = expected[i, j]
|
||||
# If all strides are correct, the exposed buffer should match the input.
|
||||
np.testing.assert_array_equal(np.array(mat), expected)
|
||||
|
||||
if type == "pybind11":
|
||||
cmat = m.Matrix(5, 4) # C contiguous.
|
||||
fmat = m.FortranMatrix(5, 4) # Fortran contiguous.
|
||||
dmat = m.DiscontiguousMatrix(5, 4, 2, 3) # Not contiguous.
|
||||
expected_exception = BufferError
|
||||
elif type == "numpy":
|
||||
cmat = np.empty((5, 4), dtype=np.float32) # C contiguous.
|
||||
fmat = np.empty((5, 4), dtype=np.float32, order="F") # Fortran contiguous.
|
||||
dmat = np.empty((5 * 2, 4 * 3), dtype=np.float32)[::2, ::3] # Not contiguous.
|
||||
# NumPy incorrectly raises ValueError; when the minimum NumPy requirement is
|
||||
# above the version that fixes https://github.com/numpy/numpy/issues/3634 then
|
||||
# BufferError can be used everywhere.
|
||||
expected_exception = (BufferError, ValueError)
|
||||
else:
|
||||
raise ValueError(f"Unknown parametrization {type}")
|
||||
|
||||
check_strides(cmat)
|
||||
# Should work in C-contiguous mode, but not Fortran order.
|
||||
m.get_py_buffer(cmat, m.PyBUF_C_CONTIGUOUS)
|
||||
m.get_py_buffer(cmat, m.PyBUF_ANY_CONTIGUOUS)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(cmat, m.PyBUF_F_CONTIGUOUS)
|
||||
|
||||
check_strides(fmat)
|
||||
# These flags imply C-contiguity, so won't work.
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(fmat, m.PyBUF_SIMPLE)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(fmat, m.PyBUF_ND)
|
||||
# Should work in Fortran-contiguous mode, but not C order.
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(fmat, m.PyBUF_C_CONTIGUOUS)
|
||||
m.get_py_buffer(fmat, m.PyBUF_ANY_CONTIGUOUS)
|
||||
m.get_py_buffer(fmat, m.PyBUF_F_CONTIGUOUS)
|
||||
|
||||
check_strides(dmat)
|
||||
# Should never work.
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_SIMPLE)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_ND)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_C_CONTIGUOUS)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS)
|
||||
|
|
|
@ -297,7 +297,7 @@ def test_int_convert():
|
|||
cant_convert(3.14159)
|
||||
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
|
||||
# TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
|
||||
if sys.version_info < (3, 10) and env.CPYTHON:
|
||||
if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON:
|
||||
with env.deprecated_call():
|
||||
assert convert(Int()) == 42
|
||||
else:
|
||||
|
|
|
@ -95,8 +95,8 @@ TEST_SUBMODULE(call_policies, m) {
|
|||
},
|
||||
py::call_guard<DependentGuard, CustomGuard>());
|
||||
|
||||
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
|
||||
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy/GraalPy as well,
|
||||
#if !defined(PYPY_VERSION)
|
||||
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well,
|
||||
// but it's unclear how to test it without `PyGILState_GetThisThreadState`.
|
||||
auto report_gil_status = []() {
|
||||
auto is_gil_held = false;
|
||||
|
|
|
@ -8,7 +8,6 @@ from pybind11_tests import call_policies as m
|
|||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", reason="sometimes comes out 1 off on PyPy", strict=False)
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_keep_alive_argument(capture):
|
||||
n_inst = ConstructorStats.detail_reg_inst()
|
||||
with capture:
|
||||
|
@ -61,7 +60,6 @@ def test_keep_alive_argument(capture):
|
|||
assert str(excinfo.value) == "Could not activate keep_alive!"
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_keep_alive_return_value(capture):
|
||||
n_inst = ConstructorStats.detail_reg_inst()
|
||||
with capture:
|
||||
|
@ -120,7 +118,6 @@ def test_keep_alive_return_value(capture):
|
|||
|
||||
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
|
||||
@pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented")
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_alive_gc(capture):
|
||||
n_inst = ConstructorStats.detail_reg_inst()
|
||||
p = m.ParentGC()
|
||||
|
@ -140,7 +137,6 @@ def test_alive_gc(capture):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_alive_gc_derived(capture):
|
||||
class Derived(m.Parent):
|
||||
pass
|
||||
|
@ -163,7 +159,6 @@ def test_alive_gc_derived(capture):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_alive_gc_multi_derived(capture):
|
||||
class Derived(m.Parent, m.Child):
|
||||
def __init__(self):
|
||||
|
@ -190,7 +185,6 @@ def test_alive_gc_multi_derived(capture):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_return_none(capture):
|
||||
n_inst = ConstructorStats.detail_reg_inst()
|
||||
with capture:
|
||||
|
@ -218,7 +212,6 @@ def test_return_none(capture):
|
|||
assert capture == "Releasing parent."
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_keep_alive_constructor(capture):
|
||||
n_inst = ConstructorStats.detail_reg_inst()
|
||||
|
||||
|
|
|
@ -269,7 +269,12 @@ TEST_SUBMODULE(callbacks, m) {
|
|||
rec_capsule.set_name(rec_capsule_name);
|
||||
m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr()));
|
||||
|
||||
// This test requires a new ABI version to pass
|
||||
#if PYBIND11_INTERNALS_VERSION > 4
|
||||
// rec_capsule with nullptr name
|
||||
py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
|
||||
m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr()));
|
||||
#else
|
||||
m.add_object("custom_function2", py::none());
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -90,7 +90,6 @@ def test_keyword_args_and_generalized_unpacking():
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_lambda_closure_cleanup():
|
||||
m.test_lambda_closure_cleanup()
|
||||
cstats = m.payload_cstats()
|
||||
|
@ -99,7 +98,6 @@ def test_lambda_closure_cleanup():
|
|||
assert cstats.move_constructions >= 1
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
||||
def test_cpp_callable_cleanup():
|
||||
alive_counts = m.test_cpp_callable_cleanup()
|
||||
assert alive_counts == [0, 1, 2, 1, 2, 1, 0]
|
||||
|
@ -217,7 +215,9 @@ def test_custom_func():
|
|||
assert m.roundtrip(m.custom_function)(4) == 36
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.GRAALPY", reason="TODO debug segfault")
|
||||
@pytest.mark.skipif(
|
||||
m.custom_function2 is None, reason="Current PYBIND11_INTERNALS_VERSION too low"
|
||||
)
|
||||
def test_custom_func2():
|
||||
assert m.custom_function2(3) == 27
|
||||
assert m.roundtrip(m.custom_function2)(3) == 27
|
||||
|
|
|
@ -52,24 +52,8 @@ void bind_empty0(py::module_ &m) {
|
|||
}
|
||||
|
||||
} // namespace pr4220_tripped_over_this
|
||||
|
||||
namespace pr5396_forward_declared_class {
|
||||
class ForwardClass;
|
||||
class Args : public py::args {};
|
||||
} // namespace pr5396_forward_declared_class
|
||||
|
||||
} // namespace test_class
|
||||
|
||||
static_assert(py::detail::is_same_or_base_of<py::args, py::args>::value, "");
|
||||
static_assert(
|
||||
py::detail::is_same_or_base_of<py::args,
|
||||
test_class::pr5396_forward_declared_class::Args>::value,
|
||||
"");
|
||||
static_assert(!py::detail::is_same_or_base_of<
|
||||
py::args,
|
||||
test_class::pr5396_forward_declared_class::ForwardClass>::value,
|
||||
"");
|
||||
|
||||
TEST_SUBMODULE(class_, m) {
|
||||
m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); });
|
||||
|
||||
|
@ -105,12 +89,6 @@ TEST_SUBMODULE(class_, m) {
|
|||
.def_static("__new__",
|
||||
[](const py::object &) { return NoConstructorNew::new_instance(); });
|
||||
|
||||
// test_pass_unique_ptr
|
||||
struct ToBeHeldByUniquePtr {};
|
||||
py::class_<ToBeHeldByUniquePtr, std::unique_ptr<ToBeHeldByUniquePtr>>(m, "ToBeHeldByUniquePtr")
|
||||
.def(py::init<>());
|
||||
m.def("pass_unique_ptr", [](std::unique_ptr<ToBeHeldByUniquePtr> &&) {});
|
||||
|
||||
// test_inheritance
|
||||
class Pet {
|
||||
public:
|
||||
|
@ -233,12 +211,11 @@ TEST_SUBMODULE(class_, m) {
|
|||
m.def("mismatched_holder_1", []() {
|
||||
auto mod = py::module_::import("__main__");
|
||||
py::class_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1");
|
||||
py::class_<MismatchDerived1, std::unique_ptr<MismatchDerived1>, MismatchBase1>(
|
||||
mod, "MismatchDerived1");
|
||||
py::class_<MismatchDerived1, MismatchBase1>(mod, "MismatchDerived1");
|
||||
});
|
||||
m.def("mismatched_holder_2", []() {
|
||||
auto mod = py::module_::import("__main__");
|
||||
py::class_<MismatchBase2, std::unique_ptr<MismatchBase2>>(mod, "MismatchBase2");
|
||||
py::class_<MismatchBase2>(mod, "MismatchBase2");
|
||||
py::class_<MismatchDerived2, std::shared_ptr<MismatchDerived2>, MismatchBase2>(
|
||||
mod, "MismatchDerived2");
|
||||
});
|
||||
|
@ -632,10 +609,8 @@ CHECK_NOALIAS(8);
|
|||
CHECK_HOLDER(1, unique);
|
||||
CHECK_HOLDER(2, unique);
|
||||
CHECK_HOLDER(3, unique);
|
||||
#ifndef PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
|
||||
CHECK_HOLDER(4, unique);
|
||||
CHECK_HOLDER(5, unique);
|
||||
#endif
|
||||
CHECK_HOLDER(6, shared);
|
||||
CHECK_HOLDER(7, shared);
|
||||
CHECK_HOLDER(8, shared);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
@ -28,9 +27,6 @@ def test_instance(msg):
|
|||
|
||||
instance = m.NoConstructor.new_instance()
|
||||
|
||||
if env.GRAALPY:
|
||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
||||
|
||||
cstats = ConstructorStats.get(m.NoConstructor)
|
||||
assert cstats.alive() == 1
|
||||
del instance
|
||||
|
@ -39,26 +35,12 @@ def test_instance(msg):
|
|||
|
||||
def test_instance_new():
|
||||
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)
|
||||
|
||||
if env.GRAALPY:
|
||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
||||
|
||||
cstats = ConstructorStats.get(m.NoConstructorNew)
|
||||
assert cstats.alive() == 1
|
||||
del instance
|
||||
assert cstats.alive() == 0
|
||||
|
||||
|
||||
def test_pass_unique_ptr():
|
||||
obj = m.ToBeHeldByUniquePtr()
|
||||
with pytest.raises(RuntimeError) as execinfo:
|
||||
m.pass_unique_ptr(obj)
|
||||
assert str(execinfo.value).startswith(
|
||||
"Passing `std::unique_ptr<T>` from Python to C++ requires `py::class_<T, py::smart_holder>` (with T = "
|
||||
)
|
||||
assert "ToBeHeldByUniquePtr" in str(execinfo.value)
|
||||
|
||||
|
||||
def test_type():
|
||||
assert m.check_type(1) == m.DerivedClass1
|
||||
with pytest.raises(RuntimeError) as execinfo:
|
||||
|
@ -379,7 +361,7 @@ def test_brace_initialization():
|
|||
assert b.vec == [123, 456]
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY or env.GRAALPY")
|
||||
@pytest.mark.xfail("env.PYPY")
|
||||
def test_class_refcount():
|
||||
"""Instances must correctly increase/decrease the reference count of their types (#1029)"""
|
||||
from sys import getrefcount
|
||||
|
@ -519,31 +501,3 @@ def test_pr4220_tripped_over_this():
|
|||
m.Empty0().get_msg()
|
||||
== "This is really only meant to exercise successful compilation."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
|
||||
def test_all_type_info_multithreaded():
|
||||
# See PR #5419 for background.
|
||||
import threading
|
||||
|
||||
from pybind11_tests import TestContext
|
||||
|
||||
class Context(TestContext):
|
||||
pass
|
||||
|
||||
num_runs = 10
|
||||
num_threads = 4
|
||||
barrier = threading.Barrier(num_threads)
|
||||
|
||||
def func():
|
||||
barrier.wait()
|
||||
with Context():
|
||||
pass
|
||||
|
||||
for _ in range(num_runs):
|
||||
threads = [threading.Thread(target=func) for _ in range(num_threads)]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_release_gil_before_calling_cpp_dtor {
|
||||
|
||||
using RegistryType = std::unordered_map<std::string, int>;
|
||||
|
||||
static RegistryType &PyGILState_Check_Results() {
|
||||
static RegistryType singleton; // Local static variables have thread-safe initialization.
|
||||
return singleton;
|
||||
}
|
||||
|
||||
template <int> // Using int as a trick to easily generate a series of types.
|
||||
struct ProbeType {
|
||||
private:
|
||||
std::string unique_key;
|
||||
|
||||
public:
|
||||
explicit ProbeType(const std::string &unique_key) : unique_key{unique_key} {}
|
||||
|
||||
~ProbeType() {
|
||||
RegistryType ® = PyGILState_Check_Results();
|
||||
assert(reg.count(unique_key) == 0);
|
||||
reg[unique_key] = PyGILState_Check();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace class_release_gil_before_calling_cpp_dtor
|
||||
} // namespace pybind11_tests
|
||||
|
||||
TEST_SUBMODULE(class_release_gil_before_calling_cpp_dtor, m) {
|
||||
using namespace pybind11_tests::class_release_gil_before_calling_cpp_dtor;
|
||||
|
||||
py::class_<ProbeType<0>>(m, "ProbeType0").def(py::init<std::string>());
|
||||
|
||||
py::class_<ProbeType<1>>(m, "ProbeType1", py::release_gil_before_calling_cpp_dtor())
|
||||
.def(py::init<std::string>());
|
||||
|
||||
m.def("PopPyGILState_Check_Result", [](const std::string &unique_key) -> std::string {
|
||||
RegistryType ® = PyGILState_Check_Results();
|
||||
if (reg.count(unique_key) == 0) {
|
||||
return "MISSING";
|
||||
}
|
||||
int res = reg[unique_key];
|
||||
reg.erase(unique_key);
|
||||
return std::to_string(res);
|
||||
});
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import gc
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_release_gil_before_calling_cpp_dtor as m
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("probe_type", "unique_key", "expected_result"),
|
||||
[
|
||||
(m.ProbeType0, "without_manipulating_gil", "1"),
|
||||
(m.ProbeType1, "release_gil_before_calling_cpp_dtor", "0"),
|
||||
],
|
||||
)
|
||||
def test_gil_state_check_results(probe_type, unique_key, expected_result):
|
||||
probe_type(unique_key)
|
||||
gc.collect()
|
||||
result = m.PopPyGILState_Check_Result(unique_key)
|
||||
assert result == expected_result
|
|
@ -1,247 +0,0 @@
|
|||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_basic {
|
||||
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
atyp() : mtxt("DefaultConstructor") {}
|
||||
explicit atyp(const std::string &mtxt_) : mtxt(mtxt_) {}
|
||||
atyp(const atyp &other) { mtxt = other.mtxt + "_CpCtor"; }
|
||||
atyp(atyp &&other) noexcept { mtxt = other.mtxt + "_MvCtor"; }
|
||||
};
|
||||
|
||||
struct uconsumer { // unique_ptr consumer
|
||||
std::unique_ptr<atyp> held;
|
||||
bool valid() const { return static_cast<bool>(held); }
|
||||
|
||||
void pass_valu(std::unique_ptr<atyp> obj) { held = std::move(obj); }
|
||||
void pass_rref(std::unique_ptr<atyp> &&obj) { held = std::move(obj); }
|
||||
std::unique_ptr<atyp> rtrn_valu() { return std::move(held); }
|
||||
std::unique_ptr<atyp> &rtrn_lref() { return held; }
|
||||
const std::unique_ptr<atyp> &rtrn_cref() const { return held; }
|
||||
};
|
||||
|
||||
/// Custom deleter that is default constructible.
|
||||
struct custom_deleter {
|
||||
std::string trace_txt;
|
||||
|
||||
custom_deleter() = default;
|
||||
explicit custom_deleter(const std::string &trace_txt_) : trace_txt(trace_txt_) {}
|
||||
|
||||
custom_deleter(const custom_deleter &other) { trace_txt = other.trace_txt + "_CpCtor"; }
|
||||
|
||||
custom_deleter &operator=(const custom_deleter &rhs) {
|
||||
trace_txt = rhs.trace_txt + "_CpLhs";
|
||||
return *this;
|
||||
}
|
||||
|
||||
custom_deleter(custom_deleter &&other) noexcept {
|
||||
trace_txt = other.trace_txt + "_MvCtorTo";
|
||||
other.trace_txt += "_MvCtorFrom";
|
||||
}
|
||||
|
||||
custom_deleter &operator=(custom_deleter &&rhs) noexcept {
|
||||
trace_txt = rhs.trace_txt + "_MvLhs";
|
||||
rhs.trace_txt += "_MvRhs";
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(atyp *p) const { std::default_delete<atyp>()(p); }
|
||||
void operator()(const atyp *p) const { std::default_delete<const atyp>()(p); }
|
||||
};
|
||||
static_assert(std::is_default_constructible<custom_deleter>::value, "");
|
||||
|
||||
/// Custom deleter that is not default constructible.
|
||||
struct custom_deleter_nd : custom_deleter {
|
||||
custom_deleter_nd() = delete;
|
||||
explicit custom_deleter_nd(const std::string &trace_txt_) : custom_deleter(trace_txt_) {}
|
||||
};
|
||||
static_assert(!std::is_default_constructible<custom_deleter_nd>::value, "");
|
||||
|
||||
// clang-format off
|
||||
|
||||
atyp rtrn_valu() { atyp obj{"rtrn_valu"}; return obj; }
|
||||
atyp&& rtrn_rref() { static atyp obj; obj.mtxt = "rtrn_rref"; return std::move(obj); }
|
||||
atyp const& rtrn_cref() { static atyp obj; obj.mtxt = "rtrn_cref"; return obj; }
|
||||
atyp& rtrn_mref() { static atyp obj; obj.mtxt = "rtrn_mref"; return obj; }
|
||||
atyp const* rtrn_cptr() { return new atyp{"rtrn_cptr"}; }
|
||||
atyp* rtrn_mptr() { return new atyp{"rtrn_mptr"}; }
|
||||
|
||||
std::string pass_valu(atyp obj) { return "pass_valu:" + obj.mtxt; } // NOLINT
|
||||
std::string pass_cref(atyp const& obj) { return "pass_cref:" + obj.mtxt; }
|
||||
std::string pass_mref(atyp& obj) { return "pass_mref:" + obj.mtxt; }
|
||||
std::string pass_cptr(atyp const* obj) { return "pass_cptr:" + obj->mtxt; }
|
||||
std::string pass_mptr(atyp* obj) { return "pass_mptr:" + obj->mtxt; }
|
||||
|
||||
std::shared_ptr<atyp> rtrn_shmp() { return std::make_shared<atyp>("rtrn_shmp"); }
|
||||
std::shared_ptr<atyp const> rtrn_shcp() { return std::shared_ptr<atyp const>(new atyp{"rtrn_shcp"}); }
|
||||
|
||||
std::string pass_shmp(std::shared_ptr<atyp> obj) { return "pass_shmp:" + obj->mtxt; } // NOLINT
|
||||
std::string pass_shcp(std::shared_ptr<atyp const> obj) { return "pass_shcp:" + obj->mtxt; } // NOLINT
|
||||
|
||||
std::unique_ptr<atyp> rtrn_uqmp() { return std::unique_ptr<atyp >(new atyp{"rtrn_uqmp"}); }
|
||||
std::unique_ptr<atyp const> rtrn_uqcp() { return std::unique_ptr<atyp const>(new atyp{"rtrn_uqcp"}); }
|
||||
|
||||
std::string pass_uqmp(std::unique_ptr<atyp > obj) { return "pass_uqmp:" + obj->mtxt; }
|
||||
std::string pass_uqcp(std::unique_ptr<atyp const> obj) { return "pass_uqcp:" + obj->mtxt; }
|
||||
|
||||
struct sddm : std::default_delete<atyp > {};
|
||||
struct sddc : std::default_delete<atyp const> {};
|
||||
|
||||
std::unique_ptr<atyp, sddm> rtrn_udmp() { return std::unique_ptr<atyp, sddm>(new atyp{"rtrn_udmp"}); }
|
||||
std::unique_ptr<atyp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp const, sddc>(new atyp{"rtrn_udcp"}); }
|
||||
|
||||
std::string pass_udmp(std::unique_ptr<atyp, sddm> obj) { return "pass_udmp:" + obj->mtxt; }
|
||||
std::string pass_udcp(std::unique_ptr<atyp const, sddc> obj) { return "pass_udcp:" + obj->mtxt; }
|
||||
|
||||
std::unique_ptr<atyp, custom_deleter> rtrn_udmp_del() { return std::unique_ptr<atyp, custom_deleter>(new atyp{"rtrn_udmp_del"}, custom_deleter{"udmp_deleter"}); }
|
||||
std::unique_ptr<atyp const, custom_deleter> rtrn_udcp_del() { return std::unique_ptr<atyp const, custom_deleter>(new atyp{"rtrn_udcp_del"}, custom_deleter{"udcp_deleter"}); }
|
||||
|
||||
std::string pass_udmp_del(std::unique_ptr<atyp, custom_deleter> obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
std::string pass_udcp_del(std::unique_ptr<atyp const, custom_deleter> obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
|
||||
std::unique_ptr<atyp, custom_deleter_nd> rtrn_udmp_del_nd() { return std::unique_ptr<atyp, custom_deleter_nd>(new atyp{"rtrn_udmp_del_nd"}, custom_deleter_nd{"udmp_deleter_nd"}); }
|
||||
std::unique_ptr<atyp const, custom_deleter_nd> rtrn_udcp_del_nd() { return std::unique_ptr<atyp const, custom_deleter_nd>(new atyp{"rtrn_udcp_del_nd"}, custom_deleter_nd{"udcp_deleter_nd"}); }
|
||||
|
||||
std::string pass_udmp_del_nd(std::unique_ptr<atyp, custom_deleter_nd> obj) { return "pass_udmp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
std::string pass_udcp_del_nd(std::unique_ptr<atyp const, custom_deleter_nd> obj) { return "pass_udcp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Helpers for testing.
|
||||
std::string get_mtxt(atyp const &obj) { return obj.mtxt; }
|
||||
std::ptrdiff_t get_ptr(atyp const &obj) { return reinterpret_cast<std::ptrdiff_t>(&obj); }
|
||||
|
||||
std::unique_ptr<atyp> unique_ptr_roundtrip(std::unique_ptr<atyp> obj) { return obj; }
|
||||
|
||||
std::string pass_unique_ptr_cref(const std::unique_ptr<atyp> &obj) { return obj->mtxt; }
|
||||
|
||||
const std::unique_ptr<atyp> &rtrn_unique_ptr_cref(const std::string &mtxt) {
|
||||
static std::unique_ptr<atyp> obj{new atyp{"static_ctor_arg"}};
|
||||
if (!mtxt.empty()) {
|
||||
obj->mtxt = mtxt;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const std::unique_ptr<atyp> &unique_ptr_cref_roundtrip(const std::unique_ptr<atyp> &obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
struct SharedPtrStash {
|
||||
std::vector<std::shared_ptr<const atyp>> stash;
|
||||
void Add(const std::shared_ptr<const atyp> &obj) { stash.push_back(obj); }
|
||||
};
|
||||
|
||||
class LocalUnusualOpRef : UnusualOpRef {}; // To avoid clashing with `py::class_<UnusualOpRef>`.
|
||||
py::object CastUnusualOpRefConstRef(const LocalUnusualOpRef &cref) { return py::cast(cref); }
|
||||
py::object CastUnusualOpRefMovable(LocalUnusualOpRef &&mvbl) { return py::cast(std::move(mvbl)); }
|
||||
|
||||
TEST_SUBMODULE(class_sh_basic, m) {
|
||||
namespace py = pybind11;
|
||||
|
||||
py::classh<atyp>(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) {
|
||||
atyp obj;
|
||||
obj.mtxt = mtxt;
|
||||
return obj;
|
||||
}));
|
||||
|
||||
m.def("rtrn_valu", rtrn_valu);
|
||||
m.def("rtrn_rref", rtrn_rref);
|
||||
m.def("rtrn_cref", rtrn_cref);
|
||||
m.def("rtrn_mref", rtrn_mref);
|
||||
m.def("rtrn_cptr", rtrn_cptr);
|
||||
m.def("rtrn_mptr", rtrn_mptr);
|
||||
|
||||
m.def("pass_valu", pass_valu);
|
||||
m.def("pass_cref", pass_cref);
|
||||
m.def("pass_mref", pass_mref);
|
||||
m.def("pass_cptr", pass_cptr);
|
||||
m.def("pass_mptr", pass_mptr);
|
||||
|
||||
m.def("rtrn_shmp", rtrn_shmp);
|
||||
m.def("rtrn_shcp", rtrn_shcp);
|
||||
|
||||
m.def("pass_shmp", pass_shmp);
|
||||
m.def("pass_shcp", pass_shcp);
|
||||
|
||||
m.def("rtrn_uqmp", rtrn_uqmp);
|
||||
m.def("rtrn_uqcp", rtrn_uqcp);
|
||||
|
||||
m.def("pass_uqmp", pass_uqmp);
|
||||
m.def("pass_uqcp", pass_uqcp);
|
||||
|
||||
m.def("rtrn_udmp", rtrn_udmp);
|
||||
m.def("rtrn_udcp", rtrn_udcp);
|
||||
|
||||
m.def("pass_udmp", pass_udmp);
|
||||
m.def("pass_udcp", pass_udcp);
|
||||
|
||||
m.def("rtrn_udmp_del", rtrn_udmp_del);
|
||||
m.def("rtrn_udcp_del", rtrn_udcp_del);
|
||||
|
||||
m.def("pass_udmp_del", pass_udmp_del);
|
||||
m.def("pass_udcp_del", pass_udcp_del);
|
||||
|
||||
m.def("rtrn_udmp_del_nd", rtrn_udmp_del_nd);
|
||||
m.def("rtrn_udcp_del_nd", rtrn_udcp_del_nd);
|
||||
|
||||
m.def("pass_udmp_del_nd", pass_udmp_del_nd);
|
||||
m.def("pass_udcp_del_nd", pass_udcp_del_nd);
|
||||
|
||||
py::classh<uconsumer>(m, "uconsumer")
|
||||
.def(py::init<>())
|
||||
.def("valid", &uconsumer::valid)
|
||||
.def("pass_valu", &uconsumer::pass_valu)
|
||||
.def("pass_rref", &uconsumer::pass_rref)
|
||||
.def("rtrn_valu", &uconsumer::rtrn_valu)
|
||||
.def("rtrn_lref", &uconsumer::rtrn_lref)
|
||||
.def("rtrn_cref", &uconsumer::rtrn_cref);
|
||||
|
||||
// Helpers for testing.
|
||||
// These require selected functions above to work first, as indicated:
|
||||
m.def("get_mtxt", get_mtxt); // pass_cref
|
||||
m.def("get_ptr", get_ptr); // pass_cref
|
||||
|
||||
m.def("unique_ptr_roundtrip", unique_ptr_roundtrip); // pass_uqmp, rtrn_uqmp
|
||||
|
||||
m.def("pass_unique_ptr_cref", pass_unique_ptr_cref);
|
||||
m.def("rtrn_unique_ptr_cref", rtrn_unique_ptr_cref);
|
||||
m.def("unique_ptr_cref_roundtrip", unique_ptr_cref_roundtrip);
|
||||
|
||||
py::classh<SharedPtrStash>(m, "SharedPtrStash")
|
||||
.def(py::init<>())
|
||||
.def("Add", &SharedPtrStash::Add, py::arg("obj"));
|
||||
|
||||
m.def("py_type_handle_of_atyp", []() {
|
||||
return py::type::handle_of<atyp>(); // Exercises static_cast in this function.
|
||||
});
|
||||
|
||||
// Checks for type names used as arguments
|
||||
m.def("args_shared_ptr", [](std::shared_ptr<atyp> p) { return p; });
|
||||
m.def("args_shared_ptr_const", [](std::shared_ptr<atyp const> p) { return p; });
|
||||
m.def("args_unique_ptr", [](std::unique_ptr<atyp> p) { return p; });
|
||||
m.def("args_unique_ptr_const", [](std::unique_ptr<atyp const> p) { return p; });
|
||||
|
||||
// Make sure unique_ptr type caster accept automatic_reference return value policy.
|
||||
m.def(
|
||||
"rtrn_uq_automatic_reference",
|
||||
[]() { return std::unique_ptr<atyp>(new atyp("rtrn_uq_automatic_reference")); },
|
||||
pybind11::return_value_policy::automatic_reference);
|
||||
|
||||
m.def("pass_shared_ptr_ptr", [](std::shared_ptr<atyp> *) {});
|
||||
|
||||
py::classh<LocalUnusualOpRef>(m, "LocalUnusualOpRef");
|
||||
m.def("CallCastUnusualOpRefConstRef",
|
||||
[]() { return CastUnusualOpRefConstRef(LocalUnusualOpRef()); });
|
||||
m.def("CallCastUnusualOpRefMovable",
|
||||
[]() { return CastUnusualOpRefMovable(LocalUnusualOpRef()); });
|
||||
}
|
||||
|
||||
} // namespace class_sh_basic
|
||||
} // namespace pybind11_tests
|
|
@ -1,246 +0,0 @@
|
|||
# Importing re before pytest after observing a PyPy CI flake when importing pytest first.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_basic as m
|
||||
|
||||
|
||||
def test_atyp_constructors():
|
||||
obj = m.atyp()
|
||||
assert obj.__class__.__name__ == "atyp"
|
||||
obj = m.atyp("")
|
||||
assert obj.__class__.__name__ == "atyp"
|
||||
obj = m.atyp("txtm")
|
||||
assert obj.__class__.__name__ == "atyp"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("rtrn_f", "expected"),
|
||||
[
|
||||
(m.rtrn_valu, "rtrn_valu(_MvCtor)*_MvCtor"),
|
||||
(m.rtrn_rref, "rtrn_rref(_MvCtor)*_MvCtor"),
|
||||
(m.rtrn_cref, "rtrn_cref(_MvCtor)*_CpCtor"),
|
||||
(m.rtrn_mref, "rtrn_mref(_MvCtor)*_CpCtor"),
|
||||
(m.rtrn_cptr, "rtrn_cptr"),
|
||||
(m.rtrn_mptr, "rtrn_mptr"),
|
||||
(m.rtrn_shmp, "rtrn_shmp"),
|
||||
(m.rtrn_shcp, "rtrn_shcp"),
|
||||
(m.rtrn_uqmp, "rtrn_uqmp"),
|
||||
(m.rtrn_uqcp, "rtrn_uqcp"),
|
||||
(m.rtrn_udmp, "rtrn_udmp"),
|
||||
(m.rtrn_udcp, "rtrn_udcp"),
|
||||
],
|
||||
)
|
||||
def test_cast(rtrn_f, expected):
|
||||
assert re.match(expected, m.get_mtxt(rtrn_f()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "mtxt", "expected"),
|
||||
[
|
||||
(m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"),
|
||||
(m.pass_cref, "Cref", "pass_cref:Cref(_MvCtor)*_MvCtor"),
|
||||
(m.pass_mref, "Mref", "pass_mref:Mref(_MvCtor)*_MvCtor"),
|
||||
(m.pass_cptr, "Cptr", "pass_cptr:Cptr(_MvCtor)*_MvCtor"),
|
||||
(m.pass_mptr, "Mptr", "pass_mptr:Mptr(_MvCtor)*_MvCtor"),
|
||||
(m.pass_shmp, "Shmp", "pass_shmp:Shmp(_MvCtor)*_MvCtor"),
|
||||
(m.pass_shcp, "Shcp", "pass_shcp:Shcp(_MvCtor)*_MvCtor"),
|
||||
(m.pass_uqmp, "Uqmp", "pass_uqmp:Uqmp(_MvCtor)*_MvCtor"),
|
||||
(m.pass_uqcp, "Uqcp", "pass_uqcp:Uqcp(_MvCtor)*_MvCtor"),
|
||||
],
|
||||
)
|
||||
def test_load_with_mtxt(pass_f, mtxt, expected):
|
||||
assert re.match(expected, pass_f(m.atyp(mtxt)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "expected"),
|
||||
[
|
||||
(m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"),
|
||||
(m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"),
|
||||
],
|
||||
)
|
||||
def test_load_with_rtrn_f(pass_f, rtrn_f, expected):
|
||||
assert pass_f(rtrn_f()) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "regex_expected"),
|
||||
[
|
||||
(
|
||||
m.pass_udmp_del,
|
||||
m.rtrn_udmp_del,
|
||||
"pass_udmp_del:rtrn_udmp_del,udmp_deleter(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udcp_del,
|
||||
m.rtrn_udcp_del,
|
||||
"pass_udcp_del:rtrn_udcp_del,udcp_deleter(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udmp_del_nd,
|
||||
m.rtrn_udmp_del_nd,
|
||||
"pass_udmp_del_nd:rtrn_udmp_del_nd,udmp_deleter_nd(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udcp_del_nd,
|
||||
m.rtrn_udcp_del_nd,
|
||||
"pass_udcp_del_nd:rtrn_udcp_del_nd,udcp_deleter_nd(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_deleter_roundtrip(pass_f, rtrn_f, regex_expected):
|
||||
assert re.match(regex_expected, pass_f(rtrn_f()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "expected"),
|
||||
[
|
||||
(m.pass_uqmp, m.rtrn_uqmp, "pass_uqmp:rtrn_uqmp"),
|
||||
(m.pass_uqcp, m.rtrn_uqcp, "pass_uqcp:rtrn_uqcp"),
|
||||
(m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"),
|
||||
(m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"),
|
||||
],
|
||||
)
|
||||
def test_pass_unique_ptr_disowns(pass_f, rtrn_f, expected):
|
||||
obj = rtrn_f()
|
||||
assert pass_f(obj) == expected
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
pass_f(obj)
|
||||
assert str(exc_info.value) == (
|
||||
"Missing value for wrapped C++ type"
|
||||
+ " `pybind11_tests::class_sh_basic::atyp`:"
|
||||
+ " Python instance was disowned."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f"),
|
||||
[
|
||||
(m.pass_uqmp, m.rtrn_uqmp),
|
||||
(m.pass_uqcp, m.rtrn_uqcp),
|
||||
(m.pass_udmp, m.rtrn_udmp),
|
||||
(m.pass_udcp, m.rtrn_udcp),
|
||||
],
|
||||
)
|
||||
def test_cannot_disown_use_count_ne_1(pass_f, rtrn_f):
|
||||
obj = rtrn_f()
|
||||
stash = m.SharedPtrStash()
|
||||
stash.Add(obj)
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
pass_f(obj)
|
||||
assert str(exc_info.value) == ("Cannot disown use_count != 1 (load_as_unique_ptr).")
|
||||
|
||||
|
||||
def test_unique_ptr_roundtrip(num_round_trips=1000):
|
||||
# Multiple roundtrips to stress-test instance registration/deregistration.
|
||||
recycled = m.atyp("passenger")
|
||||
for _ in range(num_round_trips):
|
||||
id_orig = id(recycled)
|
||||
recycled = m.unique_ptr_roundtrip(recycled)
|
||||
assert re.match("passenger(_MvCtor)*_MvCtor", m.get_mtxt(recycled))
|
||||
id_rtrn = id(recycled)
|
||||
# Ensure the returned object is a different Python instance.
|
||||
assert id_rtrn != id_orig
|
||||
id_orig = id_rtrn
|
||||
|
||||
|
||||
def test_pass_unique_ptr_cref():
|
||||
obj = m.atyp("ctor_arg")
|
||||
assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj))
|
||||
assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.pass_unique_ptr_cref(obj))
|
||||
assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj))
|
||||
|
||||
|
||||
def test_rtrn_unique_ptr_cref():
|
||||
obj0 = m.rtrn_unique_ptr_cref("")
|
||||
assert m.get_mtxt(obj0) == "static_ctor_arg"
|
||||
obj1 = m.rtrn_unique_ptr_cref("passed_mtxt_1")
|
||||
assert m.get_mtxt(obj1) == "passed_mtxt_1"
|
||||
assert m.get_mtxt(obj0) == "passed_mtxt_1"
|
||||
assert obj0 is obj1
|
||||
|
||||
|
||||
def test_unique_ptr_cref_roundtrip(num_round_trips=1000):
|
||||
# Multiple roundtrips to stress-test implementation.
|
||||
orig = m.atyp("passenger")
|
||||
mtxt_orig = m.get_mtxt(orig)
|
||||
recycled = orig
|
||||
for _ in range(num_round_trips):
|
||||
recycled = m.unique_ptr_cref_roundtrip(recycled)
|
||||
assert recycled is orig
|
||||
assert m.get_mtxt(recycled) == mtxt_orig
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "moved_out", "moved_in"),
|
||||
[
|
||||
(m.uconsumer.pass_valu, m.uconsumer.rtrn_valu, True, True),
|
||||
(m.uconsumer.pass_rref, m.uconsumer.rtrn_valu, True, True),
|
||||
(m.uconsumer.pass_valu, m.uconsumer.rtrn_lref, True, False),
|
||||
(m.uconsumer.pass_valu, m.uconsumer.rtrn_cref, True, False),
|
||||
],
|
||||
)
|
||||
def test_unique_ptr_consumer_roundtrip(pass_f, rtrn_f, moved_out, moved_in):
|
||||
c = m.uconsumer()
|
||||
assert not c.valid()
|
||||
recycled = m.atyp("passenger")
|
||||
mtxt_orig = m.get_mtxt(recycled)
|
||||
assert re.match("passenger_(MvCtor){1,2}", mtxt_orig)
|
||||
|
||||
pass_f(c, recycled)
|
||||
if moved_out:
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
m.get_mtxt(recycled)
|
||||
assert "Python instance was disowned" in str(excinfo.value)
|
||||
|
||||
recycled = rtrn_f(c)
|
||||
assert c.valid() != moved_in
|
||||
assert m.get_mtxt(recycled) == mtxt_orig
|
||||
|
||||
|
||||
def test_py_type_handle_of_atyp():
|
||||
obj = m.py_type_handle_of_atyp()
|
||||
assert obj.__class__.__name__ == "pybind11_type"
|
||||
|
||||
|
||||
def test_function_signatures(doc):
|
||||
assert (
|
||||
doc(m.args_shared_ptr)
|
||||
== "args_shared_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
assert (
|
||||
doc(m.args_shared_ptr_const)
|
||||
== "args_shared_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
assert (
|
||||
doc(m.args_unique_ptr)
|
||||
== "args_unique_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
assert (
|
||||
doc(m.args_unique_ptr_const)
|
||||
== "args_unique_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
|
||||
|
||||
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<T> *` 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"
|
||||
assert m.CallCastUnusualOpRefMovable().__class__.__name__ == "LocalUnusualOpRef"
|
|
@ -1,41 +0,0 @@
|
|||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_disowning {
|
||||
|
||||
template <int SerNo> // Using int as a trick to easily generate a series of types.
|
||||
struct Atype {
|
||||
int val = 0;
|
||||
explicit Atype(int val_) : val{val_} {}
|
||||
int get() const { return val * 10 + SerNo; }
|
||||
};
|
||||
|
||||
int same_twice(std::unique_ptr<Atype<1>> at1a, std::unique_ptr<Atype<1>> at1b) {
|
||||
return at1a->get() * 100 + at1b->get() * 10;
|
||||
}
|
||||
|
||||
int mixed(std::unique_ptr<Atype<1>> at1, std::unique_ptr<Atype<2>> at2) {
|
||||
return at1->get() * 200 + at2->get() * 20;
|
||||
}
|
||||
|
||||
int overloaded(std::unique_ptr<Atype<1>> at1, int i) { return at1->get() * 30 + i; }
|
||||
int overloaded(std::unique_ptr<Atype<2>> at2, int i) { return at2->get() * 40 + i; }
|
||||
|
||||
} // namespace class_sh_disowning
|
||||
} // namespace pybind11_tests
|
||||
|
||||
TEST_SUBMODULE(class_sh_disowning, m) {
|
||||
using namespace pybind11_tests::class_sh_disowning;
|
||||
|
||||
py::classh<Atype<1>>(m, "Atype1").def(py::init<int>()).def("get", &Atype<1>::get);
|
||||
py::classh<Atype<2>>(m, "Atype2").def(py::init<int>()).def("get", &Atype<2>::get);
|
||||
|
||||
m.def("same_twice", same_twice);
|
||||
|
||||
m.def("mixed", mixed);
|
||||
|
||||
m.def("overloaded", (int (*)(std::unique_ptr<Atype<1>>, int)) &overloaded);
|
||||
m.def("overloaded", (int (*)(std::unique_ptr<Atype<2>>, int)) &overloaded);
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_disowning as m
|
||||
|
||||
|
||||
def is_disowned(obj):
|
||||
try:
|
||||
obj.get()
|
||||
except ValueError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def test_same_twice():
|
||||
while True:
|
||||
obj1a = m.Atype1(57)
|
||||
obj1b = m.Atype1(62)
|
||||
assert m.same_twice(obj1a, obj1b) == (57 * 10 + 1) * 100 + (62 * 10 + 1) * 10
|
||||
assert is_disowned(obj1a)
|
||||
assert is_disowned(obj1b)
|
||||
obj1c = m.Atype1(0)
|
||||
with pytest.raises(ValueError):
|
||||
# Disowning works for one argument, but not both.
|
||||
m.same_twice(obj1c, obj1c)
|
||||
assert is_disowned(obj1c)
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_mixed():
|
||||
first_pass = True
|
||||
while True:
|
||||
obj1a = m.Atype1(90)
|
||||
obj2a = m.Atype2(25)
|
||||
assert m.mixed(obj1a, obj2a) == (90 * 10 + 1) * 200 + (25 * 10 + 2) * 20
|
||||
assert is_disowned(obj1a)
|
||||
assert is_disowned(obj2a)
|
||||
|
||||
# The C++ order of evaluation of function arguments is (unfortunately) unspecified:
|
||||
# https://en.cppreference.com/w/cpp/language/eval_order
|
||||
# Read on.
|
||||
obj1b = m.Atype1(0)
|
||||
with pytest.raises(ValueError):
|
||||
# If the 1st argument is evaluated first, obj1b is disowned before the conversion for
|
||||
# the already disowned obj2a fails as expected.
|
||||
m.mixed(obj1b, obj2a)
|
||||
obj2b = m.Atype2(0)
|
||||
with pytest.raises(ValueError):
|
||||
# If the 2nd argument is evaluated first, obj2b is disowned before the conversion for
|
||||
# the already disowned obj1a fails as expected.
|
||||
m.mixed(obj1a, obj2b)
|
||||
|
||||
# Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not
|
||||
# both.
|
||||
is_disowned_results = (is_disowned(obj1b), is_disowned(obj2b))
|
||||
assert is_disowned_results.count(True) == 1
|
||||
if first_pass:
|
||||
first_pass = False
|
||||
ix = is_disowned_results.index(True) + 1
|
||||
print(f"\nC++ function argument {ix} is evaluated first.")
|
||||
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_overloaded():
|
||||
while True:
|
||||
obj1 = m.Atype1(81)
|
||||
obj2 = m.Atype2(60)
|
||||
with pytest.raises(TypeError):
|
||||
m.overloaded(obj1, "NotInt")
|
||||
assert obj1.get() == 81 * 10 + 1 # Not disowned.
|
||||
assert m.overloaded(obj1, 3) == (81 * 10 + 1) * 30 + 3
|
||||
with pytest.raises(TypeError):
|
||||
m.overloaded(obj2, "NotInt")
|
||||
assert obj2.get() == 60 * 10 + 2 # Not disowned.
|
||||
assert m.overloaded(obj2, 2) == (60 * 10 + 2) * 40 + 2
|
||||
return # Comment out for manual leak checking (use `top` command).
|
|
@ -1,85 +0,0 @@
|
|||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_disowning_mi {
|
||||
|
||||
// Diamond inheritance (copied from test_multiple_inheritance.cpp).
|
||||
struct B {
|
||||
int val_b = 10;
|
||||
B() = default;
|
||||
B(const B &) = default;
|
||||
virtual ~B() = default;
|
||||
};
|
||||
|
||||
struct C0 : public virtual B {
|
||||
int val_c0 = 20;
|
||||
};
|
||||
|
||||
struct C1 : public virtual B {
|
||||
int val_c1 = 21;
|
||||
};
|
||||
|
||||
struct D : public C0, public C1 {
|
||||
int val_d = 30;
|
||||
};
|
||||
|
||||
void disown_b(std::unique_ptr<B>) {}
|
||||
|
||||
// test_multiple_inheritance_python
|
||||
struct Base1 {
|
||||
explicit Base1(int i) : i(i) {}
|
||||
int foo() const { return i; }
|
||||
int i;
|
||||
};
|
||||
|
||||
struct Base2 {
|
||||
explicit Base2(int j) : j(j) {}
|
||||
int bar() const { return j; }
|
||||
int j;
|
||||
};
|
||||
|
||||
int disown_base1(std::unique_ptr<Base1> b1) { return b1->i * 2000 + 1; }
|
||||
int disown_base2(std::unique_ptr<Base2> b2) { return b2->j * 2000 + 2; }
|
||||
|
||||
} // namespace class_sh_disowning_mi
|
||||
} // namespace pybind11_tests
|
||||
|
||||
TEST_SUBMODULE(class_sh_disowning_mi, m) {
|
||||
using namespace pybind11_tests::class_sh_disowning_mi;
|
||||
|
||||
py::classh<B>(m, "B")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_b", &D::val_b)
|
||||
.def("b", [](B *self) { return self; })
|
||||
.def("get", [](const B &self) { return self.val_b; });
|
||||
|
||||
py::classh<C0, B>(m, "C0")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_c0", &D::val_c0)
|
||||
.def("c0", [](C0 *self) { return self; })
|
||||
.def("get", [](const C0 &self) { return self.val_b * 100 + self.val_c0; });
|
||||
|
||||
py::classh<C1, B>(m, "C1")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_c1", &D::val_c1)
|
||||
.def("c1", [](C1 *self) { return self; })
|
||||
.def("get", [](const C1 &self) { return self.val_b * 100 + self.val_c1; });
|
||||
|
||||
py::classh<D, C0, C1>(m, "D")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_d", &D::val_d)
|
||||
.def("d", [](D *self) { return self; })
|
||||
.def("get", [](const D &self) {
|
||||
return self.val_b * 1000000 + self.val_c0 * 10000 + self.val_c1 * 100 + self.val_d;
|
||||
});
|
||||
|
||||
m.def("disown_b", disown_b);
|
||||
|
||||
// test_multiple_inheritance_python
|
||||
py::classh<Base1>(m, "Base1").def(py::init<int>()).def("foo", &Base1::foo);
|
||||
py::classh<Base2>(m, "Base2").def(py::init<int>()).def("bar", &Base2::bar);
|
||||
m.def("disown_base1", disown_base1);
|
||||
m.def("disown_base2", disown_base2);
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import env # noqa: F401
|
||||
from pybind11_tests import class_sh_disowning_mi as m
|
||||
|
||||
|
||||
def test_diamond_inheritance():
|
||||
# Very similar to test_multiple_inheritance.py:test_diamond_inheritance.
|
||||
d = m.D()
|
||||
assert d is d.d()
|
||||
assert d is d.c0()
|
||||
assert d is d.c1()
|
||||
assert d is d.b()
|
||||
assert d is d.c0().b()
|
||||
assert d is d.c1().b()
|
||||
assert d is d.c0().c1().b().c0().b()
|
||||
|
||||
|
||||
def is_disowned(callable_method):
|
||||
try:
|
||||
callable_method()
|
||||
except ValueError as e:
|
||||
assert "Python instance was disowned" in str(e) # noqa: PT017
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def test_disown_b():
|
||||
b = m.B()
|
||||
assert b.get() == 10
|
||||
m.disown_b(b)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var_to_disown", ["c0", "b"])
|
||||
def test_disown_c0(var_to_disown):
|
||||
c0 = m.C0()
|
||||
assert c0.get() == 1020
|
||||
b = c0.b()
|
||||
m.disown_b(locals()[var_to_disown])
|
||||
assert is_disowned(c0.get)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var_to_disown", ["c1", "b"])
|
||||
def test_disown_c1(var_to_disown):
|
||||
c1 = m.C1()
|
||||
assert c1.get() == 1021
|
||||
b = c1.b()
|
||||
m.disown_b(locals()[var_to_disown])
|
||||
assert is_disowned(c1.get)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var_to_disown", ["d", "c1", "c0", "b"])
|
||||
def test_disown_d(var_to_disown):
|
||||
d = m.D()
|
||||
assert d.get() == 10202130
|
||||
b = d.b()
|
||||
c0 = d.c0()
|
||||
c1 = d.c1()
|
||||
m.disown_b(locals()[var_to_disown])
|
||||
assert is_disowned(d.get)
|
||||
assert is_disowned(c1.get)
|
||||
assert is_disowned(c0.get)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
# Based on test_multiple_inheritance.py:test_multiple_inheritance_python.
|
||||
class MI1(m.Base1, m.Base2):
|
||||
def __init__(self, i, j):
|
||||
m.Base1.__init__(self, i)
|
||||
m.Base2.__init__(self, j)
|
||||
|
||||
|
||||
class B1:
|
||||
def v(self):
|
||||
return 1
|
||||
|
||||
|
||||
class MI2(B1, m.Base1, m.Base2):
|
||||
def __init__(self, i, j):
|
||||
B1.__init__(self)
|
||||
m.Base1.__init__(self, i)
|
||||
m.Base2.__init__(self, j)
|
||||
|
||||
|
||||
class MI3(MI2):
|
||||
def __init__(self, i, j):
|
||||
MI2.__init__(self, i, j)
|
||||
|
||||
|
||||
class MI4(MI3, m.Base2):
|
||||
def __init__(self, i, j):
|
||||
MI3.__init__(self, i, j)
|
||||
# This should be ignored (Base2 is already initialized via MI2):
|
||||
m.Base2.__init__(self, i + 100)
|
||||
|
||||
|
||||
class MI5(m.Base2, B1, m.Base1):
|
||||
def __init__(self, i, j):
|
||||
B1.__init__(self)
|
||||
m.Base1.__init__(self, i)
|
||||
m.Base2.__init__(self, j)
|
||||
|
||||
|
||||
class MI6(m.Base2, B1):
|
||||
def __init__(self, i):
|
||||
m.Base2.__init__(self, i)
|
||||
B1.__init__(self)
|
||||
|
||||
|
||||
class B2(B1):
|
||||
def v(self):
|
||||
return 2
|
||||
|
||||
|
||||
class B3:
|
||||
def v(self):
|
||||
return 3
|
||||
|
||||
|
||||
class B4(B3, B2):
|
||||
def v(self):
|
||||
return 4
|
||||
|
||||
|
||||
class MI7(B4, MI6):
|
||||
def __init__(self, i):
|
||||
B4.__init__(self)
|
||||
MI6.__init__(self, i)
|
||||
|
||||
|
||||
class MI8(MI6, B3):
|
||||
def __init__(self, i):
|
||||
MI6.__init__(self, i)
|
||||
B3.__init__(self)
|
||||
|
||||
|
||||
class MI8b(B3, MI6):
|
||||
def __init__(self, i):
|
||||
B3.__init__(self)
|
||||
MI6.__init__(self, i)
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY")
|
||||
def test_multiple_inheritance_python():
|
||||
# Based on test_multiple_inheritance.py:test_multiple_inheritance_python.
|
||||
# Exercises values_and_holders with 2 value_and_holder instances.
|
||||
|
||||
mi1 = MI1(1, 2)
|
||||
assert mi1.foo() == 1
|
||||
assert mi1.bar() == 2
|
||||
|
||||
mi2 = MI2(3, 4)
|
||||
assert mi2.v() == 1
|
||||
assert mi2.foo() == 3
|
||||
assert mi2.bar() == 4
|
||||
|
||||
mi3 = MI3(5, 6)
|
||||
assert mi3.v() == 1
|
||||
assert mi3.foo() == 5
|
||||
assert mi3.bar() == 6
|
||||
|
||||
mi4 = MI4(7, 8)
|
||||
assert mi4.v() == 1
|
||||
assert mi4.foo() == 7
|
||||
assert mi4.bar() == 8
|
||||
|
||||
mi5 = MI5(10, 11)
|
||||
assert mi5.v() == 1
|
||||
assert mi5.foo() == 10
|
||||
assert mi5.bar() == 11
|
||||
|
||||
mi6 = MI6(12)
|
||||
assert mi6.v() == 1
|
||||
assert mi6.bar() == 12
|
||||
|
||||
mi7 = MI7(13)
|
||||
assert mi7.v() == 4
|
||||
assert mi7.bar() == 13
|
||||
|
||||
mi8 = MI8(14)
|
||||
assert mi8.v() == 1
|
||||
assert mi8.bar() == 14
|
||||
|
||||
mi8b = MI8b(15)
|
||||
assert mi8b.v() == 3
|
||||
assert mi8b.bar() == 15
|
||||
|
||||
|
||||
DISOWN_CLS_I_J_V_LIST = [
|
||||
(MI1, 1, 2, None),
|
||||
(MI2, 3, 4, 1),
|
||||
(MI3, 5, 6, 1),
|
||||
(MI4, 7, 8, 1),
|
||||
(MI5, 10, 11, 1),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST)
|
||||
def test_disown_base1_first(cls, i, j, v):
|
||||
obj = cls(i, j)
|
||||
assert obj.foo() == i
|
||||
assert m.disown_base1(obj) == 2000 * i + 1
|
||||
assert is_disowned(obj.foo)
|
||||
assert obj.bar() == j
|
||||
assert m.disown_base2(obj) == 2000 * j + 2
|
||||
assert is_disowned(obj.bar)
|
||||
if v is not None:
|
||||
assert obj.v() == v
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST)
|
||||
def test_disown_base2_first(cls, i, j, v):
|
||||
obj = cls(i, j)
|
||||
assert obj.bar() == j
|
||||
assert m.disown_base2(obj) == 2000 * j + 2
|
||||
assert is_disowned(obj.bar)
|
||||
assert obj.foo() == i
|
||||
assert m.disown_base1(obj) == 2000 * i + 1
|
||||
assert is_disowned(obj.foo)
|
||||
if v is not None:
|
||||
assert obj.v() == v
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.parametrize(
|
||||
("cls", "j", "v"),
|
||||
[
|
||||
(MI6, 12, 1),
|
||||
(MI7, 13, 4),
|
||||
(MI8, 14, 1),
|
||||
(MI8b, 15, 3),
|
||||
],
|
||||
)
|
||||
def test_disown_base2(cls, j, v):
|
||||
obj = cls(j)
|
||||
assert obj.bar() == j
|
||||
assert m.disown_base2(obj) == 2000 * j + 2
|
||||
assert is_disowned(obj.bar)
|
||||
assert obj.v() == v
|
|
@ -1,164 +0,0 @@
|
|||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_factory_constructors {
|
||||
|
||||
template <int> // Using int as a trick to easily generate a series of types.
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::string get_mtxt(const T &obj) {
|
||||
return obj.mtxt;
|
||||
}
|
||||
|
||||
using atyp_valu = atyp<0x0>;
|
||||
using atyp_rref = atyp<0x1>;
|
||||
using atyp_cref = atyp<0x2>;
|
||||
using atyp_mref = atyp<0x3>;
|
||||
using atyp_cptr = atyp<0x4>;
|
||||
using atyp_mptr = atyp<0x5>;
|
||||
using atyp_shmp = atyp<0x6>;
|
||||
using atyp_shcp = atyp<0x7>;
|
||||
using atyp_uqmp = atyp<0x8>;
|
||||
using atyp_uqcp = atyp<0x9>;
|
||||
using atyp_udmp = atyp<0xA>;
|
||||
using atyp_udcp = atyp<0xB>;
|
||||
|
||||
// clang-format off
|
||||
|
||||
atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
|
||||
atyp_rref&& rtrn_rref() { static atyp_rref obj; obj.mtxt = "Rref"; return std::move(obj); }
|
||||
atyp_cref const& rtrn_cref() { static atyp_cref obj; obj.mtxt = "Cref"; return obj; }
|
||||
atyp_mref& rtrn_mref() { static atyp_mref obj; obj.mtxt = "Mref"; return obj; }
|
||||
atyp_cptr const* rtrn_cptr() { return new atyp_cptr{"Cptr"}; }
|
||||
atyp_mptr* rtrn_mptr() { return new atyp_mptr{"Mptr"}; }
|
||||
|
||||
std::shared_ptr<atyp_shmp> rtrn_shmp() { return std::make_shared<atyp_shmp>(atyp_shmp{"Shmp"}); }
|
||||
std::shared_ptr<atyp_shcp const> rtrn_shcp() { return std::shared_ptr<atyp_shcp const>(new atyp_shcp{"Shcp"}); }
|
||||
|
||||
std::unique_ptr<atyp_uqmp> rtrn_uqmp() { return std::unique_ptr<atyp_uqmp >(new atyp_uqmp{"Uqmp"}); }
|
||||
std::unique_ptr<atyp_uqcp const> rtrn_uqcp() { return std::unique_ptr<atyp_uqcp const>(new atyp_uqcp{"Uqcp"}); }
|
||||
|
||||
struct sddm : std::default_delete<atyp_udmp > {};
|
||||
struct sddc : std::default_delete<atyp_udcp const> {};
|
||||
|
||||
std::unique_ptr<atyp_udmp, sddm> rtrn_udmp() { return std::unique_ptr<atyp_udmp, sddm>(new atyp_udmp{"Udmp"}); }
|
||||
std::unique_ptr<atyp_udcp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp_udcp const, sddc>(new atyp_udcp{"Udcp"}); }
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Minimalistic approach to achieve full coverage of construct() overloads for constructing
|
||||
// smart_holder from unique_ptr and shared_ptr returns.
|
||||
struct with_alias {
|
||||
int val = 0;
|
||||
virtual ~with_alias() = default;
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
with_alias() = default;
|
||||
with_alias(const with_alias &) = default;
|
||||
with_alias(with_alias &&) = default;
|
||||
with_alias &operator=(const with_alias &) = default;
|
||||
with_alias &operator=(with_alias &&) = default;
|
||||
};
|
||||
struct with_alias_alias : with_alias {};
|
||||
struct sddwaa : std::default_delete<with_alias_alias> {};
|
||||
|
||||
} // namespace class_sh_factory_constructors
|
||||
} // namespace pybind11_tests
|
||||
|
||||
TEST_SUBMODULE(class_sh_factory_constructors, m) {
|
||||
using namespace pybind11_tests::class_sh_factory_constructors;
|
||||
|
||||
py::classh<atyp_valu>(m, "atyp_valu")
|
||||
.def(py::init(&rtrn_valu))
|
||||
.def("get_mtxt", get_mtxt<atyp_valu>);
|
||||
|
||||
py::classh<atyp_rref>(m, "atyp_rref")
|
||||
.def(py::init(&rtrn_rref))
|
||||
.def("get_mtxt", get_mtxt<atyp_rref>);
|
||||
|
||||
py::classh<atyp_cref>(m, "atyp_cref")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_cref))
|
||||
.def("get_mtxt", get_mtxt<atyp_cref>);
|
||||
|
||||
py::classh<atyp_mref>(m, "atyp_mref")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_mref))
|
||||
.def("get_mtxt", get_mtxt<atyp_mref>);
|
||||
|
||||
py::classh<atyp_cptr>(m, "atyp_cptr")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... must return a compatible ...
|
||||
// .def(py::init(&rtrn_cptr))
|
||||
.def("get_mtxt", get_mtxt<atyp_cptr>);
|
||||
|
||||
py::classh<atyp_mptr>(m, "atyp_mptr")
|
||||
.def(py::init(&rtrn_mptr))
|
||||
.def("get_mtxt", get_mtxt<atyp_mptr>);
|
||||
|
||||
py::classh<atyp_shmp>(m, "atyp_shmp")
|
||||
.def(py::init(&rtrn_shmp))
|
||||
.def("get_mtxt", get_mtxt<atyp_shmp>);
|
||||
|
||||
py::classh<atyp_shcp>(m, "atyp_shcp")
|
||||
// py::class_<atyp_shcp, std::shared_ptr<atyp_shcp>>(m, "atyp_shcp")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_shcp))
|
||||
.def("get_mtxt", get_mtxt<atyp_shcp>);
|
||||
|
||||
py::classh<atyp_uqmp>(m, "atyp_uqmp")
|
||||
.def(py::init(&rtrn_uqmp))
|
||||
.def("get_mtxt", get_mtxt<atyp_uqmp>);
|
||||
|
||||
py::classh<atyp_uqcp>(m, "atyp_uqcp")
|
||||
// class_: ... cannot pass object of non-trivial type ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_uqcp))
|
||||
.def("get_mtxt", get_mtxt<atyp_uqcp>);
|
||||
|
||||
py::classh<atyp_udmp>(m, "atyp_udmp")
|
||||
.def(py::init(&rtrn_udmp))
|
||||
.def("get_mtxt", get_mtxt<atyp_udmp>);
|
||||
|
||||
py::classh<atyp_udcp>(m, "atyp_udcp")
|
||||
// py::class_<atyp_udcp, std::unique_ptr<atyp_udcp, sddc>>(m, "atyp_udcp")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_udcp))
|
||||
.def("get_mtxt", get_mtxt<atyp_udcp>);
|
||||
|
||||
py::classh<with_alias, with_alias_alias>(m, "with_alias")
|
||||
.def_readonly("val", &with_alias::val)
|
||||
.def(py::init([](int i) {
|
||||
auto p = std::unique_ptr<with_alias_alias, sddwaa>(new with_alias_alias);
|
||||
p->val = i * 100;
|
||||
return p;
|
||||
}))
|
||||
.def(py::init([](int i, int j) {
|
||||
auto p = std::unique_ptr<with_alias_alias>(new with_alias_alias);
|
||||
p->val = i * 100 + j * 10;
|
||||
return p;
|
||||
}))
|
||||
.def(py::init([](int i, int j, int k) {
|
||||
auto p = std::make_shared<with_alias_alias>();
|
||||
p->val = i * 100 + j * 10 + k;
|
||||
return p;
|
||||
}))
|
||||
.def(py::init(
|
||||
[](int, int, int, int) { return std::unique_ptr<with_alias>(new with_alias); },
|
||||
[](int, int, int, int) {
|
||||
return std::unique_ptr<with_alias>(new with_alias); // Invalid alias factory.
|
||||
}))
|
||||
.def(py::init([](int, int, int, int, int) { return std::make_shared<with_alias>(); },
|
||||
[](int, int, int, int, int) {
|
||||
return std::make_shared<with_alias>(); // Invalid alias factory.
|
||||
}));
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_factory_constructors as m
|
||||
|
||||
|
||||
def test_atyp_factories():
|
||||
assert m.atyp_valu().get_mtxt() == "Valu"
|
||||
assert m.atyp_rref().get_mtxt() == "Rref"
|
||||
# sert m.atyp_cref().get_mtxt() == "Cref"
|
||||
# sert m.atyp_mref().get_mtxt() == "Mref"
|
||||
# sert m.atyp_cptr().get_mtxt() == "Cptr"
|
||||
assert m.atyp_mptr().get_mtxt() == "Mptr"
|
||||
assert m.atyp_shmp().get_mtxt() == "Shmp"
|
||||
# sert m.atyp_shcp().get_mtxt() == "Shcp"
|
||||
assert m.atyp_uqmp().get_mtxt() == "Uqmp"
|
||||
# sert m.atyp_uqcp().get_mtxt() == "Uqcp"
|
||||
assert m.atyp_udmp().get_mtxt() == "Udmp"
|
||||
# sert m.atyp_udcp().get_mtxt() == "Udcp"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("init_args", "expected"),
|
||||
[
|
||||
((3,), 300),
|
||||
((5, 7), 570),
|
||||
((9, 11, 13), 1023),
|
||||
],
|
||||
)
|
||||
def test_with_alias_success(init_args, expected):
|
||||
assert m.with_alias(*init_args).val == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("num_init_args", "smart_ptr"),
|
||||
[
|
||||
(4, "std::unique_ptr"),
|
||||
(5, "std::shared_ptr"),
|
||||
],
|
||||
)
|
||||
def test_with_alias_invalid(num_init_args, smart_ptr):
|
||||
class PyDrvdWithAlias(m.with_alias):
|
||||
pass
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
PyDrvdWithAlias(*((0,) * num_init_args))
|
||||
assert (
|
||||
str(excinfo.value)
|
||||
== "pybind11::init(): construction failed: returned "
|
||||
+ smart_ptr
|
||||
+ " pointee is not an alias instance"
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue