Skip to content

Commit d28c3a5

Browse files
authored
[smart_holder] .def_readonly, .def_readwrite adaptors (continuation of PR #3581). (#3844)
* Transferred net diff from PR #3581, as-is. * Automatic `pre-commit run --all-files` fixes. NO manual changes. * Removing trailing `//` (originally added to manipulate clang-format), as suggested by @charlesbeattie back in Jan/Feb under PR #3581. * Renaming `xetter_cpp_function` to `property_cpp_function` as suggested by @rainwoodman * Fully explain the terse variable naming scheme in test_class_sh_property (as suggested by @rainwoodman) * Also use parametrize for readonly, readwrite (as suggested by @rainwoodman) * Apply change suggested by @Skylion007 (with clang-format).
1 parent aebdf00 commit d28c3a5

File tree

6 files changed

+419
-5
lines changed

6 files changed

+419
-5
lines changed

.codespell-ignorelines

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
1111
valu = other.valu;
1212
with pytest.raises(ValueError) as excinfo:
1313
with pytest.raises(ValueError) as exc_info:
14+
// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,

include/pybind11/detail/smart_holder_sfinae_hooks_only.h

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

77
#include "common.h"
88

9+
#include <memory>
910
#include <type_traits>
1011

1112
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
@@ -29,5 +30,17 @@ struct smart_holder_type_caster_base_tag {};
2930
template <typename T>
3031
struct type_uses_smart_holder_type_caster;
3132

33+
// Simple helpers that may eventually be a better fit for another header file:
34+
35+
template <typename T>
36+
struct is_std_unique_ptr : std::false_type {};
37+
template <typename T, typename D>
38+
struct is_std_unique_ptr<std::unique_ptr<T, D>> : std::true_type {};
39+
40+
template <typename T>
41+
struct is_std_shared_ptr : std::false_type {};
42+
template <typename T>
43+
struct is_std_shared_ptr<std::shared_ptr<T>> : std::true_type {};
44+
3245
PYBIND11_NAMESPACE_END(detail)
3346
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/pybind11.h

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,154 @@ using default_holder_type = smart_holder;
15141514

15151515
#endif
15161516

1517+
// Helper for the property_cpp_function static member functions below.
1518+
// The only purpose of these functions is to support .def_readonly & .def_readwrite.
1519+
// In this context, the PM template parameter is certain to be a Pointer to a Member.
1520+
// The main purpose of must_be_member_function_pointer is to make this obvious, and to guard
1521+
// against accidents. As a side-effect, it also explains why the syntactical overhead for
1522+
// perfect forwarding is not needed.
1523+
template <typename PM>
1524+
using must_be_member_function_pointer
1525+
= detail::enable_if_t<std::is_member_pointer<PM>::value, int>;
1526+
1527+
// Note that property_cpp_function is intentionally in the main pybind11 namespace,
1528+
// because user-defined specializations could be useful.
1529+
1530+
// Classic (non-smart_holder) implementations for .def_readonly and .def_readwrite
1531+
// getter and setter functions.
1532+
// WARNING: This classic implementation can lead to dangling pointers for raw pointer members.
1533+
// See test_ptr() in tests/test_class_sh_property.py
1534+
// This implementation works as-is (and safely) for smart_holder std::shared_ptr members.
1535+
template <typename T, typename D, typename SFINAE = void>
1536+
struct property_cpp_function {
1537+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1538+
static cpp_function readonly(PM pm, const handle &hdl) {
1539+
return cpp_function([pm](const T &c) -> const D & { return c.*pm; }, is_method(hdl));
1540+
}
1541+
1542+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1543+
static cpp_function read(PM pm, const handle &hdl) {
1544+
return readonly(pm, hdl);
1545+
}
1546+
1547+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1548+
static cpp_function write(PM pm, const handle &hdl) {
1549+
return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl));
1550+
}
1551+
};
1552+
1553+
// smart_holder specializations for raw pointer members.
1554+
// WARNING: Like the classic implementation, this implementation can lead to dangling pointers.
1555+
// See test_ptr() in tests/test_class_sh_property.py
1556+
// However, the read functions return a shared_ptr to the member, emulating the PyCLIF approach:
1557+
// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233
1558+
// This prevents disowning of the Python object owning the raw pointer member.
1559+
template <typename T, typename D>
1560+
struct property_cpp_function<
1561+
T,
1562+
D,
1563+
detail::enable_if_t<detail::all_of<detail::type_uses_smart_holder_type_caster<T>,
1564+
detail::type_uses_smart_holder_type_caster<D>,
1565+
std::is_pointer<D>>::value>> {
1566+
1567+
using drp = typename std::remove_pointer<D>::type;
1568+
1569+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1570+
static cpp_function readonly(PM pm, const handle &hdl) {
1571+
return cpp_function(
1572+
[pm](const std::shared_ptr<T> &c_sp) -> std::shared_ptr<drp> {
1573+
D ptr = (*c_sp).*pm;
1574+
return std::shared_ptr<drp>(c_sp, ptr);
1575+
},
1576+
is_method(hdl));
1577+
}
1578+
1579+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1580+
static cpp_function read(PM pm, const handle &hdl) {
1581+
return readonly(pm, hdl);
1582+
}
1583+
1584+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1585+
static cpp_function write(PM pm, const handle &hdl) {
1586+
return cpp_function([pm](T &c, D value) { c.*pm = std::forward<D>(value); },
1587+
is_method(hdl));
1588+
}
1589+
};
1590+
1591+
// smart_holder specializations for members held by-value.
1592+
// The read functions return a shared_ptr to the member, emulating the PyCLIF approach:
1593+
// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233
1594+
// This prevents disowning of the Python object owning the member.
1595+
template <typename T, typename D>
1596+
struct property_cpp_function<
1597+
T,
1598+
D,
1599+
detail::enable_if_t<detail::all_of<detail::type_uses_smart_holder_type_caster<T>,
1600+
detail::type_uses_smart_holder_type_caster<D>,
1601+
detail::none_of<std::is_pointer<D>,
1602+
detail::is_std_unique_ptr<D>,
1603+
detail::is_std_shared_ptr<D>>>::value>> {
1604+
1605+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1606+
static cpp_function readonly(PM pm, const handle &hdl) {
1607+
return cpp_function(
1608+
[pm](const std::shared_ptr<T> &c_sp)
1609+
-> std::shared_ptr<typename std::add_const<D>::type> {
1610+
return std::shared_ptr<typename std::add_const<D>::type>(c_sp, &(c_sp.get()->*pm));
1611+
},
1612+
is_method(hdl));
1613+
}
1614+
1615+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1616+
static cpp_function read(PM pm, const handle &hdl) {
1617+
return cpp_function(
1618+
[pm](const std::shared_ptr<T> &c_sp) -> std::shared_ptr<D> {
1619+
return std::shared_ptr<D>(c_sp, &(c_sp.get()->*pm));
1620+
},
1621+
is_method(hdl));
1622+
}
1623+
1624+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1625+
static cpp_function write(PM pm, const handle &hdl) {
1626+
return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl));
1627+
}
1628+
};
1629+
1630+
// smart_holder specializations for std::unique_ptr members.
1631+
// read disowns the member unique_ptr.
1632+
// write disowns the passed Python object.
1633+
// readonly is disabled (static_assert) because there is no safe & intuitive way to make the member
1634+
// accessible as a Python object without disowning the member unique_ptr. A .def_readonly disowning
1635+
// the unique_ptr member is deemed highly prone to misunderstandings.
1636+
template <typename T, typename D>
1637+
struct property_cpp_function<
1638+
T,
1639+
D,
1640+
detail::enable_if_t<detail::all_of<
1641+
detail::type_uses_smart_holder_type_caster<T>,
1642+
detail::is_std_unique_ptr<D>,
1643+
detail::type_uses_smart_holder_type_caster<typename D::element_type>>::value>> {
1644+
1645+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1646+
static cpp_function readonly(PM, const handle &) {
1647+
static_assert(!detail::is_std_unique_ptr<D>::value,
1648+
"def_readonly cannot be used for std::unique_ptr members.");
1649+
return cpp_function{}; // Unreachable.
1650+
}
1651+
1652+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1653+
static cpp_function read(PM pm, const handle &hdl) {
1654+
return cpp_function(
1655+
[pm](const std::shared_ptr<T> &c_sp) -> D { return D{std::move(c_sp.get()->*pm)}; },
1656+
is_method(hdl));
1657+
}
1658+
1659+
template <typename PM, must_be_member_function_pointer<PM> = 0>
1660+
static cpp_function write(PM pm, const handle &hdl) {
1661+
return cpp_function([pm](T &c, D &&value) { c.*pm = std::move(value); }, is_method(hdl));
1662+
}
1663+
};
1664+
15171665
template <typename type_, typename... options>
15181666
class class_ : public detail::generic_type {
15191667
template <typename T>
@@ -1727,18 +1875,22 @@ class class_ : public detail::generic_type {
17271875
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
17281876
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
17291877
"def_readwrite() requires a class member (or base class member)");
1730-
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)),
1731-
fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
1732-
def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
1878+
def_property(name,
1879+
property_cpp_function<type, D>::read(pm, *this),
1880+
property_cpp_function<type, D>::write(pm, *this),
1881+
return_value_policy::reference_internal,
1882+
extra...);
17331883
return *this;
17341884
}
17351885

17361886
template <typename C, typename D, typename... Extra>
17371887
class_ &def_readonly(const char *name, const D C::*pm, const Extra &...extra) {
17381888
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
17391889
"def_readonly() requires a class member (or base class member)");
1740-
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this));
1741-
def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
1890+
def_property_readonly(name,
1891+
property_cpp_function<type, D>::readonly(pm, *this),
1892+
return_value_policy::reference_internal,
1893+
extra...);
17421894
return *this;
17431895
}
17441896

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ set(PYBIND11_TEST_FILES
128128
test_class_sh_factory_constructors
129129
test_class_sh_inheritance
130130
test_class_sh_module_local.py
131+
test_class_sh_property
131132
test_class_sh_shared_ptr_copy_move
132133
test_class_sh_trampoline_basic
133134
test_class_sh_trampoline_self_life_support

tests/test_class_sh_property.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// The compact 4-character naming matches that in test_class_sh_basic.cpp
2+
// Variable names are intentionally terse, to not distract from the more important C++ type names:
3+
// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,
4+
// sh = shared_ptr, uq = unique_ptr.
5+
6+
#include "pybind11/smart_holder.h"
7+
#include "pybind11_tests.h"
8+
9+
#include <memory>
10+
11+
namespace test_class_sh_property {
12+
13+
struct ClassicField {
14+
int num = -88;
15+
};
16+
17+
struct ClassicOuter {
18+
ClassicField *m_mptr = nullptr;
19+
const ClassicField *m_cptr = nullptr;
20+
};
21+
22+
struct Field {
23+
int num = -99;
24+
};
25+
26+
struct Outer {
27+
Field m_valu;
28+
Field *m_mptr = nullptr;
29+
const Field *m_cptr = nullptr;
30+
std::unique_ptr<Field> m_uqmp;
31+
std::unique_ptr<const Field> m_uqcp;
32+
std::shared_ptr<Field> m_shmp;
33+
std::shared_ptr<const Field> m_shcp;
34+
};
35+
36+
inline void DisownOuter(std::unique_ptr<Outer>) {}
37+
38+
} // namespace test_class_sh_property
39+
40+
PYBIND11_TYPE_CASTER_BASE_HOLDER(test_class_sh_property::ClassicField,
41+
std::unique_ptr<test_class_sh_property::ClassicField>)
42+
PYBIND11_TYPE_CASTER_BASE_HOLDER(test_class_sh_property::ClassicOuter,
43+
std::unique_ptr<test_class_sh_property::ClassicOuter>)
44+
45+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::Field)
46+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::Outer)
47+
48+
TEST_SUBMODULE(class_sh_property, m) {
49+
using namespace test_class_sh_property;
50+
51+
py::class_<ClassicField, std::unique_ptr<ClassicField>>(m, "ClassicField")
52+
.def(py::init<>())
53+
.def_readwrite("num", &ClassicField::num);
54+
55+
py::class_<ClassicOuter, std::unique_ptr<ClassicOuter>>(m, "ClassicOuter")
56+
.def(py::init<>())
57+
.def_readonly("m_mptr_readonly", &ClassicOuter::m_mptr)
58+
.def_readwrite("m_mptr_readwrite", &ClassicOuter::m_mptr)
59+
.def_readwrite("m_cptr_readonly", &ClassicOuter::m_cptr)
60+
.def_readwrite("m_cptr_readwrite", &ClassicOuter::m_cptr);
61+
62+
py::classh<Field>(m, "Field").def(py::init<>()).def_readwrite("num", &Field::num);
63+
64+
py::classh<Outer>(m, "Outer")
65+
.def(py::init<>())
66+
67+
.def_readonly("m_valu_readonly", &Outer::m_valu)
68+
.def_readwrite("m_valu_readwrite", &Outer::m_valu)
69+
70+
.def_readonly("m_mptr_readonly", &Outer::m_mptr)
71+
.def_readwrite("m_mptr_readwrite", &Outer::m_mptr)
72+
.def_readonly("m_cptr_readonly", &Outer::m_cptr)
73+
.def_readwrite("m_cptr_readwrite", &Outer::m_cptr)
74+
75+
// .def_readonly("m_uqmp_readonly", &Outer::m_uqmp) // Custom compilation Error.
76+
.def_readwrite("m_uqmp_readwrite", &Outer::m_uqmp)
77+
// .def_readonly("m_uqcp_readonly", &Outer::m_uqcp) // Custom compilation Error.
78+
.def_readwrite("m_uqcp_readwrite", &Outer::m_uqcp)
79+
80+
.def_readwrite("m_shmp_readonly", &Outer::m_shmp)
81+
.def_readwrite("m_shmp_readwrite", &Outer::m_shmp)
82+
.def_readwrite("m_shcp_readonly", &Outer::m_shcp)
83+
.def_readwrite("m_shcp_readwrite", &Outer::m_shcp);
84+
85+
m.def("DisownOuter", DisownOuter);
86+
}

0 commit comments

Comments
 (0)