Merge pull request #2060 from borglab/fan/fix_pybind_version
Fix incorrect pybind version in `wrap`release/4.3a0
commit
f401a9056e
|
|
@ -313,7 +313,7 @@ public:
|
||||||
typedef typename traits<T>::LieAlgebra LieAlgebra;
|
typedef typename traits<T>::LieAlgebra LieAlgebra;
|
||||||
typedef typename traits<T>::TangentVector TangentVector;
|
typedef typename traits<T>::TangentVector TangentVector;
|
||||||
|
|
||||||
BOOST_CONCEPT_USAGE(IsMatrixLieGroup) {
|
GTSAM_CONCEPT_USAGE(IsMatrixLieGroup) {
|
||||||
// hat and vee
|
// hat and vee
|
||||||
X = traits<T>::Hat(xi);
|
X = traits<T>::Hat(xi);
|
||||||
xi = traits<T>::Vee(X);
|
xi = traits<T>::Vee(X);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
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(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) {
|
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"])
|
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
|
||||||
struct IntStruct {
|
struct IntStruct {
|
||||||
explicit IntStruct(int v) : value(v){};
|
explicit IntStruct(int v) : value(v){};
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ nox -s build
|
||||||
### Full setup
|
### Full setup
|
||||||
|
|
||||||
To setup an ideal development environment, run the following commands on a
|
To setup an ideal development environment, run the following commands on a
|
||||||
system with CMake 3.15+:
|
system with CMake 3.14+:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
|
|
@ -96,8 +96,8 @@ Tips:
|
||||||
* You can use `virtualenv` (faster, from PyPI) instead of `venv`.
|
* You can use `virtualenv` (faster, from PyPI) instead of `venv`.
|
||||||
* You can select any name for your environment folder; if it contains "env" it
|
* You can select any name for your environment folder; if it contains "env" it
|
||||||
will be ignored by git.
|
will be ignored by git.
|
||||||
* If you don't have CMake 3.15+, just add "cmake" to the pip install command.
|
* 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.
|
* 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`.
|
* In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`.
|
||||||
FindPython uses `-DPython_ROOT_DIR=/path/to` or
|
FindPython uses `-DPython_ROOT_DIR=/path/to` or
|
||||||
`-DPython_EXECUTABLE=/path/to/python`.
|
`-DPython_EXECUTABLE=/path/to/python`.
|
||||||
|
|
@ -149,8 +149,8 @@ To run the tests, you can "build" the check target:
|
||||||
cmake --build build --target check
|
cmake --build build --target check
|
||||||
```
|
```
|
||||||
|
|
||||||
`--target` can be spelled `-t`. You can also run individual tests with these
|
`--target` can be spelled `-t` in CMake 3.15+. You can also run individual
|
||||||
targets:
|
tests with these targets:
|
||||||
|
|
||||||
* `pytest`: Python tests only, using the
|
* `pytest`: Python tests only, using the
|
||||||
[pytest](https://docs.pytest.org/en/stable/) framework
|
[pytest](https://docs.pytest.org/en/stable/) framework
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,6 @@ jobs:
|
||||||
- 'pypy-3.8'
|
- 'pypy-3.8'
|
||||||
- 'pypy-3.9'
|
- 'pypy-3.9'
|
||||||
- 'pypy-3.10'
|
- 'pypy-3.10'
|
||||||
- 'pypy-3.11'
|
|
||||||
- 'graalpy-24.1'
|
|
||||||
|
|
||||||
# Items in here will either be added to the build matrix (if not
|
# 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
|
# present), or add new keys to an existing matrix element if all the
|
||||||
|
|
@ -66,45 +64,9 @@ jobs:
|
||||||
# Inject a couple Windows 2019 runs
|
# Inject a couple Windows 2019 runs
|
||||||
- runs-on: windows-2019
|
- runs-on: windows-2019
|
||||||
python: '3.9'
|
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
|
# Extra ubuntu latest job
|
||||||
- runs-on: ubuntu-latest
|
- runs-on: ubuntu-latest
|
||||||
python: '3.11'
|
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 }}"
|
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"
|
||||||
|
|
@ -160,7 +122,6 @@ jobs:
|
||||||
-DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
|
-DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
|
||||||
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON
|
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON
|
||||||
-DPYBIND11_NUMPY_1_ONLY=ON
|
-DPYBIND11_NUMPY_1_ONLY=ON
|
||||||
-DPYBIND11_PYTEST_ARGS=-v
|
|
||||||
-DDOWNLOAD_CATCH=ON
|
-DDOWNLOAD_CATCH=ON
|
||||||
-DDOWNLOAD_EIGEN=ON
|
-DDOWNLOAD_EIGEN=ON
|
||||||
-DCMAKE_CXX_STANDARD=11
|
-DCMAKE_CXX_STANDARD=11
|
||||||
|
|
@ -190,7 +151,6 @@ jobs:
|
||||||
-DPYBIND11_WERROR=ON
|
-DPYBIND11_WERROR=ON
|
||||||
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
|
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
|
||||||
-DPYBIND11_NUMPY_1_ONLY=ON
|
-DPYBIND11_NUMPY_1_ONLY=ON
|
||||||
-DPYBIND11_PYTEST_ARGS=-v
|
|
||||||
-DDOWNLOAD_CATCH=ON
|
-DDOWNLOAD_CATCH=ON
|
||||||
-DDOWNLOAD_EIGEN=ON
|
-DDOWNLOAD_EIGEN=ON
|
||||||
-DCMAKE_CXX_STANDARD=17
|
-DCMAKE_CXX_STANDARD=17
|
||||||
|
|
@ -210,7 +170,6 @@ jobs:
|
||||||
run: >
|
run: >
|
||||||
cmake -S . -B build3
|
cmake -S . -B build3
|
||||||
-DPYBIND11_WERROR=ON
|
-DPYBIND11_WERROR=ON
|
||||||
-DPYBIND11_PYTEST_ARGS=-v
|
|
||||||
-DDOWNLOAD_CATCH=ON
|
-DDOWNLOAD_CATCH=ON
|
||||||
-DDOWNLOAD_EIGEN=ON
|
-DDOWNLOAD_EIGEN=ON
|
||||||
-DCMAKE_CXX_STANDARD=17
|
-DCMAKE_CXX_STANDARD=17
|
||||||
|
|
@ -351,11 +310,22 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
clang:
|
||||||
|
- 3.6
|
||||||
|
- 3.7
|
||||||
|
- 3.9
|
||||||
|
- 7
|
||||||
|
- 9
|
||||||
|
- dev
|
||||||
|
std:
|
||||||
|
- 11
|
||||||
container_suffix:
|
container_suffix:
|
||||||
- ""
|
- ""
|
||||||
include:
|
include:
|
||||||
- clang: 5
|
- clang: 5
|
||||||
std: 14
|
std: 14
|
||||||
|
- clang: 10
|
||||||
|
std: 17
|
||||||
- clang: 11
|
- clang: 11
|
||||||
std: 20
|
std: 20
|
||||||
- clang: 12
|
- clang: 12
|
||||||
|
|
@ -533,6 +503,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- { gcc: 7, std: 11 }
|
||||||
|
- { gcc: 7, std: 17 }
|
||||||
|
- { gcc: 8, std: 14 }
|
||||||
|
- { gcc: 8, std: 17 }
|
||||||
- { gcc: 9, std: 20 }
|
- { gcc: 9, std: 20 }
|
||||||
- { gcc: 10, std: 17 }
|
- { gcc: 10, std: 17 }
|
||||||
- { gcc: 10, std: 20 }
|
- { gcc: 10, std: 20 }
|
||||||
|
|
@ -753,9 +727,9 @@ jobs:
|
||||||
|
|
||||||
# This tests an "install" with the CMake tools
|
# This tests an "install" with the CMake tools
|
||||||
install-classic:
|
install-classic:
|
||||||
name: "🐍 3.9 • Debian • x86 • Install"
|
name: "🐍 3.7 • Debian • x86 • Install"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: i386/debian:bullseye
|
container: i386/debian:buster
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1 # v1 is required to run inside docker
|
- uses: actions/checkout@v1 # v1 is required to run inside docker
|
||||||
|
|
@ -835,6 +809,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python:
|
python:
|
||||||
|
- '3.7'
|
||||||
- '3.8'
|
- '3.8'
|
||||||
- '3.9'
|
- '3.9'
|
||||||
- '3.10'
|
- '3.10'
|
||||||
|
|
@ -852,6 +827,8 @@ jobs:
|
||||||
args: -DCMAKE_CXX_STANDARD=20
|
args: -DCMAKE_CXX_STANDARD=20
|
||||||
- python: '3.8'
|
- python: '3.8'
|
||||||
args: -DCMAKE_CXX_STANDARD=17
|
args: -DCMAKE_CXX_STANDARD=17
|
||||||
|
- python: '3.7'
|
||||||
|
args: -DCMAKE_CXX_STANDARD=14
|
||||||
|
|
||||||
|
|
||||||
name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}"
|
name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}"
|
||||||
|
|
@ -1030,6 +1007,7 @@ jobs:
|
||||||
git
|
git
|
||||||
mingw-w64-${{matrix.env}}-gcc
|
mingw-w64-${{matrix.env}}-gcc
|
||||||
mingw-w64-${{matrix.env}}-python-pip
|
mingw-w64-${{matrix.env}}-python-pip
|
||||||
|
mingw-w64-${{matrix.env}}-python-numpy
|
||||||
mingw-w64-${{matrix.env}}-cmake
|
mingw-w64-${{matrix.env}}-cmake
|
||||||
mingw-w64-${{matrix.env}}-make
|
mingw-w64-${{matrix.env}}-make
|
||||||
mingw-w64-${{matrix.env}}-python-pytest
|
mingw-w64-${{matrix.env}}-python-pytest
|
||||||
|
|
@ -1041,7 +1019,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
msystem: ${{matrix.sys}}
|
msystem: ${{matrix.sys}}
|
||||||
install: >-
|
install: >-
|
||||||
mingw-w64-${{matrix.env}}-python-numpy
|
git
|
||||||
mingw-w64-${{matrix.env}}-python-scipy
|
mingw-w64-${{matrix.env}}-python-scipy
|
||||||
mingw-w64-${{matrix.env}}-eigen3
|
mingw-w64-${{matrix.env}}-eigen3
|
||||||
|
|
||||||
|
|
@ -1139,7 +1117,7 @@ jobs:
|
||||||
uses: jwlawson/actions-setup-cmake@v2.0
|
uses: jwlawson/actions-setup-cmake@v2.0
|
||||||
|
|
||||||
- name: Install ninja-build tool
|
- name: Install ninja-build tool
|
||||||
uses: seanmiddleditch/gha-setup-ninja@v6
|
uses: seanmiddleditch/gha-setup-ninja@v5
|
||||||
|
|
||||||
- name: Run pip installs
|
- name: Run pip installs
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- runs-on: ubuntu-20.04
|
- runs-on: ubuntu-20.04
|
||||||
arch: x64
|
arch: x64
|
||||||
cmake: "3.15"
|
cmake: "3.5"
|
||||||
|
|
||||||
- runs-on: ubuntu-20.04
|
- runs-on: ubuntu-20.04
|
||||||
arch: x64
|
arch: x64
|
||||||
|
|
@ -39,22 +39,22 @@ jobs:
|
||||||
|
|
||||||
- runs-on: macos-13
|
- runs-on: macos-13
|
||||||
arch: x64
|
arch: x64
|
||||||
cmake: "3.15"
|
cmake: "3.7"
|
||||||
|
|
||||||
- runs-on: windows-2019
|
- runs-on: windows-2019
|
||||||
arch: x64 # x86 compilers seem to be missing on 2019 image
|
arch: x64 # x86 compilers seem to be missing on 2019 image
|
||||||
cmake: "3.18"
|
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 }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Python 3.8
|
- name: Setup Python 3.7
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.7
|
||||||
architecture: ${{ matrix.arch }}
|
architecture: ${{ matrix.arch }}
|
||||||
|
|
||||||
- name: Prepare env
|
- name: Prepare env
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
submodules: true
|
submodules: true
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: pypa/cibuildwheel@v2.23
|
- uses: pypa/cibuildwheel@v2.20
|
||||||
env:
|
env:
|
||||||
PYODIDE_BUILD_EXPORTS: whole_archive
|
PYODIDE_BUILD_EXPORTS: whole_archive
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ jobs:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Generate artifact attestation for sdist and wheel
|
- 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:
|
with:
|
||||||
subject-path: "*/pybind11*"
|
subject-path: "*/pybind11*"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,14 @@ repos:
|
||||||
|
|
||||||
# Clang format the codebase automatically
|
# Clang format the codebase automatically
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: "v19.1.7"
|
rev: "v18.1.8"
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types_or: [c++, c, cuda]
|
types_or: [c++, c, cuda]
|
||||||
|
|
||||||
# Ruff, the Python auto-correcting linter/formatter written in Rust
|
# Ruff, the Python auto-correcting linter/formatter written in Rust
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.9.9
|
rev: v0.6.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: ["--fix", "--show-fixes"]
|
args: ["--fix", "--show-fixes"]
|
||||||
|
|
@ -40,7 +40,7 @@ repos:
|
||||||
|
|
||||||
# Check static types with mypy
|
# Check static types with mypy
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: "v1.15.0"
|
rev: "v1.11.2"
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
args: []
|
args: []
|
||||||
|
|
@ -62,7 +62,7 @@ repos:
|
||||||
|
|
||||||
# Standard hooks
|
# Standard hooks
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: "v5.0.0"
|
rev: "v4.6.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
|
|
@ -76,11 +76,10 @@ repos:
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
- id: requirements-txt-fixer
|
- id: requirements-txt-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude: \.patch?$
|
|
||||||
|
|
||||||
# Also code format the docs
|
# Also code format the docs
|
||||||
- repo: https://github.com/adamchainz/blacken-docs
|
- repo: https://github.com/adamchainz/blacken-docs
|
||||||
rev: "1.19.1"
|
rev: "1.18.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
|
|
@ -91,11 +90,10 @@ repos:
|
||||||
rev: "v1.5.5"
|
rev: "v1.5.5"
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (^docs/.*|\.patch)?$
|
|
||||||
|
|
||||||
# Avoid directional quotes
|
# Avoid directional quotes
|
||||||
- repo: https://github.com/sirosen/texthooks
|
- repo: https://github.com/sirosen/texthooks
|
||||||
rev: "0.6.8"
|
rev: "0.6.7"
|
||||||
hooks:
|
hooks:
|
||||||
- id: fix-ligatures
|
- id: fix-ligatures
|
||||||
- id: fix-smartquotes
|
- id: fix-smartquotes
|
||||||
|
|
@ -110,7 +108,7 @@ repos:
|
||||||
|
|
||||||
# Checks the manifest for missing files (native support)
|
# Checks the manifest for missing files (native support)
|
||||||
- repo: https://github.com/mgedmin/check-manifest
|
- repo: https://github.com/mgedmin/check-manifest
|
||||||
rev: "0.50"
|
rev: "0.49"
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-manifest
|
- id: check-manifest
|
||||||
# This is a slow hook, so only run this if --hook-stage manual is passed
|
# 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
|
# Use tools/codespell_ignore_lines_from_errors.py
|
||||||
# to rebuild .codespell-ignore-lines
|
# to rebuild .codespell-ignore-lines
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: "v2.4.1"
|
rev: "v2.3.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
exclude: ".supp$"
|
exclude: ".supp$"
|
||||||
|
|
@ -144,14 +142,14 @@ repos:
|
||||||
|
|
||||||
# PyLint has native support - not always usable, but works for us
|
# PyLint has native support - not always usable, but works for us
|
||||||
- repo: https://github.com/PyCQA/pylint
|
- repo: https://github.com/PyCQA/pylint
|
||||||
rev: "v3.3.4"
|
rev: "v3.2.7"
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
files: ^pybind11
|
files: ^pybind11
|
||||||
|
|
||||||
# Check schemas on some of our YAML files
|
# Check schemas on some of our YAML files
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.31.2
|
rev: 0.29.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-github-workflows
|
- id: check-github-workflows
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,16 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.27")
|
||||||
cmake_policy(GET CMP0148 _pybind11_cmp0148)
|
cmake_policy(GET CMP0148 _pybind11_cmp0148)
|
||||||
endif()
|
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)
|
if(_pybind11_cmp0148)
|
||||||
cmake_policy(SET CMP0148 ${_pybind11_cmp0148})
|
cmake_policy(SET CMP0148 ${_pybind11_cmp0148})
|
||||||
|
|
@ -18,7 +27,9 @@ if(_pybind11_cmp0148)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Avoid infinite recursion if tests include this as a subdirectory
|
# 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
|
# Extract project version from source
|
||||||
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/pybind11/detail/common.h"
|
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)
|
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}")
|
message(STATUS "CMake ${CMAKE_VERSION}")
|
||||||
|
|
||||||
if(CMAKE_CXX_STANDARD)
|
if(CMAKE_CXX_STANDARD)
|
||||||
|
|
@ -114,7 +133,8 @@ cmake_dependent_option(
|
||||||
"Install pybind11 headers in Python include directory instead of default installation prefix"
|
"Install pybind11 headers in Python include directory instead of default installation prefix"
|
||||||
OFF "PYBIND11_INSTALL" OFF)
|
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
|
# Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests
|
||||||
# (makes transition easier while we support both modes).
|
# (makes transition easier while we support both modes).
|
||||||
|
|
@ -131,13 +151,10 @@ set(PYBIND11_HEADERS
|
||||||
include/pybind11/detail/common.h
|
include/pybind11/detail/common.h
|
||||||
include/pybind11/detail/cpp_conduit.h
|
include/pybind11/detail/cpp_conduit.h
|
||||||
include/pybind11/detail/descr.h
|
include/pybind11/detail/descr.h
|
||||||
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
|
|
||||||
include/pybind11/detail/init.h
|
include/pybind11/detail/init.h
|
||||||
include/pybind11/detail/internals.h
|
include/pybind11/detail/internals.h
|
||||||
include/pybind11/detail/struct_smart_holder.h
|
|
||||||
include/pybind11/detail/type_caster_base.h
|
include/pybind11/detail/type_caster_base.h
|
||||||
include/pybind11/detail/typeid.h
|
include/pybind11/detail/typeid.h
|
||||||
include/pybind11/detail/using_smart_holder.h
|
|
||||||
include/pybind11/detail/value_and_holder.h
|
include/pybind11/detail/value_and_holder.h
|
||||||
include/pybind11/detail/exception_translation.h
|
include/pybind11/detail/exception_translation.h
|
||||||
include/pybind11/attr.h
|
include/pybind11/attr.h
|
||||||
|
|
@ -146,9 +163,6 @@ set(PYBIND11_HEADERS
|
||||||
include/pybind11/chrono.h
|
include/pybind11/chrono.h
|
||||||
include/pybind11/common.h
|
include/pybind11/common.h
|
||||||
include/pybind11/complex.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/options.h
|
||||||
include/pybind11/eigen.h
|
include/pybind11/eigen.h
|
||||||
include/pybind11/eigen/common.h
|
include/pybind11/eigen/common.h
|
||||||
|
|
@ -167,13 +181,11 @@ set(PYBIND11_HEADERS
|
||||||
include/pybind11/stl.h
|
include/pybind11/stl.h
|
||||||
include/pybind11/stl_bind.h
|
include/pybind11/stl_bind.h
|
||||||
include/pybind11/stl/filesystem.h
|
include/pybind11/stl/filesystem.h
|
||||||
include/pybind11/trampoline_self_life_support.h
|
|
||||||
include/pybind11/type_caster_pyobject_ptr.h
|
include/pybind11/type_caster_pyobject_ptr.h
|
||||||
include/pybind11/typing.h
|
include/pybind11/typing.h)
|
||||||
include/pybind11/warnings.h)
|
|
||||||
|
|
||||||
# Compare with grep and warn if mismatched
|
# Compare with grep and warn if mismatched
|
||||||
if(PYBIND11_MASTER_PROJECT)
|
if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12)
|
||||||
file(
|
file(
|
||||||
GLOB_RECURSE _pybind11_header_check
|
GLOB_RECURSE _pybind11_header_check
|
||||||
LIST_DIRECTORIES false
|
LIST_DIRECTORIES false
|
||||||
|
|
@ -191,7 +203,10 @@ if(PYBIND11_MASTER_PROJECT)
|
||||||
endif()
|
endif()
|
||||||
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
|
# Cache variable so this can be used in parent projects
|
||||||
set(pybind11_INCLUDE_DIR
|
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"
|
tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||||
INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
||||||
|
|
||||||
# CMake natively supports header-only libraries
|
if(CMAKE_VERSION VERSION_LESS 3.14)
|
||||||
write_basic_package_version_file(
|
# Remove CMAKE_SIZEOF_VOID_P from ConfigVersion.cmake since the library does
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
|
# not depend on architecture specific settings or libraries.
|
||||||
VERSION ${PROJECT_VERSION}
|
set(_PYBIND11_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P})
|
||||||
COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT)
|
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(
|
install(
|
||||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
|
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
|
.. figure:: https://github.com/pybind/pybind11/raw/master/docs/pybind11-logo.png
|
||||||
:alt: pybind11 logo
|
: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|
|
|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
|
Think of this library as a tiny self-contained version of Boost.Python
|
||||||
with everything stripped away that isn't relevant for binding
|
with everything stripped away that isn't relevant for binding
|
||||||
generation. Without comments, the core header files only require ~4K
|
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
|
standard library. This compact implementation was possible thanks to
|
||||||
some C++11 language features (specifically: tuples, lambda functions and
|
some C++11 language features (specifically: tuples, lambda functions and
|
||||||
variadic templates). Since its creation, this library has grown beyond
|
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
|
In addition to the core functionality, pybind11 provides some extra
|
||||||
goodies:
|
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).
|
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
|
- It is possible to bind C++11 lambda functions with captured
|
||||||
|
|
@ -134,34 +134,11 @@ About
|
||||||
|
|
||||||
This project was created by `Wenzel
|
This project was created by `Wenzel
|
||||||
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
|
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
|
||||||
improvements to the code were contributed by
|
improvements to the code were contributed by Jonas Adler, Lori A. Burns,
|
||||||
Jonas Adler,
|
Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel
|
||||||
Lori A. Burns,
|
Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov, Johan Mabille, Tomasz Miąsko,
|
||||||
Sylvain Corlay,
|
Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim
|
||||||
Eric Cousineau,
|
Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart.
|
||||||
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.
|
|
||||||
|
|
||||||
We thank Google for a generous financial contribution to the continuous
|
We thank Google for a generous financial contribution to the continuous
|
||||||
integration infrastructure used by this project.
|
integration infrastructure used by this project.
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,35 @@
|
||||||
Custom type casters
|
Custom type casters
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Some applications may prefer custom type casters that convert between existing
|
In very rare cases, applications may require custom type casters that cannot be
|
||||||
Python types and C++ types, similar to the ``list`` ↔ ``std::vector``
|
expressed using the abstractions provided by pybind11, thus requiring raw
|
||||||
and ``dict`` ↔ ``std::map`` conversions which are built into pybind11.
|
Python C API calls. This is fairly advanced usage and should only be pursued by
|
||||||
Implementing custom type casters is fairly advanced usage.
|
experts who are familiar with the intricacies of Python reference counting.
|
||||||
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.
|
|
||||||
|
|
||||||
The following snippets demonstrate how this works for a very simple ``Point2D`` type.
|
The following snippets demonstrate how this works for a very simple ``inty``
|
||||||
We want this type to be convertible to C++ from Python types implementing the
|
type that that should be convertible from Python types that provide a
|
||||||
``Sequence`` protocol and having two elements of type ``float``.
|
``__int__(self)`` method.
|
||||||
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.
|
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
namespace user_space {
|
struct inty { long long_value; };
|
||||||
|
|
||||||
struct Point2D {
|
void print(inty s) {
|
||||||
double x;
|
std::cout << s.long_value << std::endl;
|
||||||
double y;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Point2D negate(const Point2D &point) { return Point2D{-point.x, -point.y}; }
|
The following Python snippet demonstrates the intended usage from the Python side:
|
||||||
|
|
||||||
} // namespace user_space
|
|
||||||
|
|
||||||
|
|
||||||
The following Python snippet demonstrates the intended usage of ``negate`` from the Python side:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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)
|
from example import print
|
||||||
assert point2 == (-1.0, 1.0)
|
|
||||||
|
print(A())
|
||||||
|
|
||||||
To register the necessary conversion routines, it is necessary to add an
|
To register the necessary conversion routines, it is necessary to add an
|
||||||
instantiation of the ``pybind11::detail::type_caster<T>`` template.
|
instantiation of the ``pybind11::detail::type_caster<T>`` template.
|
||||||
|
|
@ -56,57 +38,47 @@ type is explicitly allowed.
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
namespace pybind11 {
|
namespace PYBIND11_NAMESPACE { namespace detail {
|
||||||
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> {
|
* Conversion part 1 (Python->C++): convert a PyObject into a inty
|
||||||
// This macro inserts a lot of boilerplate code and sets the type hint.
|
* instance or return false upon failure. The second argument
|
||||||
// `io_name` is used to specify different type hints for arguments and return values.
|
* indicates whether implicit conversions should be applied.
|
||||||
// The signature of our negate function would then look like:
|
*/
|
||||||
// `negate(Sequence[float]) -> tuple[float, float]`
|
bool load(handle src, bool) {
|
||||||
PYBIND11_TYPE_CASTER(user_space::Point2D, io_name("Sequence[float]", "tuple[float, float]"));
|
/* Extract PyObject from handle */
|
||||||
|
PyObject *source = src.ptr();
|
||||||
// C++ -> Python: convert `Point2D` to `tuple[float, float]`. The second and third arguments
|
/* Try converting into a Python integer value */
|
||||||
// are used to indicate the return value policy and parent object (for
|
PyObject *tmp = PyNumber_Long(source);
|
||||||
// return_value_policy::reference_internal) and are often ignored by custom casters.
|
if (!tmp)
|
||||||
// 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)) {
|
|
||||||
return false;
|
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
|
* Conversion part 2 (C++ -> Python): convert an inty instance into
|
||||||
|
* a Python object. The second and third arguments are used to
|
||||||
// Bind the negate function
|
* indicate the return value policy and parent object (for
|
||||||
PYBIND11_MODULE(docs_advanced_cast_custom, m) { m.def("negate", user_space::negate); }
|
* ``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::
|
.. note::
|
||||||
|
|
||||||
|
|
@ -114,22 +86,8 @@ type is explicitly allowed.
|
||||||
that ``T`` is default-constructible (``value`` is first default constructed
|
that ``T`` is default-constructible (``value`` is first default constructed
|
||||||
and then ``load()`` assigns to it).
|
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::
|
.. warning::
|
||||||
|
|
||||||
When using custom type casters, it's important to declare them consistently
|
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
|
in every compilation unit of the Python extension module. Otherwise,
|
||||||
(`ODR <https://en.cppreference.com/w/cpp/language/definition>`_). Otherwise,
|
|
||||||
undefined behavior can ensue.
|
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::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` |
|
| ``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
|
.. [#] ``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
|
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
|
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
|
common header) preceding any usage of ``std::vector<int>``. Opaque types must
|
||||||
also have a corresponding ``py::class_`` declaration to associate them with a
|
also have a corresponding ``class_`` declaration to associate them with a name
|
||||||
name in Python, and to define a set of available operations, e.g.:
|
in Python, and to define a set of available operations, e.g.:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ helper class that is defined as follows:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
class PyAnimal : public Animal, py::trampoline_self_life_support {
|
class PyAnimal : public Animal {
|
||||||
public:
|
public:
|
||||||
/* Inherit the constructors */
|
/* Inherit the constructors */
|
||||||
using Animal::Animal;
|
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
|
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
|
functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have
|
||||||
a default implementation. There are also two alternate macros
|
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
|
:emphasize-lines: 2,3
|
||||||
|
|
||||||
PYBIND11_MODULE(example, m) {
|
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(py::init<>())
|
||||||
.def("go", &Animal::go);
|
.def("go", &Animal::go);
|
||||||
|
|
||||||
py::class_<Dog, Animal, py::smart_holder>(m, "Dog")
|
py::class_<Dog, Animal>(m, "Dog")
|
||||||
.def(py::init<>());
|
.def(py::init<>());
|
||||||
|
|
||||||
m.def("call_go", &call_go);
|
m.def("call_go", &call_go);
|
||||||
}
|
}
|
||||||
|
|
||||||
Importantly, pybind11 is made aware of the trampoline helper class by
|
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
|
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
|
order of template types does not matter). Following this, we are able to
|
||||||
define a constructor as usual.
|
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
|
.. code-block:: cpp
|
||||||
:emphasize-lines: 3
|
:emphasize-lines: 3
|
||||||
|
|
||||||
py::class_<Animal, PyAnimal /* <--- trampoline */, py::smart_holder>(m, "Animal");
|
py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal");
|
||||||
.def(py::init<>())
|
.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
|
Note, however, that the above is sufficient for allowing python classes to
|
||||||
extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the
|
extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the
|
||||||
|
|
@ -256,13 +244,13 @@ override the ``name()`` method):
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
class PyAnimal : public Animal, py::trampoline_self_life_support {
|
class PyAnimal : public Animal {
|
||||||
public:
|
public:
|
||||||
using Animal::Animal; // Inherit constructors
|
using Animal::Animal; // Inherit constructors
|
||||||
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Animal, go, n_times); }
|
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, ); }
|
std::string name() override { PYBIND11_OVERRIDE(std::string, Animal, name, ); }
|
||||||
};
|
};
|
||||||
class PyDog : public Dog, py::trampoline_self_life_support {
|
class PyDog : public Dog {
|
||||||
public:
|
public:
|
||||||
using Dog::Dog; // Inherit constructors
|
using Dog::Dog; // Inherit constructors
|
||||||
std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, Dog, go, n_times); }
|
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
|
.. code-block:: cpp
|
||||||
|
|
||||||
class Husky : public Dog {};
|
class Husky : public Dog {};
|
||||||
class PyHusky : public Husky, py::trampoline_self_life_support {
|
class PyHusky : public Husky {
|
||||||
public:
|
public:
|
||||||
using Husky::Husky; // Inherit constructors
|
using Husky::Husky; // Inherit constructors
|
||||||
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Husky, go, n_times); }
|
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Husky, go, n_times); }
|
||||||
|
|
@ -299,15 +287,13 @@ follows:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
template <class AnimalBase = Animal>
|
template <class AnimalBase = Animal> class PyAnimal : public AnimalBase {
|
||||||
class PyAnimal : public AnimalBase, py::trampoline_self_life_support {
|
|
||||||
public:
|
public:
|
||||||
using AnimalBase::AnimalBase; // Inherit constructors
|
using AnimalBase::AnimalBase; // Inherit constructors
|
||||||
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); }
|
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, ); }
|
std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); }
|
||||||
};
|
};
|
||||||
template <class DogBase = Dog>
|
template <class DogBase = Dog> class PyDog : public PyAnimal<DogBase> {
|
||||||
class PyDog : public PyAnimal<DogBase>, py::trampoline_self_life_support {
|
|
||||||
public:
|
public:
|
||||||
using PyAnimal<DogBase>::PyAnimal; // Inherit constructors
|
using PyAnimal<DogBase>::PyAnimal; // Inherit constructors
|
||||||
// Override PyAnimal's pure virtual go() with a non-pure one:
|
// 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
|
.. code-block:: cpp
|
||||||
|
|
||||||
py::class_<Animal, PyAnimal<>, py::smart_holder> animal(m, "Animal");
|
py::class_<Animal, PyAnimal<>> animal(m, "Animal");
|
||||||
py::class_<Dog, Animal, PyDog<>, py::smart_holder> dog(m, "Dog");
|
py::class_<Dog, Animal, PyDog<>> dog(m, "Dog");
|
||||||
py::class_<Husky, Dog, PyDog<Husky>, py::smart_holder> husky(m, "Husky");
|
py::class_<Husky, Dog, PyDog<Husky>> husky(m, "Husky");
|
||||||
// ... add animal, dog, husky definitions
|
// ... add animal, dog, husky definitions
|
||||||
|
|
||||||
Note that ``Husky`` did not require a dedicated trampoline template class at
|
Note that ``Husky`` did not require a dedicated trampoline template class at
|
||||||
|
|
@ -513,12 +499,12 @@ an alias:
|
||||||
// ...
|
// ...
|
||||||
virtual ~Example() = default;
|
virtual ~Example() = default;
|
||||||
};
|
};
|
||||||
class PyExample : public Example, py::trampoline_self_life_support {
|
class PyExample : public Example {
|
||||||
public:
|
public:
|
||||||
using Example::Example;
|
using Example::Example;
|
||||||
PyExample(Example &&base) : Example(std::move(base)) {}
|
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
|
// Returns an Example pointer. If a PyExample is needed, the Example
|
||||||
// instance will be moved via the extra constructor in PyExample, above.
|
// instance will be moved via the extra constructor in PyExample, above.
|
||||||
.def(py::init([]() { return new Example(); }))
|
.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
|
is responsible for managing the lifetime of instances will reference the
|
||||||
destructor even if no deallocations ever take place. In order to expose classes
|
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
|
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
|
type via a holder type argument to ``class_``. Pybind11 provides a helper class
|
||||||
class ``py::nodelete`` that disables any destructor invocations. In this case,
|
``py::nodelete`` that disables any destructor invocations. In this case, it is
|
||||||
it is crucial that instances are deallocated on the C++ side to avoid memory
|
crucial that instances are deallocated on the C++ side to avoid memory leaks.
|
||||||
leaks.
|
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. 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
|
always use the latest available version. Beware: failure to follow these
|
||||||
instructions will cause important pybind11 memory allocation routines to be
|
instructions will cause important pybind11 memory allocation routines to be
|
||||||
skipped during unpickling, which will likely lead to memory corruption
|
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::
|
.. seealso::
|
||||||
|
|
||||||
|
|
@ -886,7 +872,7 @@ Multiple Inheritance
|
||||||
|
|
||||||
pybind11 can create bindings for types that derive from multiple base types
|
pybind11 can create bindings for types that derive from multiple base types
|
||||||
(aka. *multiple inheritance*). To do so, specify all bases in the template
|
(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
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
|
@ -961,11 +947,11 @@ because of conflicting definitions on the external type:
|
||||||
// dogs.cpp
|
// dogs.cpp
|
||||||
|
|
||||||
// Binding for external library class:
|
// Binding for external library class:
|
||||||
py::class_<pets::Pet>(m, "Pet")
|
py::class<pets::Pet>(m, "Pet")
|
||||||
.def("name", &pets::Pet::name);
|
.def("name", &pets::Pet::name);
|
||||||
|
|
||||||
// Binding for local extension class:
|
// Binding for local extension class:
|
||||||
py::class_<Dog, pets::Pet>(m, "Dog")
|
py::class<Dog, pets::Pet>(m, "Dog")
|
||||||
.def(py::init<std::string>());
|
.def(py::init<std::string>());
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. 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.
|
// cats.cpp, in a completely separate project from the above dogs.cpp.
|
||||||
|
|
||||||
// Binding for external library class:
|
// Binding for external library class:
|
||||||
py::class_<pets::Pet>(m, "Pet")
|
py::class<pets::Pet>(m, "Pet")
|
||||||
.def("get_name", &pets::Pet::name);
|
.def("get_name", &pets::Pet::name);
|
||||||
|
|
||||||
// Binding for local extending class:
|
// Binding for local extending class:
|
||||||
py::class_<Cat, pets::Pet>(m, "Cat")
|
py::class<Cat, pets::Pet>(m, "Cat")
|
||||||
.def(py::init<std::string>());
|
.def(py::init<std::string>());
|
||||||
|
|
||||||
.. code-block:: pycon
|
.. code-block:: pycon
|
||||||
|
|
@ -995,13 +981,13 @@ the ``py::class_`` constructor:
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
// Pet binding in dogs.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);
|
.def("name", &pets::Pet::name);
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
// Pet binding in cats.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);
|
.def("get_name", &pets::Pet::name);
|
||||||
|
|
||||||
This makes the Python-side ``dogs.Pet`` and ``cats.Pet`` into distinct classes,
|
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; }
|
virtual int foo() const { return 42; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Trampoline : public A, py::trampoline_self_life_support {
|
class Trampoline : public A {
|
||||||
public:
|
public:
|
||||||
int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); }
|
int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); }
|
||||||
};
|
};
|
||||||
|
|
@ -1129,7 +1115,7 @@ described trampoline:
|
||||||
using A::foo;
|
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`!
|
.def("foo", &Publicist::foo); // <-- `Publicist` here, not `Trampoline`!
|
||||||
|
|
||||||
Binding final classes
|
Binding final classes
|
||||||
|
|
@ -1210,7 +1196,7 @@ but once again each instantiation must be explicitly specified:
|
||||||
T fn(V v);
|
T fn(V v);
|
||||||
};
|
};
|
||||||
|
|
||||||
py::class_<MyClass<int>>(m, "MyClassT")
|
py::class<MyClass<int>>(m, "MyClassT")
|
||||||
.def("fn", &MyClass<int>::fn<std::string>);
|
.def("fn", &MyClass<int>::fn<std::string>);
|
||||||
|
|
||||||
Custom automatic downcasters
|
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
|
.. code-block:: cmake
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15...3.30)
|
cmake_minimum_required(VERSION 3.5...3.29)
|
||||||
project(example)
|
project(example)
|
||||||
|
|
||||||
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`
|
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.
|
the C++ runtime calls ``std::terminate()`` to abort immediately.
|
||||||
|
|
||||||
Similarly, Python exceptions raised in a class's ``__del__`` method do not
|
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>`_
|
<https://docs.python.org/3/library/sys.html#sys.unraisablehook>`_
|
||||||
and an auditing event is logged.
|
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++ |
|
| | 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. |
|
| | 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 |
|
| :enum:`return_value_policy::reference_internal` | Indicates that the lifetime of the return value is tied to the lifetime |
|
||||||
| | (the implicit ``this``, or ``self`` argument of the called method or |
|
| | of a parent object, namely the implicit ``this``, or ``self`` argument of |
|
||||||
| | property) is kept alive for at least the lifespan of the return value. |
|
| | the called method or property. Internally, this policy works just like |
|
||||||
| | **Otherwise this policy falls back to :enum:`return_value_policy::move` |
|
|
||||||
| | (see #5528).** Internally, this policy works just like |
|
|
||||||
| | :enum:`return_value_policy::reference` but additionally applies a |
|
| | :enum:`return_value_policy::reference` but additionally applies a |
|
||||||
| | ``keep_alive<0, 1>`` *call policy* (described in the next section) that |
|
| | ``keep_alive<0, 1>`` *call policy* (described in the next section) that |
|
||||||
| | prevents the parent object from being garbage collected as long as the |
|
| | 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.
|
back into Python.
|
||||||
|
|
||||||
When writing C++ code that is called from other C++ code, if that code accesses
|
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
|
Python state, it must explicitly acquire and release the GIL.
|
||||||
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
|
|
||||||
|
|
||||||
The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be
|
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++
|
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
|
.. code-block:: cpp
|
||||||
:emphasize-lines: 8,30,31
|
:emphasize-lines: 8,30,31
|
||||||
|
|
||||||
class PyAnimal : public Animal, py::trampoline_self_life_support {
|
class PyAnimal : public Animal {
|
||||||
public:
|
public:
|
||||||
/* Inherit the constructors */
|
/* Inherit the constructors */
|
||||||
using Animal::Animal;
|
using Animal::Animal;
|
||||||
|
|
@ -98,12 +94,12 @@ could be realized as follows (important changes highlighted):
|
||||||
};
|
};
|
||||||
|
|
||||||
PYBIND11_MODULE(example, m) {
|
PYBIND11_MODULE(example, m) {
|
||||||
py::class_<Animal, PyAnimal, py::smart_holder> animal(m, "Animal");
|
py::class_<Animal, PyAnimal> animal(m, "Animal");
|
||||||
animal
|
animal
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def("go", &Animal::go);
|
.def("go", &Animal::go);
|
||||||
|
|
||||||
py::class_<Dog, py::smart_holder>(m, "Dog", animal)
|
py::class_<Dog>(m, "Dog", animal)
|
||||||
.def(py::init<>());
|
.def(py::init<>());
|
||||||
|
|
||||||
m.def("call_go", [](Animal *animal) -> std::string {
|
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
|
destructors can sometimes get invoked in weird and unexpected circumstances as a result
|
||||||
of exceptions.
|
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
|
- 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
|
within pybind11 that will throw exceptions on certain GIL handling errors
|
||||||
(reference counting operations).
|
(reference counting operations).
|
||||||
|
|
@ -188,7 +181,7 @@ from Section :ref:`inheritance`.
|
||||||
Suppose now that ``Pet`` bindings are defined in a module named ``basic``,
|
Suppose now that ``Pet`` bindings are defined in a module named ``basic``,
|
||||||
whereas the ``Dog`` bindings are defined somewhere else. The challenge is of
|
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
|
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:
|
However, it can be acquired as follows:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
@ -200,7 +193,7 @@ However, it can be acquired as follows:
|
||||||
.def("bark", &Dog::bark);
|
.def("bark", &Dog::bark);
|
||||||
|
|
||||||
Alternatively, you can specify the base class as a template parameter option to
|
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``
|
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``
|
function once to ensure that the pybind11 binding code of the module ``basic``
|
||||||
has been executed:
|
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
|
std::unique_ptr
|
||||||
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>``.
|
|
||||||
|
|
||||||
.. note::
|
Given a class ``Example`` with Python bindings, it's possible to return
|
||||||
|
instances wrapped in C++11 unique pointers, like so
|
||||||
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>``:
|
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. 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);
|
m.def("create_example", &create_example);
|
||||||
|
|
||||||
However, this will fail with ``py::class_<Example>`` (but works with
|
In other words, there is nothing special that needs to be done. While returning
|
||||||
``py::class_<Example, py::smart_holder>``):
|
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
|
.. code-block:: cpp
|
||||||
|
|
||||||
void do_something_with_example(std::unique_ptr<Example> ex) { ... }
|
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
|
std::shared_ptr
|
||||||
<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.
|
|
||||||
|
|
||||||
|
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 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.
|
||||||
It is possible to use ``std::shared_ptr`` as the holder, for example:
|
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. 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
|
One potential stumbling block when using holder types is that they need to be
|
||||||
single holder type, ``std::unique_ptr<T>`` cannot even be passed from C++
|
applied consistently. Can you guess what's broken about the following binding
|
||||||
to Python. This will become apparent only at runtime, often through a
|
code?
|
||||||
segmentation fault.
|
|
||||||
|
|
||||||
* Similar to the ``std::unique_ptr`` holder, the handling of base-and-derived
|
.. code-block:: cpp
|
||||||
classes involves a ``reinterpret_cast`` that has strictly speaking undefined
|
|
||||||
behavior, although it works as expected in most situations.
|
|
||||||
|
|
||||||
|
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:
|
.. _smart_pointers:
|
||||||
|
|
||||||
Custom smart pointers
|
Custom smart pointers
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
For custom smart pointers (e.g. ``c10::intrusive_ptr`` in pytorch), transparent
|
pybind11 supports ``std::unique_ptr`` and ``std::shared_ptr`` right out of the
|
||||||
conversions can be enabled using a macro invocation similar to the following.
|
box. For any other custom smart pointer, transparent conversions can be enabled
|
||||||
It must be declared at the top namespace level before any binding code:
|
using a macro invocation similar to the following. It must be declared at the
|
||||||
|
top namespace level before any binding code:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
|
@ -164,70 +167,8 @@ specialized:
|
||||||
The above specialization informs pybind11 that the custom ``SmartPtr`` class
|
The above specialization informs pybind11 that the custom ``SmartPtr`` class
|
||||||
provides ``.get()`` functionality via ``.getPointer()``.
|
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::
|
.. seealso::
|
||||||
|
|
||||||
The file :file:`tests/test_smart_ptr.cpp` contains a complete example
|
The file :file:`tests/test_smart_ptr.cpp` contains a complete example
|
||||||
that demonstrates how to work with custom reference-counting holder types
|
that demonstrates how to work with custom reference-counting holder types
|
||||||
in more detail.
|
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
|
.. 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::
|
.. note::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ def generate_dummy_code_boost(nclasses=10):
|
||||||
decl += "\n"
|
decl += "\n"
|
||||||
|
|
||||||
for cl in range(nclasses):
|
for cl in range(nclasses):
|
||||||
decl += f"class cl{cl:03} {{\n"
|
decl += "class cl%03i {\n" % cl
|
||||||
decl += "public:\n"
|
decl += "public:\n"
|
||||||
bindings += f' py::class_<cl{cl:03}>("cl{cl:03}")\n'
|
bindings += f' py::class_<cl{cl:03}>("cl{cl:03}")\n'
|
||||||
for fn in range(nfns):
|
for fn in range(nfns):
|
||||||
|
|
@ -85,5 +85,5 @@ for codegen in [generate_dummy_code_pybind11, generate_dummy_code_boost]:
|
||||||
n2 = dt.datetime.now()
|
n2 = dt.datetime.now()
|
||||||
elapsed = (n2 - n1).total_seconds()
|
elapsed = (n2 - n1).total_seconds()
|
||||||
size = os.stat("test.so").st_size
|
size = os.stat("test.so").st_size
|
||||||
print(f" {{{nclasses * nfns}, {elapsed:.6f}, {size}}},")
|
print(" {%i, %f, %i}," % (nclasses * nfns, elapsed, size))
|
||||||
print("}")
|
print("}")
|
||||||
|
|
|
||||||
|
|
@ -34,18 +34,11 @@ The binding code for ``Pet`` looks as follows:
|
||||||
.def("getName", &Pet::getName);
|
.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
|
structure. :func:`init` is a convenience function that takes the types of a
|
||||||
constructor's parameters as template arguments and wraps the corresponding
|
constructor's parameters as template arguments and wraps the corresponding
|
||||||
constructor (see the :ref:`custom_constructors` section for details).
|
constructor (see the :ref:`custom_constructors` section for details). An
|
||||||
|
interactive Python session demonstrating this example is shown below:
|
||||||
.. 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:
|
|
||||||
|
|
||||||
.. code-block:: pycon
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
|
@ -265,7 +258,7 @@ inheritance relationship:
|
||||||
|
|
||||||
There are two different ways of indicating a hierarchical relationship to
|
There are two different ways of indicating a hierarchical relationship to
|
||||||
pybind11: the first specifies the C++ base class as an extra template
|
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
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
|
@ -279,7 +272,7 @@ parameter of the ``py::class_``:
|
||||||
.def("bark", &Dog::bark);
|
.def("bark", &Dog::bark);
|
||||||
|
|
||||||
Alternatively, we can also assign a name to the previously bound ``Pet``
|
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
|
.. 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
|
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
|
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
|
into the parent scope, which should be skipped for newer C++11-style strongly
|
||||||
typed enums.
|
typed enums.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ A Python extension module can be created with just a few lines of code:
|
||||||
|
|
||||||
.. code-block:: cmake
|
.. code-block:: cmake
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15...3.30)
|
cmake_minimum_required(VERSION 3.15...3.29)
|
||||||
project(example LANGUAGES CXX)
|
project(example LANGUAGES CXX)
|
||||||
|
|
||||||
set(PYBIND11_FINDPYTHON ON)
|
set(PYBIND11_FINDPYTHON ON)
|
||||||
|
|
@ -319,11 +319,11 @@ Building with CMake
|
||||||
|
|
||||||
For C++ codebases that have an existing CMake-based build system, a Python
|
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
|
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
|
the module section. Pybind11 currently supports a lower minimum if you don't
|
||||||
aware that CMake 3.27 removed the old mechanism, so pybind11 will automatically
|
use the modern FindPython, though be aware that CMake 3.27 removed the old
|
||||||
switch if the old mechanism is not available. Please opt into the new mechanism
|
mechanism, so pybind11 will automatically switch if the old mechanism is not
|
||||||
if at all possible. Our default may change in future versions. This is the
|
available. Please opt into the new mechanism if at all possible. Our default
|
||||||
minimum required:
|
may change in future versions. This is the minimum required:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -333,9 +333,6 @@ minimum required:
|
||||||
.. versionchanged:: 2.11
|
.. versionchanged:: 2.11
|
||||||
CMake 3.5+ is required.
|
CMake 3.5+ is required.
|
||||||
|
|
||||||
.. versionchanged:: 2.14
|
|
||||||
CMake 3.15+ is required.
|
|
||||||
|
|
||||||
|
|
||||||
Further information can be found at :doc:`cmake/index`.
|
Further information can be found at :doc:`cmake/index`.
|
||||||
|
|
||||||
|
|
@ -429,7 +426,7 @@ with ``PYTHON_EXECUTABLE``. For example:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
cmake -DPYBIND11_PYTHON_VERSION=3.8 ..
|
cmake -DPYBIND11_PYTHON_VERSION=3.7 ..
|
||||||
|
|
||||||
# Another method:
|
# Another method:
|
||||||
cmake -DPYTHON_EXECUTABLE=/path/to/python ..
|
cmake -DPYTHON_EXECUTABLE=/path/to/python ..
|
||||||
|
|
@ -447,7 +444,7 @@ See the `Config file`_ docstring for details of relevant CMake variables.
|
||||||
|
|
||||||
.. code-block:: cmake
|
.. code-block:: cmake
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15...3.30)
|
cmake_minimum_required(VERSION 3.4...3.18)
|
||||||
project(example LANGUAGES CXX)
|
project(example LANGUAGES CXX)
|
||||||
|
|
||||||
find_package(pybind11 REQUIRED)
|
find_package(pybind11 REQUIRED)
|
||||||
|
|
@ -486,16 +483,17 @@ can refer to the same [cmake_example]_ repository for a full sample project
|
||||||
FindPython mode
|
FindPython mode
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Modern CMake (3.18.2+ ideal) added a new module called FindPython that had a
|
CMake 3.12+ (3.15+ recommended, 3.18.2+ ideal) added a new module called
|
||||||
highly improved search algorithm and modern targets and tools. If you use
|
FindPython that had a highly improved search algorithm and modern targets
|
||||||
FindPython, pybind11 will detect this and use the existing targets instead:
|
and tools. If you use FindPython, pybind11 will detect this and use the
|
||||||
|
existing targets instead:
|
||||||
|
|
||||||
.. code-block:: cmake
|
.. code-block:: cmake
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15...3.30)
|
cmake_minimum_required(VERSION 3.15...3.22)
|
||||||
project(example LANGUAGES CXX)
|
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)
|
find_package(pybind11 CONFIG REQUIRED)
|
||||||
# or add_subdirectory(pybind11)
|
# or add_subdirectory(pybind11)
|
||||||
|
|
||||||
|
|
@ -543,7 +541,7 @@ available in all modes. The targets provided are:
|
||||||
Just the "linking" part of pybind11:module
|
Just the "linking" part of pybind11:module
|
||||||
|
|
||||||
``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``
|
``pybind11::embed``
|
||||||
Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Python`` (FindPython) or Python libs
|
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
|
.. code-block:: cmake
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15...3.30)
|
cmake_minimum_required(VERSION 3.5...3.29)
|
||||||
project(example LANGUAGES CXX)
|
project(example LANGUAGES CXX)
|
||||||
|
|
||||||
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
||||||
|
|
@ -628,7 +626,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
|
||||||
|
|
||||||
.. code-block:: cmake
|
.. code-block:: cmake
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.15...3.30)
|
cmake_minimum_required(VERSION 3.5...3.29)
|
||||||
project(example LANGUAGES CXX)
|
project(example LANGUAGES CXX)
|
||||||
|
|
||||||
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
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.)
|
your prefer.)
|
||||||
|
|
||||||
You can alternatively try ``-DPYBIND11_FINDPYTHON=ON``, which will activate the
|
You can alternatively try ``-DPYBIND11_FINDPYTHON=ON``, which will activate the
|
||||||
new CMake FindPython support instead of pybind11's custom search. Newer CMake,
|
new CMake FindPython support instead of pybind11's custom search. Requires
|
||||||
like, 3.18.2+, is recommended. You can set this in your ``CMakeLists.txt``
|
CMake 3.12+, and 3.15+ or 3.18.2+ are even better. You can set this in your
|
||||||
before adding or finding pybind11, as well.
|
``CMakeLists.txt`` before adding or finding pybind11, as well.
|
||||||
|
|
||||||
Inconsistent detection of Python version in CMake and pybind11
|
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
|
from CMake and rely on pybind11 in detecting Python version. If this is not
|
||||||
possible, the CMake machinery should be called *before* including pybind11.
|
possible, the CMake machinery should be called *before* including pybind11.
|
||||||
2. Set ``PYBIND11_FINDPYTHON`` to ``True`` or use ``find_package(Python
|
2. Set ``PYBIND11_FINDPYTHON`` to ``True`` or use ``find_package(Python
|
||||||
COMPONENTS Interpreter Development)`` on modern CMake ( 3.18.2+ best).
|
COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better,
|
||||||
Pybind11 in these cases uses the new CMake FindPython instead of the old,
|
3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead
|
||||||
deprecated search tools, and these modules are much better at finding the
|
of the old, deprecated search tools, and these modules are much better at
|
||||||
correct Python. If FindPythonLibs/Interp are not available (CMake 3.27+),
|
finding the correct Python. If FindPythonLibs/Interp are not available
|
||||||
then this will be ignored and FindPython will be used.
|
(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.
|
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
|
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
|
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:
|
.. _extras:
|
||||||
|
|
||||||
Passing extra arguments to ``def`` or ``py::class_``
|
Passing extra arguments to ``def`` or ``class_``
|
||||||
====================================================
|
================================================
|
||||||
|
|
||||||
.. doxygengroup:: annotations
|
.. doxygengroup:: annotations
|
||||||
:members:
|
:members:
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,9 @@ imagesize==1.4.1 \
|
||||||
--hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
|
--hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
|
||||||
--hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
|
--hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
|
||||||
# via sphinx
|
# via sphinx
|
||||||
jinja2==3.1.6 \
|
jinja2==3.1.4 \
|
||||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
--hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \
|
||||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
--hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d
|
||||||
# via sphinx
|
# via sphinx
|
||||||
markupsafe==2.1.5 \
|
markupsafe==2.1.5 \
|
||||||
--hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \
|
--hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,7 @@ changes are that:
|
||||||
function is not available anymore.
|
function is not available anymore.
|
||||||
|
|
||||||
Due to NumPy changes, you may experience difficulties updating to NumPy 2.
|
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>`_
|
Please see the [NumPy 2 migration guide](https://numpy.org/devdocs/numpy_2_0_migration_guide.html) for details.
|
||||||
for details.
|
|
||||||
For example, a more direct change could be that the default integer ``"int_"``
|
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).
|
(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
|
/// Annotation which enables the buffer protocol for a type
|
||||||
struct buffer_protocol {};
|
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
|
/// Annotation which requests that a special metaclass is created for a type
|
||||||
struct metaclass {
|
struct metaclass {
|
||||||
handle value;
|
handle value;
|
||||||
|
|
@ -276,7 +272,7 @@ struct function_record {
|
||||||
struct type_record {
|
struct type_record {
|
||||||
PYBIND11_NOINLINE type_record()
|
PYBIND11_NOINLINE type_record()
|
||||||
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
|
: 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 to the parent scope
|
||||||
handle scope;
|
handle scope;
|
||||||
|
|
@ -326,17 +322,15 @@ struct type_record {
|
||||||
/// Does the class implement the buffer protocol?
|
/// Does the class implement the buffer protocol?
|
||||||
bool buffer_protocol : 1;
|
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?
|
/// Is the class definition local to the module shared object?
|
||||||
bool module_local : 1;
|
bool module_local : 1;
|
||||||
|
|
||||||
/// Is the class inheritable from python classes?
|
/// Is the class inheritable from python classes?
|
||||||
bool is_final : 1;
|
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 *) ) {
|
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) {
|
||||||
auto *base_info = detail::get_type_info(base, false);
|
auto *base_info = detail::get_type_info(base, false);
|
||||||
if (!base_info) {
|
if (!base_info) {
|
||||||
|
|
@ -346,22 +340,18 @@ struct type_record {
|
||||||
+ "\" referenced unknown base type \"" + tname + "\"");
|
+ "\" referenced unknown base type \"" + tname + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks.
|
if (default_holder != base_info->default_holder) {
|
||||||
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) {
|
|
||||||
std::string tname(base.name());
|
std::string tname(base.name());
|
||||||
detail::clean_type_id(tname);
|
detail::clean_type_id(tname);
|
||||||
pybind11_fail("generic_type: type \"" + std::string(name) + "\" "
|
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 + "\" "
|
+ " 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);
|
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;
|
dynamic_attr |= base_info->type->tp_dictoffset != 0;
|
||||||
#else
|
#else
|
||||||
dynamic_attr |= (base_info->type->tp_flags & Py_TPFLAGS_MANAGED_DICT) != 0;
|
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; }
|
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
|
/// Process a 'prepend' attribute, putting this at the beginning of the overload chain
|
||||||
template <>
|
template <>
|
||||||
struct process_attribute<prepend> : process_attribute_default<prepend> {
|
struct process_attribute<prepend> : process_attribute_default<prepend> {
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ public:
|
||||||
} else {
|
} else {
|
||||||
handle src_or_index = src;
|
handle src_or_index = src;
|
||||||
// PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls.
|
// 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;
|
object index;
|
||||||
if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr())
|
if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr())
|
||||||
index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
|
index = reinterpret_steal<object>(PyNumber_Index(src.ptr()));
|
||||||
|
|
@ -343,7 +343,7 @@ public:
|
||||||
#else
|
#else
|
||||||
// Alternate approach for CPython: this does the same as the above, but optimized
|
// 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.
|
// 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)) {
|
if (PYBIND11_NB_BOOL(tp_as_number)) {
|
||||||
res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr());
|
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(); }
|
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.
|
/// Type caster for holder types like std::shared_ptr, etc.
|
||||||
/// The SFINAE hook is provided to help work around the current lack of support
|
/// The SFINAE hook is provided to help work around the current lack of support
|
||||||
/// for smart-pointer interoperability. Please consider it an implementation
|
/// for smart-pointer interoperability. Please consider it an implementation
|
||||||
|
|
@ -790,10 +789,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
friend class type_caster_generic;
|
friend class type_caster_generic;
|
||||||
void check_holder_compat() {
|
void check_holder_compat() {
|
||||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refine holder compatibility checks.
|
if (typeinfo->default_holder) {
|
||||||
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");
|
throw cast_error("Unable to load a custom holder type from a default-holder instance");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -839,144 +835,10 @@ protected:
|
||||||
holder_type holder;
|
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
|
/// Specialize for the common std::shared_ptr, so users don't need to
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<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.
|
/// Type caster for holder types like std::unique_ptr.
|
||||||
/// Please consider the SFINAE hook an implementation detail, as explained
|
/// Please consider the SFINAE hook an implementation detail, as explained
|
||||||
/// in the comment for the copyable_holder_caster.
|
/// 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;
|
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>
|
template <typename type, typename deleter>
|
||||||
class type_caster<std::unique_ptr<type, deleter>>
|
class type_caster<std::unique_ptr<type, deleter>>
|
||||||
: public move_only_holder_caster<type, 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>,
|
copyable_holder_caster<type, holder_type>,
|
||||||
move_only_holder_caster<type, holder_type>>;
|
move_only_holder_caster<type, holder_type>>;
|
||||||
|
|
||||||
template <bool Value = false>
|
template <typename T, bool Value = false>
|
||||||
struct always_construct_holder_value {
|
struct always_construct_holder {
|
||||||
static constexpr bool value = Value;
|
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)
|
/// Create a specialization for custom holder types (silently ignores std::shared_ptr)
|
||||||
#define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \
|
#define PYBIND11_DECLARE_HOLDER_TYPE(type, holder_type, ...) \
|
||||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \
|
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \
|
||||||
namespace detail { \
|
namespace detail { \
|
||||||
template <typename type> \
|
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> \
|
template <typename type> \
|
||||||
class type_caster<holder_type, enable_if_t<!is_shared_ptr<holder_type>::value>> \
|
class type_caster<holder_type, enable_if_t<!is_shared_ptr<holder_type>::value>> \
|
||||||
: public type_caster_holder<type, holder_type> {}; \
|
: 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>
|
template <typename base, typename holder>
|
||||||
struct is_holder_type
|
struct is_holder_type
|
||||||
: std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
|
: std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
|
||||||
|
// Specialization for always-supported unique_ptr holders:
|
||||||
// Specializations for always-supported holders:
|
|
||||||
template <typename base, typename deleter>
|
template <typename base, typename deleter>
|
||||||
struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
|
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
|
#ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888
|
||||||
|
|
||||||
// This leads to compilation errors if a specialization is missing.
|
// This leads to compilation errors if a specialization is missing.
|
||||||
|
|
@ -1293,18 +1012,10 @@ template <>
|
||||||
struct handle_type_name<args> {
|
struct handle_type_name<args> {
|
||||||
static constexpr auto name = const_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 <>
|
template <>
|
||||||
struct handle_type_name<kwargs> {
|
struct handle_type_name<kwargs> {
|
||||||
static constexpr auto name = const_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 <>
|
template <>
|
||||||
struct handle_type_name<obj_attr_accessor> {
|
struct handle_type_name<obj_attr_accessor> {
|
||||||
static constexpr auto name = const_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));
|
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
|
// Placeholder type for the unneeded (and dead code) static variable in the
|
||||||
// PYBIND11_OVERRIDE_OVERRIDE macro
|
// PYBIND11_OVERRIDE_OVERRIDE macro
|
||||||
struct override_unused {};
|
struct override_unused {};
|
||||||
|
|
@ -1817,7 +1503,7 @@ struct kw_only {};
|
||||||
|
|
||||||
/// \ingroup annotations
|
/// \ingroup annotations
|
||||||
/// Annotation indicating that all previous arguments are positional-only; the is the equivalent of
|
/// 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 {};
|
struct pos_only {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
@ -1878,24 +1564,15 @@ struct function_call {
|
||||||
handle init_self;
|
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
|
/// Helper class which loads arguments for C++ functions called from Python
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
class argument_loader {
|
class argument_loader {
|
||||||
using indices = make_index_sequence<sizeof...(Args)>;
|
using indices = make_index_sequence<sizeof...(Args)>;
|
||||||
|
|
||||||
template <typename Arg>
|
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>
|
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:
|
// Get kwargs argument position, or -1 if not present:
|
||||||
static constexpr auto kwargs_pos = constexpr_last<argument_is_kwargs, Args...>();
|
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) {
|
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); });
|
with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); });
|
||||||
return true; // unused, but gives the same signature as the deregister func
|
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()) {
|
if (instance->owned || v_h.holder_constructed()) {
|
||||||
v_h.type->dealloc(v_h);
|
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:
|
// Deallocate the value/holder layout internals:
|
||||||
|
|
@ -494,9 +468,19 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) {
|
||||||
|
|
||||||
type->tp_free(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)
|
// This was not needed before Python 3.8 (Python issue 35810)
|
||||||
// https://github.com/pybind/pybind11/issues/1946
|
// https://github.com/pybind/pybind11/issues/1946
|
||||||
Py_DECREF(type);
|
Py_DECREF(type);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string error_string();
|
std::string error_string();
|
||||||
|
|
@ -576,7 +560,7 @@ extern "C" inline int pybind11_clear(PyObject *self) {
|
||||||
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
|
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
|
||||||
auto *type = &heap_type->ht_type;
|
auto *type = &heap_type->ht_type;
|
||||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
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_dictoffset = type->tp_basicsize; // place dict at the end
|
||||||
type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
|
type->tp_basicsize += (ssize_t) sizeof(PyObject *); // and allocate enough space for it
|
||||||
#else
|
#else
|
||||||
|
|
@ -609,9 +593,9 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
std::memset(view, 0, sizeof(Py_buffer));
|
std::memset(view, 0, sizeof(Py_buffer));
|
||||||
std::unique_ptr<buffer_info> info = nullptr;
|
buffer_info *info = nullptr;
|
||||||
try {
|
try {
|
||||||
info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data));
|
info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
try_translate_exceptions();
|
try_translate_exceptions();
|
||||||
raise_from(PyExc_BufferError, "Error getting buffer");
|
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) {
|
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
|
||||||
|
delete info;
|
||||||
// view->obj = nullptr; // Was just memset to 0, so not necessary
|
// view->obj = nullptr; // Was just memset to 0, so not necessary
|
||||||
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
|
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
view->obj = obj;
|
||||||
// Fill in all the information, and then downgrade as requested by the caller, or raise an
|
view->ndim = 1;
|
||||||
// error if that's not possible.
|
view->internal = info;
|
||||||
|
view->buf = info->ptr;
|
||||||
view->itemsize = info->itemsize;
|
view->itemsize = info->itemsize;
|
||||||
view->len = view->itemsize;
|
view->len = view->itemsize;
|
||||||
for (auto s : info->shape) {
|
for (auto s : info->shape) {
|
||||||
view->len *= s;
|
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);
|
view->readonly = static_cast<int>(info->readonly);
|
||||||
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
|
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
|
||||||
view->format = const_cast<char *>(info->format.c_str());
|
view->format = const_cast<char *>(info->format.c_str());
|
||||||
}
|
}
|
||||||
|
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
|
||||||
// Note, all contiguity flags imply PyBUF_STRIDES and lower.
|
view->ndim = (int) info->ndim;
|
||||||
if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) {
|
view->strides = info->strides.data();
|
||||||
if (PyBuffer_IsContiguous(view, 'C') == 0) {
|
view->shape = info->shape.data();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
Py_INCREF(view->obj);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <pybind11/conduit/wrap_include_python_h.h>
|
#define PYBIND11_VERSION_MAJOR 2
|
||||||
#if PY_VERSION_HEX < 0x03080000
|
#define PYBIND11_VERSION_MINOR 13
|
||||||
# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7."
|
#define PYBIND11_VERSION_PATCH 6
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PYBIND11_VERSION_MAJOR 3
|
|
||||||
#define PYBIND11_VERSION_MINOR 0
|
|
||||||
#define PYBIND11_VERSION_PATCH 0.dev1
|
|
||||||
|
|
||||||
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
|
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
|
||||||
// Additional convention: 0xD = dev
|
// Additional convention: 0xD = dev
|
||||||
#define PYBIND11_VERSION_HEX 0x030000D1
|
#define PYBIND11_VERSION_HEX 0x020D0600
|
||||||
|
|
||||||
// Define some generic pybind11 helper macros for warning management.
|
// Define some generic pybind11 helper macros for warning management.
|
||||||
//
|
//
|
||||||
|
|
@ -46,7 +41,7 @@
|
||||||
# define PYBIND11_COMPILER_CLANG
|
# define PYBIND11_COMPILER_CLANG
|
||||||
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
||||||
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push)
|
# 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__)
|
#elif defined(__GNUC__)
|
||||||
# define PYBIND11_COMPILER_GCC
|
# define PYBIND11_COMPILER_GCC
|
||||||
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
||||||
|
|
@ -169,6 +164,14 @@
|
||||||
# endif
|
# endif
|
||||||
#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:
|
// For CUDA, GCC7, GCC8:
|
||||||
// PYBIND11_NOINLINE_FORCED is incompatible with `-Wattributes -Werror`.
|
// PYBIND11_NOINLINE_FORCED is incompatible with `-Wattributes -Werror`.
|
||||||
// When defining PYBIND11_NOINLINE_FORCED, it is best to also use `-Wno-attributes`.
|
// When defining PYBIND11_NOINLINE_FORCED, it is best to also use `-Wno-attributes`.
|
||||||
|
|
@ -209,6 +212,31 @@
|
||||||
# define PYBIND11_MAYBE_UNUSED __attribute__((__unused__))
|
# define PYBIND11_MAYBE_UNUSED __attribute__((__unused__))
|
||||||
#endif
|
#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
|
// https://en.cppreference.com/w/c/chrono/localtime
|
||||||
#if defined(__STDC_LIB_EXT1__) && !defined(__STDC_WANT_LIB_EXT1__)
|
#if defined(__STDC_LIB_EXT1__) && !defined(__STDC_WANT_LIB_EXT1__)
|
||||||
# define __STDC_WANT_LIB_EXT1__
|
# define __STDC_WANT_LIB_EXT1__
|
||||||
|
|
@ -243,14 +271,46 @@
|
||||||
# endif
|
# endif
|
||||||
#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)
|
#if defined(PYBIND11_NUMPY_1_ONLY)
|
||||||
# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED
|
# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED
|
||||||
#endif
|
#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
|
# define PYBIND11_SIMPLE_GIL_MANAGEMENT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# if defined(PYBIND11_DEBUG_MARKER)
|
||||||
|
# define _DEBUG
|
||||||
|
# undef PYBIND11_DEBUG_MARKER
|
||||||
|
# endif
|
||||||
|
PYBIND11_WARNING_POP
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
@ -269,17 +329,6 @@
|
||||||
# endif
|
# endif
|
||||||
#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
|
// 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
|
#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L
|
||||||
# define PYBIND11_HAS_U8STRING
|
# define PYBIND11_HAS_U8STRING
|
||||||
|
|
@ -338,20 +387,6 @@
|
||||||
#define PYBIND11_CONCAT(first, second) first##second
|
#define PYBIND11_CONCAT(first, second) first##second
|
||||||
#define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals();
|
#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 \
|
#define PYBIND11_CHECK_PYTHON_VERSION \
|
||||||
{ \
|
{ \
|
||||||
const char *compiled_ver \
|
const char *compiled_ver \
|
||||||
|
|
@ -605,8 +640,6 @@ struct instance {
|
||||||
bool simple_instance_registered : 1;
|
bool simple_instance_registered : 1;
|
||||||
/// If true, get_internals().patients has an entry for this object
|
/// If true, get_internals().patients has an entry for this object
|
||||||
bool has_patients : 1;
|
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
|
/// Initializes all of the above type/values/holders data (but not the instance values
|
||||||
/// themselves)
|
/// themselves)
|
||||||
|
|
@ -629,14 +662,6 @@ struct instance {
|
||||||
static_assert(std::is_standard_layout<instance>::value,
|
static_assert(std::is_standard_layout<instance>::value,
|
||||||
"Internal error: `pybind11::detail::instance` is not standard layout!");
|
"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)
|
/// from __cpp_future__ import (convenient aliases from C++14/17)
|
||||||
#if defined(PYBIND11_CPP14)
|
#if defined(PYBIND11_CPP14)
|
||||||
using std::conditional_t;
|
using std::conditional_t;
|
||||||
|
|
@ -1103,14 +1128,14 @@ struct overload_cast_impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Class>
|
template <typename Return, typename Class>
|
||||||
constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept
|
constexpr auto operator()(Return (Class::*pmf)(Args...),
|
||||||
-> decltype(pmf) {
|
std::false_type = {}) const noexcept -> decltype(pmf) {
|
||||||
return pmf;
|
return pmf;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Return, typename Class>
|
template <typename Return, typename Class>
|
||||||
constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept
|
constexpr auto operator()(Return (Class::*pmf)(Args...) const,
|
||||||
-> decltype(pmf) {
|
std::true_type) const noexcept -> decltype(pmf) {
|
||||||
return pmf;
|
return pmf;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1258,10 +1283,5 @@ constexpr
|
||||||
# define PYBIND11_DETAILED_ERROR_MESSAGES
|
# define PYBIND11_DETAILED_ERROR_MESSAGES
|
||||||
#endif
|
#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(detail)
|
||||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||||
|
|
|
||||||
|
|
@ -99,13 +99,6 @@ constexpr descr<1, Type> const_name() {
|
||||||
return {'%'};
|
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.
|
// If "_" is defined as a macro, py::detail::_ cannot be provided.
|
||||||
// It is therefore best to use py::detail::const_name universally.
|
// It is therefore best to use py::detail::const_name universally.
|
||||||
// This block is for backward compatibility only.
|
// This block is for backward compatibility only.
|
||||||
|
|
@ -163,8 +156,9 @@ constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
template <size_t N, typename... Ts, typename... Args>
|
template <size_t N, typename... Ts, typename... Args>
|
||||||
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
|
constexpr auto concat(const descr<N, Ts...> &d,
|
||||||
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
|
const Args &...args) -> decltype(std::declval<descr<N + 2, Ts...>>()
|
||||||
|
+ concat(args...)) {
|
||||||
return d + const_name(", ") + concat(args...);
|
return d + const_name(", ") + concat(args...);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -174,15 +168,5 @@ constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {
|
||||||
return const_name("{") + descr + const_name("}");
|
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(detail)
|
||||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
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.
|
- delegate translation to the next translator by throwing a new type of exception.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool handled = with_exception_translators(
|
bool handled = with_internals([&](internals &internals) {
|
||||||
[&](std::forward_list<ExceptionTranslator> &exception_translators,
|
auto &local_exception_translators = get_local_internals().registered_exception_translators;
|
||||||
std::forward_list<ExceptionTranslator> &local_exception_translators) {
|
if (detail::apply_exception_translators(local_exception_translators)) {
|
||||||
if (detail::apply_exception_translators(local_exception_translators)) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
auto &exception_translators = internals.registered_exception_translators;
|
||||||
if (detail::apply_exception_translators(exception_translators)) {
|
if (detail::apply_exception_translators(exception_translators)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
|
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "class.h"
|
#include "class.h"
|
||||||
#include "using_smart_holder.h"
|
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
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
|
// 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
|
// derived type (through those holder's implicit conversion from derived class holder
|
||||||
// constructors).
|
// 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) {
|
void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
|
||||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||||
auto *ptr = holder_helper<Holder<Class>>::get(holder);
|
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));
|
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<...>()
|
// Implementing class for py::init<...>()
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
struct constructor {
|
struct constructor {
|
||||||
|
|
@ -479,7 +410,7 @@ struct pickle_factory<Get, Set, RetState(Self), NewInstance(ArgState)> {
|
||||||
|
|
||||||
template <typename Class, typename... Extra>
|
template <typename Class, typename... Extra>
|
||||||
void execute(Class &cl, const Extra &...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)
|
#if defined(PYBIND11_CPP14)
|
||||||
cl.def(
|
cl.def(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
# include <pybind11/gil.h>
|
# include <pybind11/gil.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <pybind11/conduit/pybind11_platform_abi_id.h>
|
|
||||||
#include <pybind11/pytypes.h>
|
#include <pybind11/pytypes.h>
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
@ -37,12 +36,18 @@
|
||||||
/// further ABI-incompatible changes may be made before the ABI is officially
|
/// further ABI-incompatible changes may be made before the ABI is officially
|
||||||
/// changed to the new version.
|
/// changed to the new version.
|
||||||
#ifndef PYBIND11_INTERNALS_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
|
#endif
|
||||||
|
|
||||||
#if PYBIND11_INTERNALS_VERSION < 7
|
// This requirement is mainly to reduce the support burden (see PR #4570).
|
||||||
# error "PYBIND11_INTERNALS_VERSION 7 is the minimum for all platforms for pybind11v3."
|
static_assert(PY_VERSION_HEX < 0x030C0000 || PYBIND11_INTERNALS_VERSION >= 5,
|
||||||
#endif
|
"pybind11 ABI version 5 is the minimum for Python 3.12+");
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||||
|
|
||||||
|
|
@ -61,29 +66,40 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass);
|
||||||
// Thread Specific Storage (TSS) API.
|
// Thread Specific Storage (TSS) API.
|
||||||
// Avoid unnecessary allocation of `Py_tss_t`, since we cannot use
|
// Avoid unnecessary allocation of `Py_tss_t`, since we cannot use
|
||||||
// `Py_LIMITED_API` anyway.
|
// `Py_LIMITED_API` anyway.
|
||||||
#define PYBIND11_TLS_KEY_REF Py_tss_t &
|
#if PYBIND11_INTERNALS_VERSION > 4
|
||||||
#if defined(__clang__)
|
# define PYBIND11_TLS_KEY_REF Py_tss_t &
|
||||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
# if defined(__clang__)
|
||||||
_Pragma("clang diagnostic push") /**/ \
|
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||||
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
_Pragma("clang diagnostic push") /**/ \
|
||||||
Py_tss_t var \
|
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||||
= Py_tss_NEEDS_INIT; \
|
Py_tss_t var \
|
||||||
_Pragma("clang diagnostic pop")
|
= Py_tss_NEEDS_INIT; \
|
||||||
#elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
_Pragma("clang diagnostic pop")
|
||||||
# define PYBIND11_TLS_KEY_INIT(var) \
|
# elif defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
||||||
_Pragma("GCC diagnostic push") /**/ \
|
# define PYBIND11_TLS_KEY_INIT(var) \
|
||||||
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
_Pragma("GCC diagnostic push") /**/ \
|
||||||
Py_tss_t var \
|
_Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \
|
||||||
= Py_tss_NEEDS_INIT; \
|
Py_tss_t var \
|
||||||
_Pragma("GCC diagnostic pop")
|
= 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
|
#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
|
#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
|
// 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
|
// 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,
|
// 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
|
// which works. If not under a known-good stl, provide our own name-based hash and equality
|
||||||
// functions that use the type name.
|
// 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; }
|
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_hash = std::hash<std::type_index>;
|
||||||
using type_equal_to = std::equal_to<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 {
|
struct internals {
|
||||||
#ifdef Py_GIL_DISABLED
|
#ifdef Py_GIL_DISABLED
|
||||||
pymutex mutex;
|
pymutex mutex;
|
||||||
pymutex exception_translator_mutex;
|
|
||||||
#endif
|
#endif
|
||||||
// std::type_index -> pybind11's type information
|
// std::type_index -> pybind11's type information
|
||||||
type_map<type_info *> registered_types_cpp;
|
type_map<type_info *> registered_types_cpp;
|
||||||
|
|
@ -179,26 +195,35 @@ struct internals {
|
||||||
std::forward_list<ExceptionTranslator> registered_exception_translators;
|
std::forward_list<ExceptionTranslator> registered_exception_translators;
|
||||||
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
|
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across
|
||||||
// extensions
|
// extensions
|
||||||
std::forward_list<std::string> static_strings; // Stores the std::strings backing
|
#if PYBIND11_INTERNALS_VERSION == 4
|
||||||
// detail::c_str()
|
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 *static_property_type;
|
||||||
PyTypeObject *default_metaclass;
|
PyTypeObject *default_metaclass;
|
||||||
PyObject *instance_base;
|
PyObject *instance_base;
|
||||||
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
|
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
|
||||||
PYBIND11_TLS_KEY_INIT(tstate)
|
PYBIND11_TLS_KEY_INIT(tstate)
|
||||||
|
#if PYBIND11_INTERNALS_VERSION > 4
|
||||||
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
|
PYBIND11_TLS_KEY_INIT(loader_life_support_tls_key)
|
||||||
|
#endif // PYBIND11_INTERNALS_VERSION > 4
|
||||||
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
|
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
|
||||||
PyInterpreterState *istate = nullptr;
|
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
|
// 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
|
// We want unique addresses since we use pointer equality to compare function records
|
||||||
std::string function_record_capsule_name = internals_function_record_capsule_name;
|
std::string function_record_capsule_name = internals_function_record_capsule_name;
|
||||||
|
#endif
|
||||||
|
|
||||||
internals() = default;
|
internals() = default;
|
||||||
internals(const internals &other) = delete;
|
internals(const internals &other) = delete;
|
||||||
internals &operator=(const internals &other) = delete;
|
internals &operator=(const internals &other) = delete;
|
||||||
~internals() {
|
~internals() {
|
||||||
|
#if PYBIND11_INTERNALS_VERSION > 4
|
||||||
PYBIND11_TLS_FREE(loader_life_support_tls_key);
|
PYBIND11_TLS_FREE(loader_life_support_tls_key);
|
||||||
|
#endif // PYBIND11_INTERNALS_VERSION > 4
|
||||||
|
|
||||||
// This destructor is called *after* Py_Finalize() in finalize_interpreter().
|
// This destructor is called *after* Py_Finalize() in finalize_interpreter().
|
||||||
// That *SHOULD BE* fine. The following details what happens when PyThread_tss_free is
|
// 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.
|
/// Additional type information which does not fit into the PyTypeObject.
|
||||||
/// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
|
/// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
|
||||||
struct type_info {
|
struct type_info {
|
||||||
|
|
@ -237,7 +251,6 @@ struct type_info {
|
||||||
buffer_info *(*get_buffer)(PyObject *, void *) = nullptr;
|
buffer_info *(*get_buffer)(PyObject *, void *) = nullptr;
|
||||||
void *get_buffer_data = nullptr;
|
void *get_buffer_data = nullptr;
|
||||||
void *(*module_local_load)(PyObject *, const type_info *) = 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
|
/* A simple type never occurs as a (direct or indirect) parent
|
||||||
* of a class that makes use of multiple inheritance.
|
* 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.
|
* 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;
|
bool simple_type : 1;
|
||||||
/* True if there is no multiple inheritance in this type's inheritance tree */
|
/* True if there is no multiple inheritance in this type's inheritance tree */
|
||||||
bool simple_ancestors : 1;
|
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 */
|
/* true if this is a type registered with py::module_local */
|
||||||
bool module_local : 1;
|
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 \
|
#define PYBIND11_INTERNALS_ID \
|
||||||
"__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
|
"__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 \
|
#define PYBIND11_MODULE_LOCAL_ID \
|
||||||
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
|
"__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
|
/// Each module locally stores a pointer to the `internals` data. The data
|
||||||
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.
|
/// 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() {
|
inline object get_python_state_dict() {
|
||||||
object 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());
|
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
|
||||||
#else
|
#else
|
||||||
# if PY_VERSION_HEX < 0x03090000
|
# if PY_VERSION_HEX < 0x03090000
|
||||||
|
|
@ -471,12 +547,13 @@ PYBIND11_NOINLINE internals &get_internals() {
|
||||||
}
|
}
|
||||||
PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate);
|
PYBIND11_TLS_REPLACE_VALUE(internals_ptr->tstate, tstate);
|
||||||
|
|
||||||
|
#if PYBIND11_INTERNALS_VERSION > 4
|
||||||
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
|
// NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
|
||||||
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) {
|
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->loader_life_support_tls_key)) {
|
||||||
pybind11_fail("get_internals: could not successfully initialize the "
|
pybind11_fail("get_internals: could not successfully initialize the "
|
||||||
"loader_life_support TSS key!");
|
"loader_life_support TSS key!");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
internals_ptr->istate = tstate->interp;
|
internals_ptr->istate = tstate->interp;
|
||||||
state_dict[PYBIND11_INTERNALS_ID] = capsule(reinterpret_cast<void *>(internals_pp));
|
state_dict[PYBIND11_INTERNALS_ID] = capsule(reinterpret_cast<void *>(internals_pp));
|
||||||
internals_ptr->registered_exception_translators.push_front(&translate_exception);
|
internals_ptr->registered_exception_translators.push_front(&translate_exception);
|
||||||
|
|
@ -506,6 +583,40 @@ PYBIND11_NOINLINE internals &get_internals() {
|
||||||
struct local_internals {
|
struct local_internals {
|
||||||
type_map<type_info *> registered_types_cpp;
|
type_map<type_info *> registered_types_cpp;
|
||||||
std::forward_list<ExceptionTranslator> registered_exception_translators;
|
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.
|
/// 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);
|
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) {
|
inline std::uint64_t mix64(std::uint64_t z) {
|
||||||
// David Stafford's variant 13 of the MurmurHash3 finalizer popularized
|
// David Stafford's variant 13 of the MurmurHash3 finalizer popularized
|
||||||
// by the SplitMix PRNG.
|
// by the SplitMix PRNG.
|
||||||
|
|
@ -555,8 +653,8 @@ inline std::uint64_t mix64(std::uint64_t z) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
inline auto with_instance_map(const void *ptr, const F &cb)
|
inline auto with_instance_map(const void *ptr,
|
||||||
-> decltype(cb(std::declval<instance_map &>())) {
|
const F &cb) -> decltype(cb(std::declval<instance_map &>())) {
|
||||||
auto &internals = get_internals();
|
auto &internals = get_internals();
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
#ifdef Py_GIL_DISABLED
|
||||||
|
|
@ -611,8 +709,7 @@ const char *c_str(Args &&...args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char *get_function_record_capsule_name() {
|
inline const char *get_function_record_capsule_name() {
|
||||||
// On GraalPy, pointer equality of the names is currently not guaranteed
|
#if PYBIND11_INTERNALS_VERSION > 4
|
||||||
#if !defined(GRAALVM_PYTHON)
|
|
||||||
return get_internals().function_record_capsule_name.c_str();
|
return get_internals().function_record_capsule_name.c_str();
|
||||||
#else
|
#else
|
||||||
return nullptr;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <pybind11/gil.h>
|
|
||||||
#include <pybind11/pytypes.h>
|
#include <pybind11/pytypes.h>
|
||||||
#include <pybind11/trampoline_self_life_support.h>
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "cpp_conduit.h"
|
#include "cpp_conduit.h"
|
||||||
#include "descr.h"
|
#include "descr.h"
|
||||||
#include "dynamic_raw_ptr_cast_if_possible.h"
|
|
||||||
#include "internals.h"
|
#include "internals.h"
|
||||||
#include "typeid.h"
|
#include "typeid.h"
|
||||||
#include "using_smart_holder.h"
|
|
||||||
#include "value_and_holder.h"
|
#include "value_and_holder.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
@ -47,7 +43,11 @@ private:
|
||||||
|
|
||||||
// Store stack pointer in thread-local storage.
|
// Store stack pointer in thread-local storage.
|
||||||
static PYBIND11_TLS_KEY_REF get_stack_tls_key() {
|
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;
|
return get_internals().loader_life_support_tls_key;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
static loader_life_support *get_stack_top() {
|
static loader_life_support *get_stack_top() {
|
||||||
return static_cast<loader_life_support *>(PYBIND11_TLS_GET_VALUE(get_stack_tls_key()));
|
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)) {
|
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
|
||||||
check.push_back((PyTypeObject *) parent.ptr());
|
check.push_back((PyTypeObject *) parent.ptr());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const &type_dict = get_internals().registered_types_py;
|
auto const &type_dict = get_internals().registered_types_py;
|
||||||
for (size_t i = 0; i < check.size(); i++) {
|
for (size_t i = 0; i < check.size(); i++) {
|
||||||
auto *type = check[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.
|
* The value is cached for the lifetime of the Python type.
|
||||||
*/
|
*/
|
||||||
inline const std::vector<detail::type_info *> &all_type_info(PyTypeObject *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);
|
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().
|
// Searches the inheritance graph for a registered Python instance, using all_type_info().
|
||||||
PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
|
PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
|
||||||
const detail::type_info *tinfo) {
|
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 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))) {
|
for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) {
|
||||||
if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) {
|
if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) {
|
||||||
auto *wrapper = reinterpret_cast<PyObject *>(it_i->second);
|
return handle((PyObject *) it_i->second).inc_ref();
|
||||||
if (try_incref(wrapper)) {
|
|
||||||
return handle(wrapper);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +459,7 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
|
||||||
}
|
}
|
||||||
|
|
||||||
inline PyThreadState *get_thread_state_unchecked() {
|
inline PyThreadState *get_thread_state_unchecked() {
|
||||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
#if defined(PYPY_VERSION)
|
||||||
return PyThreadState_GET();
|
return PyThreadState_GET();
|
||||||
#elif PY_VERSION_HEX < 0x030D0000
|
#elif PY_VERSION_HEX < 0x030D0000
|
||||||
return _PyThreadState_UncheckedGet();
|
return _PyThreadState_UncheckedGet();
|
||||||
|
|
@ -511,361 +472,6 @@ inline PyThreadState *get_thread_state_unchecked() {
|
||||||
void keep_alive_impl(handle nurse, handle patient);
|
void keep_alive_impl(handle nurse, handle patient);
|
||||||
inline PyObject *make_new_instance(PyTypeObject *type);
|
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 {
|
class type_caster_generic {
|
||||||
public:
|
public:
|
||||||
PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info)
|
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
|
// Base methods for generic caster; there are overridden in copyable_holder_caster
|
||||||
void load_value(value_and_holder &&v_h) {
|
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();
|
auto *&vptr = v_h.value_ptr();
|
||||||
// Lazy allocation for unallocated values:
|
// Lazy allocation for unallocated values:
|
||||||
if (vptr == nullptr) {
|
if (vptr == nullptr) {
|
||||||
|
|
@ -1564,14 +1161,14 @@ protected:
|
||||||
does not have a private operator new implementation. A comma operator is used in the
|
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.*/
|
decltype argument to apply SFINAE to the public copy/move constructors.*/
|
||||||
template <typename T, typename = enable_if_t<is_copy_constructible<T>::value>>
|
template <typename T, typename = enable_if_t<is_copy_constructible<T>::value>>
|
||||||
static auto make_copy_constructor(const T *)
|
static auto make_copy_constructor(const T *) -> decltype(new T(std::declval<const T>()),
|
||||||
-> decltype(new T(std::declval<const T>()), Constructor{}) {
|
Constructor{}) {
|
||||||
return [](const void *arg) -> void * { return new T(*reinterpret_cast<const T *>(arg)); };
|
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>>
|
template <typename T, typename = enable_if_t<is_move_constructible<T>::value>>
|
||||||
static auto make_move_constructor(const T *)
|
static auto make_move_constructor(const T *) -> decltype(new T(std::declval<T &&>()),
|
||||||
-> decltype(new T(std::declval<T &&>()), Constructor{}) {
|
Constructor{}) {
|
||||||
return [](const void *arg) -> void * {
|
return [](const void *arg) -> void * {
|
||||||
return new T(std::move(*const_cast<T *>(reinterpret_cast<const T *>(arg))));
|
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 "common.h"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||||
|
|
|
||||||
|
|
@ -225,22 +225,19 @@ struct EigenProps {
|
||||||
= !show_c_contiguous && show_order && requires_col_major;
|
= !show_c_contiguous && show_order && requires_col_major;
|
||||||
|
|
||||||
static constexpr auto descriptor
|
static constexpr auto descriptor
|
||||||
= const_name("typing.Annotated[")
|
= const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + const_name("[")
|
||||||
+ io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
|
|
||||||
+ npy_format_descriptor<Scalar>::name + io_name("", "]") + const_name(", \"[")
|
|
||||||
+ const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + 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<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + const_name("]")
|
||||||
+ const_name("]\"")
|
+
|
||||||
// For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to
|
// 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
|
// 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
|
// 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
|
// 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
|
// it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and
|
||||||
// 'typing.Annotated[numpy.typing.NDArray[numpy.float64], "[3,2]"]' and an error message
|
// an error message that you *gave* a numpy.ndarray of the right type and dimensions.
|
||||||
// that you *gave* a numpy.ndarray of the right type and dimensions.
|
const_name<show_writeable>(", flags.writeable", "")
|
||||||
+ const_name<show_writeable>(", \"flags.writeable\"", "")
|
+ const_name<show_c_contiguous>(", flags.c_contiguous", "")
|
||||||
+ const_name<show_c_contiguous>(", \"flags.c_contiguous\"", "")
|
+ const_name<show_f_contiguous>(", flags.f_contiguous", "") + const_name("]");
|
||||||
+ 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,
|
// 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;
|
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
|
// Allocate the new type, then build a numpy reference into it
|
||||||
value = Type(fits.rows, fits.cols);
|
value = Type(fits.rows, fits.cols);
|
||||||
PYBIND11_WARNING_POP
|
|
||||||
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
|
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
|
||||||
if (dims == 1) {
|
if (dims == 1) {
|
||||||
ref = ref.squeeze();
|
ref = ref.squeeze();
|
||||||
|
|
@ -444,9 +438,7 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return_descr forces the use of NDArray instead of ArrayLike in args
|
static constexpr auto name = props::descriptor;
|
||||||
// since Ref<...> args can only accept arrays.
|
|
||||||
static constexpr auto name = return_descr(props::descriptor);
|
|
||||||
|
|
||||||
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
|
// 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
|
// 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>
|
template <typename Type, bool ShowDetails, bool NeedsWriteable = false>
|
||||||
struct get_tensor_descriptor {
|
struct get_tensor_descriptor {
|
||||||
static constexpr auto details
|
static constexpr auto details
|
||||||
= const_name<NeedsWriteable>(", \"flags.writeable\"", "") + const_name
|
= const_name<NeedsWriteable>(", flags.writeable", "")
|
||||||
< static_cast<int>(Type::Layout)
|
+ const_name<static_cast<int>(Type::Layout) == static_cast<int>(Eigen::RowMajor)>(
|
||||||
== static_cast<int>(Eigen::RowMajor)
|
", flags.c_contiguous", ", flags.f_contiguous");
|
||||||
> (", \"flags.c_contiguous\"", ", \"flags.f_contiguous\"");
|
|
||||||
static constexpr auto value
|
static constexpr auto value
|
||||||
= const_name("typing.Annotated[")
|
= const_name("numpy.ndarray[") + npy_format_descriptor<typename Type::Scalar>::name
|
||||||
+ io_name("numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
|
+ const_name("[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor
|
||||||
+ npy_format_descriptor<typename Type::Scalar>::name + io_name("", "]")
|
+ const_name("]") + const_name<ShowDetails>(details, const_name("")) + const_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
|
// 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;
|
std::unique_ptr<MapType> value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// return_descr forces the use of NDArray instead of ArrayLike since refs can only reference
|
static constexpr auto name = get_tensor_descriptor<Type, true, needs_writeable>::value;
|
||||||
// arrays
|
|
||||||
static constexpr auto name
|
|
||||||
= return_descr(get_tensor_descriptor<Type, true, needs_writeable>::value);
|
|
||||||
explicit operator MapType *() { return value.get(); }
|
explicit operator MapType *() { return value.get(); }
|
||||||
explicit operator MapType &() { return *value; }
|
explicit operator MapType &() { return *value; }
|
||||||
explicit operator MapType &&() && { return std::move(*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();
|
detail::precheck_interpreter();
|
||||||
Py_InitializeEx(init_signal_handlers ? 1 : 0);
|
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);
|
auto argv_size = static_cast<size_t>(argc);
|
||||||
// SetArgv* on python 3 takes wchar_t, so we have to convert.
|
// 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::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;
|
std::vector<std::unique_ptr<wchar_t[], detail::wide_char_arg_deleter>> widened_argv_entries;
|
||||||
widened_argv_entries.reserve(argv_size);
|
widened_argv_entries.reserve(argv_size);
|
||||||
for (size_t ii = 0; ii < argv_size; ++ii) {
|
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()) {
|
if (!widened_argv_entries.back()) {
|
||||||
// A null here indicates a character-encoding failure or the python
|
// A null here indicates a character-encoding failure or the python
|
||||||
// interpreter out of memory. Give up.
|
// interpreter out of memory. Give up.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||||
|
|
||||||
inline void ensure_builtins_in_globals(object &global) {
|
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
|
// Running exec and eval adds `builtins` module under `__builtins__` key to
|
||||||
// globals if not yet present. Python 3.8 made PyRun_String behave
|
// globals if not yet present. Python 3.8 made PyRun_String behave
|
||||||
// similarly. Let's also do that for older versions, for consistency. This
|
// 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));
|
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>
|
template <eval_mode mode = eval_statements>
|
||||||
object eval_file(str, object, object) {
|
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>
|
template <eval_mode mode = eval_statements>
|
||||||
object eval_file(str, object) {
|
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>
|
template <eval_mode mode = eval_statements>
|
||||||
object eval_file(str) {
|
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
|
#else
|
||||||
template <eval_mode mode = eval_statements>
|
template <eval_mode mode = eval_statements>
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,9 @@ public:
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
|
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
|
||||||
tstate = PyEval_SaveThread();
|
tstate = PyEval_SaveThread();
|
||||||
if (disassoc) {
|
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);
|
PYBIND11_TLS_DELETE_VALUE(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +173,9 @@ public:
|
||||||
PyEval_RestoreThread(tstate);
|
PyEval_RestoreThread(tstate);
|
||||||
}
|
}
|
||||||
if (disassoc) {
|
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);
|
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
|
// 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
|
// normal Python code. However, this will be unnoticeable for quick call-once
|
||||||
// functions, which is usually the case.
|
// functions, which is usually the case.
|
||||||
//
|
|
||||||
// For in-depth background, see docs/advanced/deadlock.md
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class gil_safe_call_once_and_store {
|
class gil_safe_call_once_and_store {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,7 @@ inline numpy_internals &get_numpy_internals() {
|
||||||
PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) {
|
PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) {
|
||||||
module_ numpy = module_::import("numpy");
|
module_ numpy = module_::import("numpy");
|
||||||
str version_string = numpy.attr("__version__");
|
str version_string = numpy.attr("__version__");
|
||||||
|
|
||||||
module_ numpy_lib = module_::import("numpy.lib");
|
module_ numpy_lib = module_::import("numpy.lib");
|
||||||
object numpy_version = numpy_lib.attr("NumpyVersion")(version_string);
|
object numpy_version = numpy_lib.attr("NumpyVersion")(version_string);
|
||||||
int major_version = numpy_version.attr("major").cast<int>();
|
int major_version = numpy_version.attr("major").cast<int>();
|
||||||
|
|
@ -211,7 +212,6 @@ constexpr int platform_lookup(int I, Ints... Is) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct npy_api {
|
struct npy_api {
|
||||||
// If you change this code, please review `normalized_dtype_num` below.
|
|
||||||
enum constants {
|
enum constants {
|
||||||
NPY_ARRAY_C_CONTIGUOUS_ = 0x0001,
|
NPY_ARRAY_C_CONTIGUOUS_ = 0x0001,
|
||||||
NPY_ARRAY_F_CONTIGUOUS_ = 0x0002,
|
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 PyArray_Proxy *array_proxy(void *ptr) { return reinterpret_cast<PyArray_Proxy *>(ptr); }
|
||||||
|
|
||||||
inline const PyArray_Proxy *array_proxy(const void *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 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.
|
/// Size of the data type in bytes.
|
||||||
#ifdef PYBIND11_NUMPY_1_ONLY
|
#ifdef PYBIND11_NUMPY_1_ONLY
|
||||||
ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; }
|
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;
|
return detail::array_descriptor_proxy(m_ptr)->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type number of dtype. Note that different values may be returned for equivalent types,
|
/// type number of dtype.
|
||||||
/// 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.
|
|
||||||
int num() const {
|
int num() const {
|
||||||
// Note: The signature, `dtype::num` follows the naming of NumPy's public
|
// Note: The signature, `dtype::num` follows the naming of NumPy's public
|
||||||
// Python API (i.e., ``dtype.num``), rather than its internal
|
// Python API (i.e., ``dtype.num``), rather than its internal
|
||||||
|
|
@ -810,17 +733,6 @@ public:
|
||||||
return detail::array_descriptor_proxy(m_ptr)->type_num;
|
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
|
/// Single character for byteorder
|
||||||
char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; }
|
char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; }
|
||||||
|
|
||||||
|
|
@ -1516,11 +1428,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct npy_format_descriptor<
|
struct npy_format_descriptor<T, enable_if_t<is_same_ignoring_cvref<T, PyObject *>::value>> {
|
||||||
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 *))>> {
|
|
||||||
static constexpr auto name = const_name("object");
|
static constexpr auto name = const_name("object");
|
||||||
|
|
||||||
static constexpr int value = npy_api::NPY_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>
|
template <typename T, int Flags>
|
||||||
struct handle_type_name<array_t<T, Flags>> {
|
struct handle_type_name<array_t<T, Flags>> {
|
||||||
static constexpr auto name
|
static constexpr auto name
|
||||||
= io_name("typing.Annotated[numpy.typing.ArrayLike, ", "numpy.typing.NDArray[")
|
= const_name("numpy.ndarray[") + npy_format_descriptor<T>::name + const_name("]");
|
||||||
+ npy_format_descriptor<T>::name + const_name("]");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_END(detail)
|
PYBIND11_NAMESPACE_END(detail)
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "detail/class.h"
|
#include "detail/class.h"
|
||||||
#include "detail/dynamic_raw_ptr_cast_if_possible.h"
|
|
||||||
#include "detail/exception_translation.h"
|
#include "detail/exception_translation.h"
|
||||||
#include "detail/init.h"
|
#include "detail/init.h"
|
||||||
#include "detail/using_smart_holder.h"
|
|
||||||
#include "attr.h"
|
#include "attr.h"
|
||||||
#include "gil.h"
|
#include "gil.h"
|
||||||
#include "gil_safe_call_once.h"
|
#include "gil_safe_call_once.h"
|
||||||
|
|
@ -24,17 +22,10 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <stack>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#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))
|
#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914))
|
||||||
# define PYBIND11_STD_LAUNDER std::launder
|
# define PYBIND11_STD_LAUNDER std::launder
|
||||||
# define PYBIND11_HAS_STD_LAUNDER 1
|
# 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,
|
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_pos_only_args = any_of<std::is_same<pos_only, Extra>...>::value,
|
||||||
has_arg_annotations = any_of<is_keyword<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,
|
static_assert(has_arg_annotations || !has_kw_only_args,
|
||||||
"py::kw_only requires the use of argument annotations");
|
"py::kw_only requires the use of argument annotations");
|
||||||
static_assert(((/* Need `py::arg("arg_name")` annotation in function/method. */
|
static_assert(has_arg_annotations || !has_pos_only_args,
|
||||||
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,
|
|
||||||
"py::pos_only requires the use of argument annotations (for docstrings "
|
"py::pos_only requires the use of argument annotations (for docstrings "
|
||||||
"and aligning the annotations to the argument)");
|
"and aligning the annotations to the argument)");
|
||||||
|
|
||||||
|
|
@ -443,13 +423,6 @@ protected:
|
||||||
std::string signature;
|
std::string signature;
|
||||||
size_t type_index = 0, arg_index = 0;
|
size_t type_index = 0, arg_index = 0;
|
||||||
bool is_starred = false;
|
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) {
|
for (const auto *pc = text; *pc != '\0'; ++pc) {
|
||||||
const auto c = *pc;
|
const auto c = *pc;
|
||||||
|
|
||||||
|
|
@ -503,57 +476,7 @@ protected:
|
||||||
} else {
|
} else {
|
||||||
signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
|
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 {
|
} else {
|
||||||
if (c == '-' && *(pc + 1) == '>') {
|
|
||||||
is_return_value.emplace(true);
|
|
||||||
}
|
|
||||||
signature += c;
|
signature += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -650,7 +573,8 @@ protected:
|
||||||
// chain.
|
// chain.
|
||||||
chain_start = rec;
|
chain_start = rec;
|
||||||
rec->next = chain;
|
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());
|
rec_capsule.set_pointer(unique_rec.release());
|
||||||
guarded_strdup.release();
|
guarded_strdup.release();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -710,11 +634,12 @@ protected:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Install docstring */
|
||||||
auto *func = (PyCFunctionObject *) m_ptr;
|
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)
|
// 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());
|
func->m_ml->ml_doc
|
||||||
std::free(const_cast<char *>(PYBIND11_PYCFUNCTION_GET_DOC(func)));
|
= signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
|
||||||
PYBIND11_PYCFUNCTION_SET_DOC(func, doc);
|
|
||||||
|
|
||||||
if (rec->is_method) {
|
if (rec->is_method) {
|
||||||
m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr());
|
m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr());
|
||||||
|
|
@ -1382,7 +1307,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct handle_type_name<module_> {
|
struct handle_type_name<module_> {
|
||||||
static constexpr auto name = const_name("types.ModuleType");
|
static constexpr auto name = const_name("module");
|
||||||
};
|
};
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_END(detail)
|
PYBIND11_NAMESPACE_END(detail)
|
||||||
|
|
@ -1446,8 +1371,8 @@ protected:
|
||||||
tinfo->dealloc = rec.dealloc;
|
tinfo->dealloc = rec.dealloc;
|
||||||
tinfo->simple_type = true;
|
tinfo->simple_type = true;
|
||||||
tinfo->simple_ancestors = true;
|
tinfo->simple_ancestors = true;
|
||||||
|
tinfo->default_holder = rec.default_holder;
|
||||||
tinfo->module_local = rec.module_local;
|
tinfo->module_local = rec.module_local;
|
||||||
tinfo->holder_enum_v = rec.holder_enum_v;
|
|
||||||
|
|
||||||
with_internals([&](internals &internals) {
|
with_internals([&](internals &internals) {
|
||||||
auto tindex = std::type_index(*rec.type);
|
auto tindex = std::type_index(*rec.type);
|
||||||
|
|
@ -1457,17 +1382,7 @@ protected:
|
||||||
} else {
|
} else {
|
||||||
internals.registered_types_cpp[tindex] = tinfo;
|
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};
|
internals.registered_types_py[(PyTypeObject *) m_ptr] = {tinfo};
|
||||||
PYBIND11_WARNING_POP
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rec.bases.size() > 1 || rec.multiple_inheritance) {
|
if (rec.bases.size() > 1 || rec.multiple_inheritance) {
|
||||||
|
|
@ -1620,239 +1535,6 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
|
||||||
return pmf;
|
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>
|
template <typename type_, typename... options>
|
||||||
class class_ : public detail::generic_type {
|
class class_ : public detail::generic_type {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
@ -1869,7 +1551,7 @@ public:
|
||||||
using type = type_;
|
using type = type_;
|
||||||
using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
|
using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
|
||||||
constexpr static bool has_alias = !std::is_void<type_alias>::value;
|
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,
|
static_assert(detail::all_of<is_valid_class_option<options>...>::value,
|
||||||
"Unknown/invalid class_ template parameters provided");
|
"Unknown/invalid class_ template parameters provided");
|
||||||
|
|
@ -1900,16 +1582,8 @@ public:
|
||||||
record.type_align = alignof(conditional_t<has_alias, type_alias, type> &);
|
record.type_align = alignof(conditional_t<has_alias, type_alias, type> &);
|
||||||
record.holder_size = sizeof(holder_type);
|
record.holder_size = sizeof(holder_type);
|
||||||
record.init_instance = init_instance;
|
record.init_instance = init_instance;
|
||||||
|
record.dealloc = dealloc;
|
||||||
if (detail::is_instantiation<std::unique_ptr, holder_type>::value) {
|
record.default_holder = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_operator_new<type>(&record);
|
set_operator_new<type>(&record);
|
||||||
|
|
||||||
|
|
@ -1919,12 +1593,6 @@ public:
|
||||||
/* Process optional arguments, if any */
|
/* Process optional arguments, if any */
|
||||||
process_attributes<Extra...>::init(extra..., &record);
|
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);
|
generic_type::initialize(record);
|
||||||
|
|
||||||
if (has_alias) {
|
if (has_alias) {
|
||||||
|
|
@ -2048,11 +1716,9 @@ public:
|
||||||
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
|
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,
|
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_readwrite() requires a class member (or base class member)");
|
||||||
def_property(name,
|
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)),
|
||||||
property_cpp_function<type, D>::read(pm, *this),
|
fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
|
||||||
property_cpp_function<type, D>::write(pm, *this),
|
def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
|
||||||
return_value_policy::reference_internal,
|
|
||||||
extra...);
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2060,10 +1726,8 @@ public:
|
||||||
class_ &def_readonly(const char *name, const D C::*pm, const Extra &...extra) {
|
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,
|
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_readonly() requires a class member (or base class member)");
|
||||||
def_property_readonly(name,
|
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this));
|
||||||
property_cpp_function<type, D>::readonly(pm, *this),
|
def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
|
||||||
return_value_policy::reference_internal,
|
|
||||||
extra...);
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2240,8 +1904,6 @@ private:
|
||||||
/// instance. Should be called as soon as the `type` value_ptr is set for an instance. Takes
|
/// 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
|
/// 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.
|
/// `.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) {
|
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)));
|
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
|
||||||
if (!v_h.instance_registered()) {
|
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>());
|
init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename WrappedType>
|
/// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
|
||||||
static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) {
|
static void dealloc(detail::value_and_holder &v_h) {
|
||||||
return false;
|
// 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.
|
||||||
// Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy
|
// If we don't, the Python API will exit with an exception, and pybind11 will
|
||||||
// ownership semantics: if we detected via shared_from_this that a shared_ptr exists already,
|
// throw error_already_set from the C++ destructor which is forbidden and triggers
|
||||||
// it is reused, irrespective of the return_value_policy in effect.
|
// std::terminate().
|
||||||
// "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a
|
error_scope scope;
|
||||||
// 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) {
|
|
||||||
if (v_h.holder_constructed()) {
|
if (v_h.holder_constructed()) {
|
||||||
v_h.holder<holder_type>().~holder_type();
|
v_h.holder<holder_type>().~holder_type();
|
||||||
v_h.set_holder_constructed(false);
|
v_h.set_holder_constructed(false);
|
||||||
|
|
@ -2328,32 +1932,6 @@ private:
|
||||||
v_h.value_ptr() = nullptr;
|
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) {
|
static detail::function_record *get_function_record(handle h) {
|
||||||
h = detail::get_function(h);
|
h = detail::get_function(h);
|
||||||
if (!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...
|
/// Binds an existing constructor taking arguments Args...
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
detail::initimpl::constructor<Args...> init() {
|
detail::initimpl::constructor<Args...> init() {
|
||||||
|
|
@ -2441,11 +2014,9 @@ struct enum_base {
|
||||||
.format(std::move(type_name), enum_name(arg), int_(arg));
|
.format(std::move(type_name), enum_name(arg), int_(arg));
|
||||||
},
|
},
|
||||||
name("__repr__"),
|
name("__repr__"),
|
||||||
is_method(m_base),
|
is_method(m_base));
|
||||||
pos_only());
|
|
||||||
|
|
||||||
m_base.attr("name")
|
m_base.attr("name") = property(cpp_function(&enum_name, name("name"), is_method(m_base)));
|
||||||
= property(cpp_function(&enum_name, name("name"), is_method(m_base), pos_only()));
|
|
||||||
|
|
||||||
m_base.attr("__str__") = cpp_function(
|
m_base.attr("__str__") = cpp_function(
|
||||||
[](handle arg) -> str {
|
[](handle arg) -> str {
|
||||||
|
|
@ -2453,8 +2024,7 @@ struct enum_base {
|
||||||
return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg));
|
return pybind11::str("{}.{}").format(std::move(type_name), enum_name(arg));
|
||||||
},
|
},
|
||||||
name("__str__"),
|
name("__str__"),
|
||||||
is_method(m_base),
|
is_method(m_base));
|
||||||
pos_only());
|
|
||||||
|
|
||||||
if (options::show_enum_members_docstring()) {
|
if (options::show_enum_members_docstring()) {
|
||||||
m_base.attr("__doc__") = static_property(
|
m_base.attr("__doc__") = static_property(
|
||||||
|
|
@ -2509,8 +2079,7 @@ struct enum_base {
|
||||||
}, \
|
}, \
|
||||||
name(op), \
|
name(op), \
|
||||||
is_method(m_base), \
|
is_method(m_base), \
|
||||||
arg("other"), \
|
arg("other"))
|
||||||
pos_only())
|
|
||||||
|
|
||||||
#define PYBIND11_ENUM_OP_CONV(op, expr) \
|
#define PYBIND11_ENUM_OP_CONV(op, expr) \
|
||||||
m_base.attr(op) = cpp_function( \
|
m_base.attr(op) = cpp_function( \
|
||||||
|
|
@ -2520,8 +2089,7 @@ struct enum_base {
|
||||||
}, \
|
}, \
|
||||||
name(op), \
|
name(op), \
|
||||||
is_method(m_base), \
|
is_method(m_base), \
|
||||||
arg("other"), \
|
arg("other"))
|
||||||
pos_only())
|
|
||||||
|
|
||||||
#define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \
|
#define PYBIND11_ENUM_OP_CONV_LHS(op, expr) \
|
||||||
m_base.attr(op) = cpp_function( \
|
m_base.attr(op) = cpp_function( \
|
||||||
|
|
@ -2531,8 +2099,7 @@ struct enum_base {
|
||||||
}, \
|
}, \
|
||||||
name(op), \
|
name(op), \
|
||||||
is_method(m_base), \
|
is_method(m_base), \
|
||||||
arg("other"), \
|
arg("other"))
|
||||||
pos_only())
|
|
||||||
|
|
||||||
if (is_convertible) {
|
if (is_convertible) {
|
||||||
PYBIND11_ENUM_OP_CONV_LHS("__eq__", !b.is_none() && a.equal(b));
|
PYBIND11_ENUM_OP_CONV_LHS("__eq__", !b.is_none() && a.equal(b));
|
||||||
|
|
@ -2552,8 +2119,7 @@ struct enum_base {
|
||||||
m_base.attr("__invert__")
|
m_base.attr("__invert__")
|
||||||
= cpp_function([](const object &arg) { return ~(int_(arg)); },
|
= cpp_function([](const object &arg) { return ~(int_(arg)); },
|
||||||
name("__invert__"),
|
name("__invert__"),
|
||||||
is_method(m_base),
|
is_method(m_base));
|
||||||
pos_only());
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PYBIND11_ENUM_OP_STRICT("__eq__", int_(a).equal(int_(b)), return false);
|
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_CONV
|
||||||
#undef PYBIND11_ENUM_OP_STRICT
|
#undef PYBIND11_ENUM_OP_STRICT
|
||||||
|
|
||||||
m_base.attr("__getstate__") = cpp_function([](const object &arg) { return int_(arg); },
|
m_base.attr("__getstate__") = cpp_function(
|
||||||
name("__getstate__"),
|
[](const object &arg) { return int_(arg); }, name("__getstate__"), is_method(m_base));
|
||||||
is_method(m_base),
|
|
||||||
pos_only());
|
|
||||||
|
|
||||||
m_base.attr("__hash__") = cpp_function([](const object &arg) { return int_(arg); },
|
m_base.attr("__hash__") = cpp_function(
|
||||||
name("__hash__"),
|
[](const object &arg) { return int_(arg); }, name("__hash__"), is_method(m_base));
|
||||||
is_method(m_base),
|
|
||||||
pos_only());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PYBIND11_NOINLINE void value(char const *name_, object value, const char *doc = nullptr) {
|
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);
|
m_base.init(is_arithmetic, is_convertible);
|
||||||
|
|
||||||
def(init([](Scalar i) { return static_cast<Type>(i); }), arg("value"));
|
def(init([](Scalar i) { return static_cast<Type>(i); }), arg("value"));
|
||||||
def_property_readonly("value", [](Type value) { return (Scalar) value; }, pos_only());
|
def_property_readonly("value", [](Type value) { return (Scalar) value; });
|
||||||
def("__int__", [](Type value) { return (Scalar) value; }, pos_only());
|
def("__int__", [](Type value) { return (Scalar) value; });
|
||||||
def("__index__", [](Type value) { return (Scalar) value; }, pos_only());
|
def("__index__", [](Type value) { return (Scalar) value; });
|
||||||
attr("__setstate__") = cpp_function(
|
attr("__setstate__") = cpp_function(
|
||||||
[](detail::value_and_holder &v_h, Scalar arg) {
|
[](detail::value_and_holder &v_h, Scalar arg) {
|
||||||
detail::initimpl::setstate<Base>(
|
detail::initimpl::setstate<Base>(
|
||||||
|
|
@ -2684,8 +2246,7 @@ public:
|
||||||
detail::is_new_style_constructor(),
|
detail::is_new_style_constructor(),
|
||||||
pybind11::name("__setstate__"),
|
pybind11::name("__setstate__"),
|
||||||
is_method(*this),
|
is_method(*this),
|
||||||
arg("state"),
|
arg("state"));
|
||||||
pos_only());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export enumeration entries into the parent scope
|
/// 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>
|
inline std::pair<decltype(internals::registered_types_py)::iterator, bool>
|
||||||
all_type_info_get_cache(PyTypeObject *type) {
|
all_type_info_get_cache(PyTypeObject *type) {
|
||||||
auto res = with_internals([type](internals &internals) {
|
auto res = with_internals([type](internals &internals) {
|
||||||
auto ins = internals
|
return internals
|
||||||
.registered_types_py
|
.registered_types_py
|
||||||
#ifdef __cpp_lib_unordered_map_try_emplace
|
#ifdef __cpp_lib_unordered_map_try_emplace
|
||||||
.try_emplace(type);
|
.try_emplace(type);
|
||||||
#else
|
#else
|
||||||
.emplace(type, std::vector<detail::type_info *>());
|
.emplace(type, std::vector<detail::type_info *>());
|
||||||
#endif
|
#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) {
|
if (res.second) {
|
||||||
// New cache entry created; set up a weak reference to automatically remove it if the type
|
// 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)) {
|
if (!detail::get_type_info(typeid(state), false)) {
|
||||||
class_<state>(handle(), "iterator", pybind11::module_local())
|
class_<state>(handle(), "iterator", pybind11::module_local())
|
||||||
.def(
|
.def("__iter__", [](state &s) -> state & { return s; })
|
||||||
"__iter__", [](state &s) -> state & { return s; }, pos_only())
|
|
||||||
.def(
|
.def(
|
||||||
"__next__",
|
"__next__",
|
||||||
[](state &s) -> ValueType {
|
[](state &s) -> ValueType {
|
||||||
|
|
@ -2889,7 +2442,6 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) {
|
||||||
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
|
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
|
||||||
},
|
},
|
||||||
std::forward<Extra>(extra)...,
|
std::forward<Extra>(extra)...,
|
||||||
pos_only(),
|
|
||||||
Policy);
|
Policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3024,12 +2576,10 @@ void implicitly_convertible() {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void register_exception_translator(ExceptionTranslator &&translator) {
|
inline void register_exception_translator(ExceptionTranslator &&translator) {
|
||||||
detail::with_exception_translators(
|
detail::with_internals([&](detail::internals &internals) {
|
||||||
[&](std::forward_list<ExceptionTranslator> &exception_translators,
|
internals.registered_exception_translators.push_front(
|
||||||
std::forward_list<ExceptionTranslator> &local_exception_translators) {
|
std::forward<ExceptionTranslator>(translator));
|
||||||
(void) local_exception_translators;
|
});
|
||||||
exception_translators.push_front(std::forward<ExceptionTranslator>(translator));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3039,12 +2589,11 @@ inline void register_exception_translator(ExceptionTranslator &&translator) {
|
||||||
* the exception.
|
* the exception.
|
||||||
*/
|
*/
|
||||||
inline void register_local_exception_translator(ExceptionTranslator &&translator) {
|
inline void register_local_exception_translator(ExceptionTranslator &&translator) {
|
||||||
detail::with_exception_translators(
|
detail::with_internals([&](detail::internals &internals) {
|
||||||
[&](std::forward_list<ExceptionTranslator> &exception_translators,
|
(void) internals;
|
||||||
std::forward_list<ExceptionTranslator> &local_exception_translators) {
|
detail::get_local_internals().registered_exception_translators.push_front(
|
||||||
(void) exception_translators;
|
std::forward<ExceptionTranslator>(translator));
|
||||||
local_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.
|
/* Don't call dispatch code if invoked from overridden function.
|
||||||
Unfortunately this doesn't work on PyPy and GraalPy. */
|
Unfortunately this doesn't work on PyPy. */
|
||||||
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
|
#if !defined(PYPY_VERSION)
|
||||||
# if PY_VERSION_HEX >= 0x03090000
|
# if PY_VERSION_HEX >= 0x03090000
|
||||||
PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get());
|
PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get());
|
||||||
if (frame != nullptr) {
|
if (frame != nullptr) {
|
||||||
|
|
|
||||||
|
|
@ -113,17 +113,6 @@ public:
|
||||||
/// See above (the only difference is that the key is provided as a string literal)
|
/// See above (the only difference is that the key is provided as a string literal)
|
||||||
str_attr_accessor attr(const char *key) const;
|
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
|
/** \rst
|
||||||
Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple``
|
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
|
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__``.
|
/// Get or set the object's docstring, i.e. ``obj.__doc__``.
|
||||||
str_attr_accessor doc() const;
|
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
|
/// Return the object's current reference count
|
||||||
ssize_t ref_count() const {
|
ssize_t ref_count() const {
|
||||||
#ifdef PYPY_VERSION
|
#ifdef PYPY_VERSION
|
||||||
|
|
@ -657,7 +643,7 @@ struct error_fetch_and_normalize {
|
||||||
|
|
||||||
bool have_trace = false;
|
bool have_trace = false;
|
||||||
if (m_trace) {
|
if (m_trace) {
|
||||||
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
|
#if !defined(PYPY_VERSION)
|
||||||
auto *tb = reinterpret_cast<PyTracebackObject *>(m_trace.ptr());
|
auto *tb = reinterpret_cast<PyTracebackObject *>(m_trace.ptr());
|
||||||
|
|
||||||
// Get the deepest trace possible.
|
// Get the deepest trace possible.
|
||||||
|
|
@ -1370,7 +1356,7 @@ inline bool PyUnicode_Check_Permissive(PyObject *o) {
|
||||||
# define PYBIND11_STR_CHECK_FUN PyUnicode_Check
|
# define PYBIND11_STR_CHECK_FUN PyUnicode_Check
|
||||||
#endif
|
#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 {
|
class kwargs_proxy : public handle {
|
||||||
public:
|
public:
|
||||||
|
|
@ -1484,17 +1470,11 @@ public:
|
||||||
PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check)
|
PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check)
|
||||||
|
|
||||||
iterator &operator++() {
|
iterator &operator++() {
|
||||||
init();
|
|
||||||
advance();
|
advance();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator operator++(int) {
|
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;
|
auto rv = *this;
|
||||||
advance();
|
advance();
|
||||||
return rv;
|
return rv;
|
||||||
|
|
@ -1502,12 +1482,15 @@ public:
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
|
// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
|
||||||
reference operator*() const {
|
reference operator*() const {
|
||||||
init();
|
if (m_ptr && !value.ptr()) {
|
||||||
|
auto &self = const_cast<iterator &>(*this);
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer operator->() const {
|
pointer operator->() const {
|
||||||
init();
|
operator*();
|
||||||
return &value;
|
return &value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1530,13 +1513,6 @@ public:
|
||||||
friend bool operator!=(const iterator &a, const iterator &b) { return a->ptr() != b->ptr(); }
|
friend bool operator!=(const iterator &a, const iterator &b) { return a->ptr() != b->ptr(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init() const {
|
|
||||||
if (m_ptr && !value.ptr()) {
|
|
||||||
auto &self = const_cast<iterator &>(*this);
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void advance() {
|
void advance() {
|
||||||
value = reinterpret_steal<object>(PyIter_Next(m_ptr));
|
value = reinterpret_steal<object>(PyIter_Next(m_ptr));
|
||||||
if (value.ptr() == nullptr && PyErr_Occurred()) {
|
if (value.ptr() == nullptr && PyErr_Occurred()) {
|
||||||
|
|
@ -2240,18 +2216,6 @@ class kwargs : public dict {
|
||||||
PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check)
|
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 {
|
class anyset : public object {
|
||||||
public:
|
public:
|
||||||
PYBIND11_OBJECT(anyset, object, PyAnySet_Check)
|
PYBIND11_OBJECT(anyset, object, PyAnySet_Check)
|
||||||
|
|
@ -2572,19 +2536,6 @@ str_attr_accessor object_api<D>::doc() const {
|
||||||
return attr("__doc__");
|
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>
|
template <typename D>
|
||||||
handle object_api<D>::get_type() const {
|
handle object_api<D>::get_type() const {
|
||||||
return type::handle_of(derived());
|
return type::handle_of(derived());
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,10 @@
|
||||||
|
|
||||||
#include "pybind11.h"
|
#include "pybind11.h"
|
||||||
#include "detail/common.h"
|
#include "detail/common.h"
|
||||||
#include "detail/descr.h"
|
|
||||||
#include "detail/type_caster_base.h"
|
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <initializer_list>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
@ -39,89 +35,6 @@
|
||||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
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
|
/// 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.
|
/// forwarding a container element). Typically used indirect via forwarded_type(), below.
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
|
|
@ -153,10 +66,17 @@ private:
|
||||||
}
|
}
|
||||||
void reserve_maybe(const anyset &, void *) {}
|
void reserve_maybe(const anyset &, void *) {}
|
||||||
|
|
||||||
bool convert_iterable(const iterable &itbl, bool convert) {
|
public:
|
||||||
for (const auto &it : itbl) {
|
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;
|
key_conv conv;
|
||||||
if (!conv.load(it, convert)) {
|
if (!conv.load(entry, convert)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
value.insert(cast_op<Key &&>(std::move(conv)));
|
value.insert(cast_op<Key &&>(std::move(conv)));
|
||||||
|
|
@ -164,29 +84,6 @@ private:
|
||||||
return true;
|
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>
|
template <typename T>
|
||||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||||
if (!std::is_lvalue_reference<T>::value) {
|
if (!std::is_lvalue_reference<T>::value) {
|
||||||
|
|
@ -218,10 +115,15 @@ private:
|
||||||
}
|
}
|
||||||
void reserve_maybe(const dict &, void *) {}
|
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();
|
value.clear();
|
||||||
reserve_maybe(d, &value);
|
reserve_maybe(d, &value);
|
||||||
for (const auto &it : d) {
|
for (auto it : d) {
|
||||||
key_conv kconv;
|
key_conv kconv;
|
||||||
value_conv vconv;
|
value_conv vconv;
|
||||||
if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
|
if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) {
|
||||||
|
|
@ -232,25 +134,6 @@ private:
|
||||||
return true;
|
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>
|
template <typename T>
|
||||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||||
dict d;
|
dict d;
|
||||||
|
|
@ -283,35 +166,13 @@ struct list_caster {
|
||||||
using value_conv = make_caster<Value>;
|
using value_conv = make_caster<Value>;
|
||||||
|
|
||||||
bool load(handle src, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) {
|
if (!isinstance<sequence>(src) || isinstance<bytes>(src) || isinstance<str>(src)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isinstance<sequence>(src)) {
|
auto s = reinterpret_borrow<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);
|
|
||||||
value.clear();
|
value.clear();
|
||||||
reserve_maybe(s, &value);
|
reserve_maybe(s, &value);
|
||||||
for (const auto &it : seq) {
|
for (const auto &it : s) {
|
||||||
value_conv conv;
|
value_conv conv;
|
||||||
if (!conv.load(it, convert)) {
|
if (!conv.load(it, convert)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -321,6 +182,13 @@ private:
|
||||||
return true;
|
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:
|
public:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
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>
|
template <typename Type, typename Alloc>
|
||||||
struct type_caster<std::list<Type, Alloc>> : list_caster<std::list<Type, Alloc>, Type> {};
|
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>
|
template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0>
|
||||||
struct array_caster {
|
struct array_caster {
|
||||||
using value_conv = make_caster<Value>;
|
using value_conv = make_caster<Value>;
|
||||||
|
|
||||||
private:
|
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>
|
public:
|
||||||
bool convert_elements(handle seq, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
auto l = reinterpret_borrow<sequence>(seq);
|
if (!isinstance<sequence>(src)) {
|
||||||
value.reset(new ArrayType{});
|
return false;
|
||||||
// Using `resize` to preserve the behavior exactly as it was before PR #5305
|
}
|
||||||
// For the `resize` to work, `Value` must be default constructible.
|
auto l = reinterpret_borrow<sequence>(src);
|
||||||
// For `std::valarray`, this is a requirement:
|
if (!require_size(l.size())) {
|
||||||
// https://en.cppreference.com/w/cpp/named_req/NumericType
|
return false;
|
||||||
value->resize(l.size());
|
}
|
||||||
size_t ctr = 0;
|
size_t ctr = 0;
|
||||||
for (const auto &it : l) {
|
for (const auto &it : l) {
|
||||||
value_conv conv;
|
value_conv conv;
|
||||||
if (!conv.load(it, convert)) {
|
if (!conv.load(it, convert)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
(*value)[ctr++] = cast_op<Value &&>(std::move(conv));
|
value[ctr++] = cast_op<Value &&>(std::move(conv));
|
||||||
}
|
}
|
||||||
return true;
|
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>
|
template <typename T>
|
||||||
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
static handle cast(T &&src, return_value_policy policy, handle parent) {
|
||||||
list l(src.size());
|
list l(src.size());
|
||||||
|
|
@ -448,36 +272,12 @@ public:
|
||||||
return l.release();
|
return l.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code copied from PYBIND11_TYPE_CASTER macro.
|
PYBIND11_TYPE_CASTER(ArrayType,
|
||||||
// Intentionally preserving the behavior exactly as it was before PR #5305
|
const_name<Resizable>(const_name(""), const_name("Annotated["))
|
||||||
template <typename T_, enable_if_t<std::is_same<ArrayType, remove_cv_t<T_>>::value, int> = 0>
|
+ const_name("list[") + value_conv::name + const_name("]")
|
||||||
static handle cast(T_ *src, return_value_policy policy, handle parent) {
|
+ const_name<Resizable>(const_name(""),
|
||||||
if (!src) {
|
const_name(", FixedSize(")
|
||||||
return none().release();
|
+ const_name<Size>() + const_name(")]")));
|
||||||
}
|
|
||||||
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(")]"));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Type, size_t Size>
|
template <typename Type, size_t Size>
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ public:
|
||||||
return true;
|
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)
|
#endif // PYBIND11_HAS_FILESYSTEM || defined(PYBIND11_HAS_EXPERIMENTAL_FILESYSTEM)
|
||||||
|
|
|
||||||
|
|
@ -487,7 +487,7 @@ PYBIND11_NAMESPACE_END(detail)
|
||||||
//
|
//
|
||||||
// std::vector
|
// 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) {
|
class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args &&...args) {
|
||||||
using Class_ = class_<Vector, holder_type>;
|
using Class_ = class_<Vector, holder_type>;
|
||||||
|
|
||||||
|
|
@ -694,43 +694,9 @@ struct ItemsViewImpl : public detail::items_view {
|
||||||
Map ↦
|
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)
|
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) {
|
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
|
||||||
using KeyType = typename Map::key_type;
|
using KeyType = typename Map::key_type;
|
||||||
using MappedType = typename Map::mapped_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 & {
|
[](Map &m, const KeyType &k) -> MappedType & {
|
||||||
auto it = m.find(k);
|
auto it = m.find(k);
|
||||||
if (it == m.end()) {
|
if (it == m.end()) {
|
||||||
set_error(PyExc_KeyError, detail::format_message_key_error(k));
|
throw key_error();
|
||||||
throw error_already_set();
|
|
||||||
}
|
}
|
||||||
return it->second;
|
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) {
|
cl.def("__delitem__", [](Map &m, const KeyType &k) {
|
||||||
auto it = m.find(k);
|
auto it = m.find(k);
|
||||||
if (it == m.end()) {
|
if (it == m.end()) {
|
||||||
set_error(PyExc_KeyError, detail::format_message_key_error(k));
|
throw key_error();
|
||||||
throw error_already_set();
|
|
||||||
}
|
}
|
||||||
m.erase(it);
|
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>
|
#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(PYBIND11_NAMESPACE)
|
||||||
PYBIND11_NAMESPACE_BEGIN(typing)
|
PYBIND11_NAMESPACE_BEGIN(typing)
|
||||||
|
|
||||||
|
|
@ -89,18 +82,6 @@ class Optional : public object {
|
||||||
using object::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>
|
template <typename T>
|
||||||
class TypeGuard : public bool_ {
|
class TypeGuard : public bool_ {
|
||||||
using bool_::bool_;
|
using bool_::bool_;
|
||||||
|
|
@ -119,7 +100,8 @@ class Never : public none {
|
||||||
using none::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>
|
template <size_t N>
|
||||||
struct StringLiteral {
|
struct StringLiteral {
|
||||||
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, name); }
|
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...)>> {
|
struct handle_type_name<typing::Callable<Return(Args...)>> {
|
||||||
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
|
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
|
||||||
static constexpr auto name
|
static constexpr auto name
|
||||||
= const_name("Callable[[")
|
= const_name("Callable[[") + ::pybind11::detail::concat(make_caster<Args>::name...)
|
||||||
+ ::pybind11::detail::concat(::pybind11::detail::arg_descr(make_caster<Args>::name)...)
|
+ const_name("], ") + make_caster<retval_type>::name + const_name("]");
|
||||||
+ const_name("], ") + ::pybind11::detail::return_descr(make_caster<retval_type>::name)
|
|
||||||
+ const_name("]");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Return>
|
template <typename Return>
|
||||||
struct handle_type_name<typing::Callable<Return(ellipsis)>> {
|
struct handle_type_name<typing::Callable<Return(ellipsis)>> {
|
||||||
// PEP 484 specifies this syntax for defining only return types of callables
|
// 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>;
|
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
|
||||||
static constexpr auto name = const_name("Callable[..., ")
|
static constexpr auto name
|
||||||
+ ::pybind11::detail::return_descr(make_caster<retval_type>::name)
|
= const_name("Callable[..., ") + make_caster<retval_type>::name + const_name("]");
|
||||||
+ const_name("]");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
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("]");
|
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>
|
template <typename T>
|
||||||
struct handle_type_name<typing::TypeGuard<T>> {
|
struct handle_type_name<typing::TypeGuard<T>> {
|
||||||
static constexpr auto name = const_name("TypeGuard[") + make_caster<T>::name + const_name("]");
|
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)
|
#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>
|
template <typing::StringLiteral... Literals>
|
||||||
struct handle_type_name<typing::Literal<Literals...>> {
|
struct handle_type_name<typing::Literal<Literals...>> {
|
||||||
static constexpr auto name
|
static constexpr auto name = const_name("Literal[")
|
||||||
= const_name("Literal[")
|
+ pybind11::detail::concat(const_name(Literals.name)...)
|
||||||
+ pybind11::detail::concat(const_name(sanitize_string_literal<Literals>().name)...)
|
+ const_name("]");
|
||||||
+ const_name("]");
|
|
||||||
};
|
};
|
||||||
template <typing::StringLiteral StrLit>
|
template <typing::StringLiteral StrLit>
|
||||||
struct handle_type_name<typing::TypeVar<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
|
#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
|
import sys
|
||||||
|
|
||||||
if sys.version_info < (3, 8): # noqa: UP036
|
if sys.version_info < (3, 7): # noqa: UP036
|
||||||
msg = "pybind11 does not support Python < 3.8. v2.13 was the last release supporting Python 3.7."
|
msg = "pybind11 does not support Python < 3.7. v2.12 was the last release supporting Python 3.6."
|
||||||
raise ImportError(msg)
|
raise ImportError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,6 @@ def main() -> None:
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.",
|
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()
|
args = parser.parse_args()
|
||||||
if not sys.argv[1:]:
|
if not sys.argv[1:]:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
@ -85,8 +80,6 @@ def main() -> None:
|
||||||
print(quote(get_cmake_dir()))
|
print(quote(get_cmake_dir()))
|
||||||
if args.pkgconfigdir:
|
if args.pkgconfigdir:
|
||||||
print(quote(get_pkgconfig_dir()))
|
print(quote(get_pkgconfig_dir()))
|
||||||
if args.extension_suffix:
|
|
||||||
print(sysconfig.get_config_var("EXT_SUFFIX"))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
__version__ = "3.0.0.dev1"
|
__version__ = "2.13.6"
|
||||||
version_info = tuple(_to_int(s) for s in __version__.split("."))
|
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
|
cpp_flag_cache = None
|
||||||
|
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache()
|
||||||
def auto_cpp_level(compiler: Any) -> str | int:
|
def auto_cpp_level(compiler: Any) -> str | int:
|
||||||
"""
|
"""
|
||||||
Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows.
|
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]
|
[tool.pylint]
|
||||||
master.py-version = "3.8"
|
master.py-version = "3.7"
|
||||||
reports.output-format = "colorized"
|
reports.output-format = "colorized"
|
||||||
messages_control.disable = [
|
messages_control.disable = [
|
||||||
"design",
|
"design",
|
||||||
|
|
@ -45,7 +45,7 @@ messages_control.disable = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py38"
|
target-version = "py37"
|
||||||
src = ["src"]
|
src = ["src"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
|
|
@ -71,6 +71,7 @@ ignore = [
|
||||||
"PLR", # Design related pylint
|
"PLR", # Design related pylint
|
||||||
"E501", # Line too long (Black is enough)
|
"E501", # Line too long (Black is enough)
|
||||||
"PT011", # Too broad with raises in pytest
|
"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())
|
"SIM118", # iter(x) is not always the same as iter(x.keys())
|
||||||
]
|
]
|
||||||
unfixable = ["T20"]
|
unfixable = ["T20"]
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ classifiers =
|
||||||
Topic :: Utilities
|
Topic :: Utilities
|
||||||
Programming Language :: C++
|
Programming Language :: C++
|
||||||
Programming Language :: Python :: 3 :: Only
|
Programming Language :: Python :: 3 :: Only
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
Programming Language :: Python :: 3.8
|
Programming Language :: Python :: 3.8
|
||||||
Programming Language :: Python :: 3.9
|
Programming Language :: Python :: 3.9
|
||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
|
|
@ -38,5 +39,5 @@ project_urls =
|
||||||
Chat = https://gitter.im/pybind/Lobby
|
Chat = https://gitter.im/pybind/Lobby
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
python_requires = >=3.8
|
python_requires = >=3.7
|
||||||
zip_safe = False
|
zip_safe = False
|
||||||
|
|
|
||||||
|
|
@ -144,10 +144,6 @@ with remove_output("pybind11/include", "pybind11/share"):
|
||||||
stderr=sys.stderr,
|
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)
|
txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd)
|
||||||
code = compile(txt, setup_py, "exec")
|
code = compile(txt, setup_py, "exec")
|
||||||
exec(code, {"SDist": SDist})
|
exec(code, {"SDist": SDist})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,16 @@
|
||||||
# All rights reserved. Use of this source code is governed by a
|
# All rights reserved. Use of this source code is governed by a
|
||||||
# BSD-style license that can be found in the LICENSE file.
|
# 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.
|
# 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")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../tools")
|
||||||
|
|
||||||
option(PYBIND11_WERROR "Report all warnings as errors" OFF)
|
option(PYBIND11_WERROR "Report all warnings as errors" OFF)
|
||||||
option(DOWNLOAD_EIGEN "Download EIGEN" OFF)
|
option(DOWNLOAD_EIGEN "Download EIGEN (requires CMake 3.11+)" OFF)
|
||||||
option(PYBIND11_CUDA_TESTS "Enable building CUDA tests" OFF)
|
option(PYBIND11_CUDA_TESTS "Enable building CUDA tests (requires CMake 3.12+)" OFF)
|
||||||
set(PYBIND11_TEST_OVERRIDE
|
set(PYBIND11_TEST_OVERRIDE
|
||||||
""
|
""
|
||||||
CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests")
|
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_callbacks
|
||||||
test_chrono
|
test_chrono
|
||||||
test_class
|
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_const_name
|
||||||
test_constants_and_functions
|
test_constants_and_functions
|
||||||
test_copy_move
|
test_copy_move
|
||||||
|
|
@ -140,7 +131,6 @@ set(PYBIND11_TEST_FILES
|
||||||
test_custom_type_casters
|
test_custom_type_casters
|
||||||
test_custom_type_setup
|
test_custom_type_setup
|
||||||
test_docstring_options
|
test_docstring_options
|
||||||
test_docs_advanced_cast_custom
|
|
||||||
test_eigen_matrix
|
test_eigen_matrix
|
||||||
test_eigen_tensor
|
test_eigen_tensor
|
||||||
test_enum
|
test_enum
|
||||||
|
|
@ -174,8 +164,7 @@ set(PYBIND11_TEST_FILES
|
||||||
test_unnamed_namespace_a
|
test_unnamed_namespace_a
|
||||||
test_unnamed_namespace_b
|
test_unnamed_namespace_b
|
||||||
test_vector_unique_ptr_member
|
test_vector_unique_ptr_member
|
||||||
test_virtual_functions
|
test_virtual_functions)
|
||||||
test_warnings)
|
|
||||||
|
|
||||||
# Invoking cmake with something like:
|
# Invoking cmake with something like:
|
||||||
# cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" ..
|
# cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" ..
|
||||||
|
|
@ -263,21 +252,25 @@ endif()
|
||||||
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
|
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
|
||||||
# Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake).
|
# 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
|
# 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(DOWNLOAD_EIGEN)
|
||||||
if(CMAKE_VERSION VERSION_LESS 3.18)
|
if(CMAKE_VERSION VERSION_LESS 3.11)
|
||||||
set(_opts)
|
message(FATAL_ERROR "CMake 3.11+ required when using DOWNLOAD_EIGEN")
|
||||||
else()
|
|
||||||
set(_opts SOURCE_SUBDIR no-cmake-build)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
eigen
|
eigen
|
||||||
GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}"
|
GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}"
|
||||||
GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}"
|
GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}")
|
||||||
${_opts})
|
|
||||||
FetchContent_MakeAvailable(eigen)
|
FetchContent_GetProperties(eigen)
|
||||||
if(NOT CMAKE_VERSION VERSION_LESS 3.18)
|
if(NOT eigen_POPULATED)
|
||||||
set(EIGEN3_INCLUDE_DIR "${eigen_SOURCE_DIR}")
|
message(
|
||||||
|
STATUS
|
||||||
|
"Downloading Eigen ${PYBIND11_EIGEN_VERSION_STRING} (${PYBIND11_EIGEN_VERSION_HASH}) from ${PYBIND11_EIGEN_REPO}"
|
||||||
|
)
|
||||||
|
FetchContent_Populate(eigen)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(EIGEN3_INCLUDE_DIR ${eigen_SOURCE_DIR})
|
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)
|
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
|
||||||
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
|
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
|
||||||
endif()
|
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()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -510,19 +504,15 @@ foreach(target ${test_targets})
|
||||||
if(SKBUILD)
|
if(SKBUILD)
|
||||||
install(TARGETS ${target} LIBRARY DESTINATION .)
|
install(TARGETS ${target} LIBRARY DESTINATION .)
|
||||||
endif()
|
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()
|
endforeach()
|
||||||
|
|
||||||
# Provide nice organisation in IDEs
|
# Provide nice organisation in IDEs
|
||||||
source_group(
|
if(NOT CMAKE_VERSION VERSION_LESS 3.8)
|
||||||
TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
source_group(
|
||||||
PREFIX "Header Files"
|
TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||||
FILES ${PYBIND11_HEADERS})
|
PREFIX "Header Files"
|
||||||
|
FILES ${PYBIND11_HEADERS})
|
||||||
|
endif()
|
||||||
|
|
||||||
# Make sure pytest is found or produce a warning
|
# Make sure pytest is found or produce a warning
|
||||||
pybind11_find_import(pytest VERSION 3.1)
|
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)
|
${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
|
||||||
|
|
||||||
if(NOT PYBIND11_CUDA_TESTS)
|
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.
|
# Test embedding the interpreter. Provides the `cpptest` target.
|
||||||
add_subdirectory(test_embed)
|
add_subdirectory(test_embed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ except Exception:
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def use_multiprocessing_forkserver_on_linux():
|
def use_multiprocessing_forkserver_on_linux():
|
||||||
if sys.platform != "linux" or sys.implementation.name == "graalpy":
|
if sys.platform != "linux":
|
||||||
# The default on Windows, macOS and GraalPy is "spawn": If it's not broken, don't fix it.
|
# The default on Windows and macOS is "spawn": If it's not broken, don't fix it.
|
||||||
return
|
return
|
||||||
|
|
||||||
# Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592
|
# Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592
|
||||||
|
|
@ -136,7 +136,7 @@ class Capture:
|
||||||
return Output(self.err)
|
return Output(self.err)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def capture(capsys):
|
def capture(capsys):
|
||||||
"""Extended `capsys` with context manager and custom equality operators"""
|
"""Extended `capsys` with context manager and custom equality operators"""
|
||||||
return Capture(capsys)
|
return Capture(capsys)
|
||||||
|
|
@ -172,7 +172,7 @@ def _sanitize_docstring(thing):
|
||||||
return _sanitize_general(s)
|
return _sanitize_general(s)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def doc():
|
def doc():
|
||||||
"""Sanitize docstrings and add custom failure explanation"""
|
"""Sanitize docstrings and add custom failure explanation"""
|
||||||
return SanitizedString(_sanitize_docstring)
|
return SanitizedString(_sanitize_docstring)
|
||||||
|
|
@ -184,7 +184,7 @@ def _sanitize_message(thing):
|
||||||
return _hexadecimal.sub("0", s)
|
return _hexadecimal.sub("0", s)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def msg():
|
def msg():
|
||||||
"""Sanitize messages and add custom failure explanation"""
|
"""Sanitize messages and add custom failure explanation"""
|
||||||
return SanitizedString(_sanitize_message)
|
return SanitizedString(_sanitize_message)
|
||||||
|
|
@ -198,11 +198,10 @@ def pytest_assertrepr_compare(op, left, right): # noqa: ARG001
|
||||||
|
|
||||||
|
|
||||||
def gc_collect():
|
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)"""
|
reference counting tests with PyPy)"""
|
||||||
gc.collect()
|
gc.collect()
|
||||||
gc.collect()
|
gc.collect()
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure():
|
def pytest_configure():
|
||||||
|
|
@ -212,9 +211,9 @@ def pytest_configure():
|
||||||
|
|
||||||
def pytest_report_header(config):
|
def pytest_report_header(config):
|
||||||
del config # Unused.
|
del config # Unused.
|
||||||
assert pybind11_tests.compiler_info is not None, (
|
assert (
|
||||||
"Please update pybind11_tests.cpp if this assert fails."
|
pybind11_tests.compiler_info is not None
|
||||||
)
|
), "Please update pybind11_tests.cpp if this assert fails."
|
||||||
return (
|
return (
|
||||||
"C++ Info:"
|
"C++ Info:"
|
||||||
f" {pybind11_tests.compiler_info}"
|
f" {pybind11_tests.compiler_info}"
|
||||||
|
|
|
||||||
|
|
@ -312,16 +312,8 @@ void print_created(T *inst, Values &&...values) {
|
||||||
}
|
}
|
||||||
template <class T, typename... Values>
|
template <class T, typename... Values>
|
||||||
void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given 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...);
|
print_constr_details(inst, "destroyed", values...);
|
||||||
track_destroyed(inst);
|
track_destroyed(inst);
|
||||||
#else
|
|
||||||
py::detail::silence_unused_warnings(inst, values...);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
template <class T, typename... Values>
|
template <class T, typename... Values>
|
||||||
void print_values(T *inst, Values &&...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"
|
CPYTHON = platform.python_implementation() == "CPython"
|
||||||
PYPY = platform.python_implementation() == "PyPy"
|
PYPY = platform.python_implementation() == "PyPy"
|
||||||
GRAALPY = sys.implementation.name == "graalpy"
|
|
||||||
PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,59 @@
|
||||||
// Copyright (c) 2024 The pybind Community.
|
// 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!
|
// 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 "test_cpp_conduit_traveler_types.h"
|
||||||
|
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
#include <typeinfo>
|
||||||
|
|
||||||
namespace {
|
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) {
|
extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) {
|
||||||
const auto *cpp_traveler = pybind11_conduit_v1::get_type_pointer_ephemeral<
|
const auto *cpp_traveler
|
||||||
pybind11_tests::test_cpp_conduit::Traveler>(traveler);
|
= get_cpp_conduit_type_ptr<pybind11_tests::test_cpp_conduit::Traveler>(traveler);
|
||||||
if (cpp_traveler == nullptr) {
|
if (cpp_traveler == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -35,8 +61,9 @@ extern "C" PyObject *wrapGetLuggage(PyObject * /*self*/, PyObject *traveler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" PyObject *wrapGetPoints(PyObject * /*self*/, PyObject *premium_traveler) {
|
extern "C" PyObject *wrapGetPoints(PyObject * /*self*/, PyObject *premium_traveler) {
|
||||||
const auto *cpp_premium_traveler = pybind11_conduit_v1::get_type_pointer_ephemeral<
|
const auto *cpp_premium_traveler
|
||||||
pybind11_tests::test_cpp_conduit::PremiumTraveler>(premium_traveler);
|
= get_cpp_conduit_type_ptr<pybind11_tests::test_cpp_conduit::PremiumTraveler>(
|
||||||
|
premium_traveler);
|
||||||
if (cpp_premium_traveler == nullptr) {
|
if (cpp_premium_traveler == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import tarfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
# These tests must be run explicitly
|
# These tests must be run explicitly
|
||||||
|
# They require CMake 3.15+ (--install)
|
||||||
|
|
||||||
DIR = os.path.abspath(os.path.dirname(__file__))
|
DIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
|
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
|
||||||
|
|
@ -45,17 +46,8 @@ main_headers = {
|
||||||
"include/pybind11/pytypes.h",
|
"include/pybind11/pytypes.h",
|
||||||
"include/pybind11/stl.h",
|
"include/pybind11/stl.h",
|
||||||
"include/pybind11/stl_bind.h",
|
"include/pybind11/stl_bind.h",
|
||||||
"include/pybind11/trampoline_self_life_support.h",
|
|
||||||
"include/pybind11/type_caster_pyobject_ptr.h",
|
"include/pybind11/type_caster_pyobject_ptr.h",
|
||||||
"include/pybind11/typing.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 = {
|
detail_headers = {
|
||||||
|
|
@ -63,13 +55,10 @@ detail_headers = {
|
||||||
"include/pybind11/detail/common.h",
|
"include/pybind11/detail/common.h",
|
||||||
"include/pybind11/detail/cpp_conduit.h",
|
"include/pybind11/detail/cpp_conduit.h",
|
||||||
"include/pybind11/detail/descr.h",
|
"include/pybind11/detail/descr.h",
|
||||||
"include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
|
|
||||||
"include/pybind11/detail/init.h",
|
"include/pybind11/detail/init.h",
|
||||||
"include/pybind11/detail/internals.h",
|
"include/pybind11/detail/internals.h",
|
||||||
"include/pybind11/detail/struct_smart_holder.h",
|
|
||||||
"include/pybind11/detail/type_caster_base.h",
|
"include/pybind11/detail/type_caster_base.h",
|
||||||
"include/pybind11/detail/typeid.h",
|
"include/pybind11/detail/typeid.h",
|
||||||
"include/pybind11/detail/using_smart_holder.h",
|
|
||||||
"include/pybind11/detail/value_and_holder.h",
|
"include/pybind11/detail/value_and_holder.h",
|
||||||
"include/pybind11/detail/exception_translation.h",
|
"include/pybind11/detail/exception_translation.h",
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +73,16 @@ stl_headers = {
|
||||||
"include/pybind11/stl/filesystem.h",
|
"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 = {
|
cmake_files = {
|
||||||
"share/cmake/pybind11/FindPythonLibsNew.cmake",
|
"share/cmake/pybind11/FindPythonLibsNew.cmake",
|
||||||
"share/cmake/pybind11/pybind11Common.cmake",
|
"share/cmake/pybind11/pybind11Common.cmake",
|
||||||
|
|
@ -106,11 +105,9 @@ py_files = {
|
||||||
"commands.py",
|
"commands.py",
|
||||||
"py.typed",
|
"py.typed",
|
||||||
"setup_helpers.py",
|
"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
|
src_files = headers | cmake_files | pkgconfig_files
|
||||||
all_files = src_files | py_files
|
all_files = src_files | py_files
|
||||||
|
|
||||||
|
|
@ -119,7 +116,6 @@ sdist_files = {
|
||||||
"pybind11",
|
"pybind11",
|
||||||
"pybind11/include",
|
"pybind11/include",
|
||||||
"pybind11/include/pybind11",
|
"pybind11/include/pybind11",
|
||||||
"pybind11/include/pybind11/conduit",
|
|
||||||
"pybind11/include/pybind11/detail",
|
"pybind11/include/pybind11/detail",
|
||||||
"pybind11/include/pybind11/eigen",
|
"pybind11/include/pybind11/eigen",
|
||||||
"pybind11/include/pybind11/stl",
|
"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()) {
|
for (const auto &initializer : initializers()) {
|
||||||
initializer(m);
|
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)));
|
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"
|
version = "0.0.1"
|
||||||
dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"]
|
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]
|
[tool.scikit-build.cmake.define]
|
||||||
PYBIND11_FINDPYTHON = true
|
PYBIND11_FINDPYTHON = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
--only-binary=:all:
|
--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.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.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 python_version>="3.7" and python_version<"3.10"
|
||||||
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 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 python_version>="3.11" and python_version<"3.13"
|
||||||
numpy~=1.26.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13"
|
|
||||||
pytest~=7.0
|
pytest~=7.0
|
||||||
pytest-timeout
|
pytest-timeout
|
||||||
scipy~=1.5.4; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version<"3.10"
|
scipy~=1.5.4; platform_python_implementation!="PyPy" 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.8.0; platform_python_implementation!="PyPy" 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.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)});
|
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 {
|
class BrokenMatrix : public Matrix {
|
||||||
public:
|
public:
|
||||||
BrokenMatrix(py::ssize_t rows, py::ssize_t cols) : Matrix(rows, cols) {}
|
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(); });
|
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()):
|
for j in range(m4.cols()):
|
||||||
assert m3[i, j] == m4[i, j]
|
assert m3[i, j] == m4[i, j]
|
||||||
|
|
||||||
if env.GRAALPY:
|
|
||||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
|
||||||
cstats = ConstructorStats.get(m.Matrix)
|
cstats = ConstructorStats.get(m.Matrix)
|
||||||
assert cstats.alive() == 1
|
assert cstats.alive() == 1
|
||||||
del m3, m4
|
del m3, m4
|
||||||
|
|
@ -120,8 +118,6 @@ def test_to_python():
|
||||||
mat2[2, 3] = 5
|
mat2[2, 3] = 5
|
||||||
assert mat2[2, 3] == 5
|
assert mat2[2, 3] == 5
|
||||||
|
|
||||||
if env.GRAALPY:
|
|
||||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
|
||||||
cstats = ConstructorStats.get(m.Matrix)
|
cstats = ConstructorStats.get(m.Matrix)
|
||||||
assert cstats.alive() == 1
|
assert cstats.alive() == 1
|
||||||
del mat
|
del mat
|
||||||
|
|
@ -239,163 +235,3 @@ def test_buffer_exception():
|
||||||
memoryview(m.BrokenMatrix(1, 1))
|
memoryview(m.BrokenMatrix(1, 1))
|
||||||
assert isinstance(excinfo.value.__cause__, RuntimeError)
|
assert isinstance(excinfo.value.__cause__, RuntimeError)
|
||||||
assert "for context" in str(excinfo.value.__cause__)
|
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)
|
cant_convert(3.14159)
|
||||||
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
|
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
|
||||||
# TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
|
# 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():
|
with env.deprecated_call():
|
||||||
assert convert(Int()) == 42
|
assert convert(Int()) == 42
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@ TEST_SUBMODULE(call_policies, m) {
|
||||||
},
|
},
|
||||||
py::call_guard<DependentGuard, CustomGuard>());
|
py::call_guard<DependentGuard, CustomGuard>());
|
||||||
|
|
||||||
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
|
#if !defined(PYPY_VERSION)
|
||||||
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy/GraalPy as well,
|
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well,
|
||||||
// but it's unclear how to test it without `PyGILState_GetThisThreadState`.
|
// but it's unclear how to test it without `PyGILState_GetThisThreadState`.
|
||||||
auto report_gil_status = []() {
|
auto report_gil_status = []() {
|
||||||
auto is_gil_held = false;
|
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.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):
|
def test_keep_alive_argument(capture):
|
||||||
n_inst = ConstructorStats.detail_reg_inst()
|
n_inst = ConstructorStats.detail_reg_inst()
|
||||||
with capture:
|
with capture:
|
||||||
|
|
@ -61,7 +60,6 @@ def test_keep_alive_argument(capture):
|
||||||
assert str(excinfo.value) == "Could not activate keep_alive!"
|
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):
|
def test_keep_alive_return_value(capture):
|
||||||
n_inst = ConstructorStats.detail_reg_inst()
|
n_inst = ConstructorStats.detail_reg_inst()
|
||||||
with capture:
|
with capture:
|
||||||
|
|
@ -120,7 +118,6 @@ def test_keep_alive_return_value(capture):
|
||||||
|
|
||||||
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
|
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
|
||||||
@pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented")
|
@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):
|
def test_alive_gc(capture):
|
||||||
n_inst = ConstructorStats.detail_reg_inst()
|
n_inst = ConstructorStats.detail_reg_inst()
|
||||||
p = m.ParentGC()
|
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):
|
def test_alive_gc_derived(capture):
|
||||||
class Derived(m.Parent):
|
class Derived(m.Parent):
|
||||||
pass
|
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):
|
def test_alive_gc_multi_derived(capture):
|
||||||
class Derived(m.Parent, m.Child):
|
class Derived(m.Parent, m.Child):
|
||||||
def __init__(self):
|
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):
|
def test_return_none(capture):
|
||||||
n_inst = ConstructorStats.detail_reg_inst()
|
n_inst = ConstructorStats.detail_reg_inst()
|
||||||
with capture:
|
with capture:
|
||||||
|
|
@ -218,7 +212,6 @@ def test_return_none(capture):
|
||||||
assert capture == "Releasing parent."
|
assert capture == "Releasing parent."
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
|
||||||
def test_keep_alive_constructor(capture):
|
def test_keep_alive_constructor(capture):
|
||||||
n_inst = ConstructorStats.detail_reg_inst()
|
n_inst = ConstructorStats.detail_reg_inst()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,12 @@ TEST_SUBMODULE(callbacks, m) {
|
||||||
rec_capsule.set_name(rec_capsule_name);
|
rec_capsule.set_name(rec_capsule_name);
|
||||||
m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr()));
|
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
|
// rec_capsule with nullptr name
|
||||||
py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
|
py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
|
||||||
m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr()));
|
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():
|
def test_lambda_closure_cleanup():
|
||||||
m.test_lambda_closure_cleanup()
|
m.test_lambda_closure_cleanup()
|
||||||
cstats = m.payload_cstats()
|
cstats = m.payload_cstats()
|
||||||
|
|
@ -99,7 +98,6 @@ def test_lambda_closure_cleanup():
|
||||||
assert cstats.move_constructions >= 1
|
assert cstats.move_constructions >= 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
|
|
||||||
def test_cpp_callable_cleanup():
|
def test_cpp_callable_cleanup():
|
||||||
alive_counts = m.test_cpp_callable_cleanup()
|
alive_counts = m.test_cpp_callable_cleanup()
|
||||||
assert alive_counts == [0, 1, 2, 1, 2, 1, 0]
|
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
|
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():
|
def test_custom_func2():
|
||||||
assert m.custom_function2(3) == 27
|
assert m.custom_function2(3) == 27
|
||||||
assert m.roundtrip(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 pr4220_tripped_over_this
|
||||||
|
|
||||||
namespace pr5396_forward_declared_class {
|
|
||||||
class ForwardClass;
|
|
||||||
class Args : public py::args {};
|
|
||||||
} // namespace pr5396_forward_declared_class
|
|
||||||
|
|
||||||
} // namespace test_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) {
|
TEST_SUBMODULE(class_, m) {
|
||||||
m.def("obj_class_name", [](py::handle obj) { return py::detail::obj_class_name(obj.ptr()); });
|
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__",
|
.def_static("__new__",
|
||||||
[](const py::object &) { return NoConstructorNew::new_instance(); });
|
[](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
|
// test_inheritance
|
||||||
class Pet {
|
class Pet {
|
||||||
public:
|
public:
|
||||||
|
|
@ -233,12 +211,11 @@ TEST_SUBMODULE(class_, m) {
|
||||||
m.def("mismatched_holder_1", []() {
|
m.def("mismatched_holder_1", []() {
|
||||||
auto mod = py::module_::import("__main__");
|
auto mod = py::module_::import("__main__");
|
||||||
py::class_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1");
|
py::class_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1");
|
||||||
py::class_<MismatchDerived1, std::unique_ptr<MismatchDerived1>, MismatchBase1>(
|
py::class_<MismatchDerived1, MismatchBase1>(mod, "MismatchDerived1");
|
||||||
mod, "MismatchDerived1");
|
|
||||||
});
|
});
|
||||||
m.def("mismatched_holder_2", []() {
|
m.def("mismatched_holder_2", []() {
|
||||||
auto mod = py::module_::import("__main__");
|
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>(
|
py::class_<MismatchDerived2, std::shared_ptr<MismatchDerived2>, MismatchBase2>(
|
||||||
mod, "MismatchDerived2");
|
mod, "MismatchDerived2");
|
||||||
});
|
});
|
||||||
|
|
@ -632,10 +609,8 @@ CHECK_NOALIAS(8);
|
||||||
CHECK_HOLDER(1, unique);
|
CHECK_HOLDER(1, unique);
|
||||||
CHECK_HOLDER(2, unique);
|
CHECK_HOLDER(2, unique);
|
||||||
CHECK_HOLDER(3, 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(4, unique);
|
||||||
CHECK_HOLDER(5, unique);
|
CHECK_HOLDER(5, unique);
|
||||||
#endif
|
|
||||||
CHECK_HOLDER(6, shared);
|
CHECK_HOLDER(6, shared);
|
||||||
CHECK_HOLDER(7, shared);
|
CHECK_HOLDER(7, shared);
|
||||||
CHECK_HOLDER(8, shared);
|
CHECK_HOLDER(8, shared);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -28,9 +27,6 @@ def test_instance(msg):
|
||||||
|
|
||||||
instance = m.NoConstructor.new_instance()
|
instance = m.NoConstructor.new_instance()
|
||||||
|
|
||||||
if env.GRAALPY:
|
|
||||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
|
||||||
|
|
||||||
cstats = ConstructorStats.get(m.NoConstructor)
|
cstats = ConstructorStats.get(m.NoConstructor)
|
||||||
assert cstats.alive() == 1
|
assert cstats.alive() == 1
|
||||||
del instance
|
del instance
|
||||||
|
|
@ -39,26 +35,12 @@ def test_instance(msg):
|
||||||
|
|
||||||
def test_instance_new():
|
def test_instance_new():
|
||||||
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)
|
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)
|
||||||
|
|
||||||
if env.GRAALPY:
|
|
||||||
pytest.skip("ConstructorStats is incompatible with GraalPy.")
|
|
||||||
|
|
||||||
cstats = ConstructorStats.get(m.NoConstructorNew)
|
cstats = ConstructorStats.get(m.NoConstructorNew)
|
||||||
assert cstats.alive() == 1
|
assert cstats.alive() == 1
|
||||||
del instance
|
del instance
|
||||||
assert cstats.alive() == 0
|
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():
|
def test_type():
|
||||||
assert m.check_type(1) == m.DerivedClass1
|
assert m.check_type(1) == m.DerivedClass1
|
||||||
with pytest.raises(RuntimeError) as execinfo:
|
with pytest.raises(RuntimeError) as execinfo:
|
||||||
|
|
@ -379,7 +361,7 @@ def test_brace_initialization():
|
||||||
assert b.vec == [123, 456]
|
assert b.vec == [123, 456]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail("env.PYPY or env.GRAALPY")
|
@pytest.mark.xfail("env.PYPY")
|
||||||
def test_class_refcount():
|
def test_class_refcount():
|
||||||
"""Instances must correctly increase/decrease the reference count of their types (#1029)"""
|
"""Instances must correctly increase/decrease the reference count of their types (#1029)"""
|
||||||
from sys import getrefcount
|
from sys import getrefcount
|
||||||
|
|
@ -519,31 +501,3 @@ def test_pr4220_tripped_over_this():
|
||||||
m.Empty0().get_msg()
|
m.Empty0().get_msg()
|
||||||
== "This is really only meant to exercise successful compilation."
|
== "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.
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue