Skip to content

Commit 53f48d3

Browse files
authored
Merge pull request google#30018 from rwgk/pywrapcc_merge_sh
git merge smart_holder & test_class_sh_property_stl adjustments
2 parents 966788e + 1bcd8e8 commit 53f48d3

File tree

8 files changed

+192
-18
lines changed

8 files changed

+192
-18
lines changed

include/pybind11/detail/descr.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,26 @@ constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
238238
return descr;
239239
}
240240

241+
#if defined(PYBIND11_CPP17)
242+
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
243+
constexpr descr<N1 + N2 + 2, Ts1..., Ts2...> operator,(const descr<N1, Ts1...> &a,
244+
const descr<N2, Ts2...> &b) {
245+
// Ensure that src_loc of existing descr is used.
246+
return a + const_name(", ", src_loc{nullptr, 0}) + b;
247+
}
248+
249+
template <size_t N, typename... Ts, typename... Args>
250+
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args) {
251+
return (d, ..., args);
252+
}
253+
#else
241254
template <size_t N, typename... Ts, typename... Args>
242255
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
243256
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
244257
// Ensure that src_loc of existing descr is used.
245258
return d + const_name(", ", src_loc{nullptr, 0}) + concat(args...);
246259
}
260+
#endif
247261

248262
template <size_t N, typename... Ts>
249263
constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,18 @@ struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag
408408
}
409409
};
410410

411+
struct shared_ptr_parent_life_support {
412+
PyObject *parent;
413+
explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} {
414+
Py_INCREF(parent);
415+
}
416+
// NOLINTNEXTLINE(readability-make-member-function-const)
417+
void operator()(void *) {
418+
gil_scoped_acquire gil;
419+
Py_DECREF(parent);
420+
}
421+
};
422+
411423
struct shared_ptr_trampoline_self_life_support {
412424
PyObject *self;
413425
explicit shared_ptr_trampoline_self_life_support(instance *inst)
@@ -462,12 +474,23 @@ struct smart_holder_type_caster_load {
462474
return *raw_ptr;
463475
}
464476

465-
std::shared_ptr<T> loaded_as_shared_ptr() const {
477+
std::shared_ptr<T> make_shared_ptr_with_responsible_parent(handle parent) const {
478+
return std::shared_ptr<T>(loaded_as_raw_ptr_unowned(),
479+
shared_ptr_parent_life_support(parent.ptr()));
480+
}
481+
482+
std::shared_ptr<T> loaded_as_shared_ptr(handle responsible_parent = nullptr) const {
466483
if (load_impl.unowned_void_ptr_from_void_ptr_capsule) {
484+
if (responsible_parent) {
485+
return make_shared_ptr_with_responsible_parent(responsible_parent);
486+
}
467487
throw cast_error("Unowned pointer from a void pointer capsule cannot be converted to a"
468488
" std::shared_ptr.");
469489
}
470490
if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr) {
491+
if (responsible_parent) {
492+
return make_shared_ptr_with_responsible_parent(responsible_parent);
493+
}
471494
throw cast_error("Unowned pointer from direct conversion cannot be converted to a"
472495
" std::shared_ptr.");
473496
}
@@ -478,6 +501,9 @@ struct smart_holder_type_caster_load {
478501
holder_type &hld = holder();
479502
hld.ensure_is_not_disowned("loaded_as_shared_ptr");
480503
if (hld.vptr_is_using_noop_deleter) {
504+
if (responsible_parent) {
505+
return make_shared_ptr_with_responsible_parent(responsible_parent);
506+
}
481507
throw std::runtime_error("Non-owning holder (loaded_as_shared_ptr).");
482508
}
483509
auto *void_raw_ptr = hld.template as_raw_ptr_unowned<void>();
@@ -579,6 +605,17 @@ struct smart_holder_type_caster_load {
579605
return result;
580606
}
581607

608+
// This function will succeed even if the `responsible_parent` does not own the
609+
// wrapped C++ object directly.
610+
// It is the responsibility of the caller to ensure that the `responsible_parent`
611+
// has a `keep_alive` relationship with the owner of the wrapped C++ object, or
612+
// that the wrapped C++ object lives for the duration of the process.
613+
static std::shared_ptr<T> shared_ptr_from_python(handle responsible_parent) {
614+
smart_holder_type_caster_load<T> loader;
615+
loader.load(responsible_parent, false);
616+
return loader.loaded_as_shared_ptr(responsible_parent);
617+
}
618+
582619
private:
583620
modified_type_caster_generic_load_impl load_impl;
584621

include/pybind11/pybind11.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,8 @@ struct property_cpp_function<
15941594
template <typename PM, must_be_member_function_pointer<PM> = 0>
15951595
static cpp_function readonly(PM pm, const handle &hdl) {
15961596
return cpp_function(
1597-
[pm](const std::shared_ptr<T> &c_sp) -> std::shared_ptr<drp> {
1597+
[pm](handle c_hdl) -> std::shared_ptr<drp> {
1598+
std::shared_ptr<T> c_sp = detail::type_caster<T>::shared_ptr_from_python(c_hdl);
15981599
D ptr = (*c_sp).*pm;
15991600
return std::shared_ptr<drp>(c_sp, ptr);
16001601
},
@@ -1630,8 +1631,8 @@ struct property_cpp_function<
16301631
template <typename PM, must_be_member_function_pointer<PM> = 0>
16311632
static cpp_function readonly(PM pm, const handle &hdl) {
16321633
return cpp_function(
1633-
[pm](const std::shared_ptr<T> &c_sp)
1634-
-> std::shared_ptr<typename std::add_const<D>::type> {
1634+
[pm](handle c_hdl) -> std::shared_ptr<typename std::add_const<D>::type> {
1635+
std::shared_ptr<T> c_sp = detail::type_caster<T>::shared_ptr_from_python(c_hdl);
16351636
return std::shared_ptr<typename std::add_const<D>::type>(c_sp, &(c_sp.get()->*pm));
16361637
},
16371638
is_method(hdl));
@@ -1640,7 +1641,8 @@ struct property_cpp_function<
16401641
template <typename PM, must_be_member_function_pointer<PM> = 0>
16411642
static cpp_function read(PM pm, const handle &hdl) {
16421643
return cpp_function(
1643-
[pm](const std::shared_ptr<T> &c_sp) -> std::shared_ptr<D> {
1644+
[pm](handle c_hdl) -> std::shared_ptr<D> {
1645+
std::shared_ptr<T> c_sp = detail::type_caster<T>::shared_ptr_from_python(c_hdl);
16441646
return std::shared_ptr<D>(c_sp, &(c_sp.get()->*pm));
16451647
},
16461648
is_method(hdl));
@@ -1677,7 +1679,10 @@ struct property_cpp_function<
16771679
template <typename PM, must_be_member_function_pointer<PM> = 0>
16781680
static cpp_function read(PM pm, const handle &hdl) {
16791681
return cpp_function(
1680-
[pm](const std::shared_ptr<T> &c_sp) -> D { return D{std::move(c_sp.get()->*pm)}; },
1682+
[pm](handle c_hdl) -> D {
1683+
std::shared_ptr<T> c_sp = detail::type_caster<T>::shared_ptr_from_python(c_hdl);
1684+
return D{std::move(c_sp.get()->*pm)};
1685+
},
16811686
is_method(hdl));
16821687
}
16831688

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ set(PYBIND11_TEST_FILES
130130
test_class_sh_mi_thunks
131131
test_class_sh_module_local.py
132132
test_class_sh_property
133+
test_class_sh_property_non_owning
133134
test_class_sh_property_stl
134135
test_class_sh_shared_ptr_copy_move
135136
test_class_sh_trampoline_basic
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include "pybind11/smart_holder.h"
2+
#include "pybind11_tests.h"
3+
4+
#include <cstddef>
5+
#include <vector>
6+
7+
namespace test_class_sh_property_non_owning {
8+
9+
struct CoreField {
10+
explicit CoreField(int int_value = -99) : int_value{int_value} {}
11+
int int_value;
12+
};
13+
14+
struct DataField {
15+
DataField(int i_value, int i_shared, int i_unique)
16+
: core_fld_value{i_value}, core_fld_shared_ptr{new CoreField{i_shared}},
17+
core_fld_raw_ptr{core_fld_shared_ptr.get()}, core_fld_unique_ptr{
18+
new CoreField{i_unique}} {}
19+
CoreField core_fld_value;
20+
std::shared_ptr<CoreField> core_fld_shared_ptr;
21+
CoreField *core_fld_raw_ptr;
22+
std::unique_ptr<CoreField> core_fld_unique_ptr;
23+
};
24+
25+
struct DataFieldsHolder {
26+
private:
27+
std::vector<DataField> vec;
28+
29+
public:
30+
explicit DataFieldsHolder(std::size_t vec_size) {
31+
for (std::size_t i = 0; i < vec_size; i++) {
32+
int i11 = static_cast<int>(i) * 11;
33+
vec.emplace_back(13 + i11, 14 + i11, 15 + i11);
34+
}
35+
}
36+
37+
DataField *vec_at(std::size_t index) {
38+
if (index >= vec.size()) {
39+
return nullptr;
40+
}
41+
return &vec[index];
42+
}
43+
};
44+
45+
} // namespace test_class_sh_property_non_owning
46+
47+
using namespace test_class_sh_property_non_owning;
48+
49+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(CoreField)
50+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataField)
51+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataFieldsHolder)
52+
53+
TEST_SUBMODULE(class_sh_property_non_owning, m) {
54+
py::classh<CoreField>(m, "CoreField").def_readwrite("int_value", &CoreField::int_value);
55+
56+
py::classh<DataField>(m, "DataField")
57+
.def_readonly("core_fld_value_ro", &DataField::core_fld_value)
58+
.def_readwrite("core_fld_value_rw", &DataField::core_fld_value)
59+
.def_readonly("core_fld_shared_ptr_ro", &DataField::core_fld_shared_ptr)
60+
.def_readwrite("core_fld_shared_ptr_rw", &DataField::core_fld_shared_ptr)
61+
.def_readonly("core_fld_raw_ptr_ro", &DataField::core_fld_raw_ptr)
62+
.def_readwrite("core_fld_raw_ptr_rw", &DataField::core_fld_raw_ptr)
63+
.def_readwrite("core_fld_unique_ptr_rw", &DataField::core_fld_unique_ptr);
64+
65+
py::classh<DataFieldsHolder>(m, "DataFieldsHolder")
66+
.def(py::init<std::size_t>())
67+
.def("vec_at", &DataFieldsHolder::vec_at, py::return_value_policy::reference_internal);
68+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
3+
from pybind11_tests import class_sh_property_non_owning as m
4+
5+
6+
@pytest.mark.parametrize("persistent_holder", [True, False])
7+
@pytest.mark.parametrize(
8+
("core_fld", "expected"),
9+
[
10+
("core_fld_value_ro", (13, 24)),
11+
("core_fld_value_rw", (13, 24)),
12+
("core_fld_shared_ptr_ro", (14, 25)),
13+
("core_fld_shared_ptr_rw", (14, 25)),
14+
("core_fld_raw_ptr_ro", (14, 25)),
15+
("core_fld_raw_ptr_rw", (14, 25)),
16+
("core_fld_unique_ptr_rw", (15, 26)),
17+
],
18+
)
19+
def test_core_fld_common(core_fld, expected, persistent_holder):
20+
if persistent_holder:
21+
h = m.DataFieldsHolder(2)
22+
for i, exp in enumerate(expected):
23+
c = getattr(h.vec_at(i), core_fld)
24+
assert c.int_value == exp
25+
else:
26+
for i, exp in enumerate(expected):
27+
c = getattr(m.DataFieldsHolder(2).vec_at(i), core_fld)
28+
assert c.int_value == exp

tests/test_class_sh_property_stl.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "pybind11_tests.h"
55

6+
#include <cstddef>
67
#include <vector>
78

89
namespace test_class_sh_property_stl {
@@ -20,6 +21,11 @@ struct FieldHolder {
2021
struct VectorFieldHolder {
2122
std::vector<FieldHolder> vec_fld_hld;
2223
VectorFieldHolder() { vec_fld_hld.push_back(FieldHolder{Field{300}}); }
24+
void reset_at(std::size_t index, int wrapped_int) {
25+
if (index < vec_fld_hld.size()) {
26+
vec_fld_hld[index].fld.wrapped_int = wrapped_int;
27+
}
28+
}
2329
};
2430

2531
} // namespace test_class_sh_property_stl
@@ -37,6 +43,7 @@ TEST_SUBMODULE(class_sh_property_stl, m) {
3743

3844
py::classh<VectorFieldHolder>(m, "VectorFieldHolder")
3945
.def(py::init<>())
46+
.def("reset_at", &VectorFieldHolder::reset_at)
4047
.def_readwrite("vec_fld_hld_ref", &VectorFieldHolder::vec_fld_hld)
4148
.def_readwrite("vec_fld_hld_cpy",
4249
&VectorFieldHolder::vec_fld_hld,
Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
import pytest
2-
31
from pybind11_tests import class_sh_property_stl as m
42

53

6-
def test_vec_fld_hld_ref():
7-
vfh = m.VectorFieldHolder()
8-
vfh0 = vfh.vec_fld_hld_ref[0]
9-
with pytest.raises(RuntimeError) as exc_info:
10-
vfh0.fld
11-
assert str(exc_info.value) == "Non-owning holder (loaded_as_shared_ptr)."
4+
def test_cpy_after_ref():
5+
h = m.VectorFieldHolder()
6+
c1 = h.vec_fld_hld_cpy
7+
c2 = h.vec_fld_hld_cpy
8+
assert repr(c2) != repr(c1)
9+
r1 = h.vec_fld_hld_ref
10+
assert repr(r1) != repr(c2)
11+
assert repr(r1) != repr(c1)
12+
r2 = h.vec_fld_hld_ref
13+
assert repr(r2) == repr(r1)
14+
c3 = h.vec_fld_hld_cpy
15+
assert repr(c3) == repr(r1) # SURPRISE!
16+
17+
18+
def test_persistent_holder():
19+
h = m.VectorFieldHolder()
20+
c0 = h.vec_fld_hld_cpy[0] # Must be first. See test_cpy_after_ref().
21+
r0 = h.vec_fld_hld_ref[0] # Must be second.
22+
assert c0.fld.wrapped_int == 300
23+
assert r0.fld.wrapped_int == 300
24+
h.reset_at(0, 400)
25+
assert c0.fld.wrapped_int == 300
26+
assert r0.fld.wrapped_int == 400
1227

1328

14-
def test_vec_fld_hld_cpy():
15-
vfh = m.VectorFieldHolder()
16-
vfh0 = vfh.vec_fld_hld_cpy[0]
17-
assert vfh0.fld.wrapped_int == 300
29+
def test_temporary_holder_keep_alive():
30+
r0 = m.VectorFieldHolder().vec_fld_hld_ref[0]
31+
assert r0.fld.wrapped_int == 300

0 commit comments

Comments
 (0)