Skip to content

Commit 7ed76e2

Browse files
Rosdfrwgkpre-commit-ci[bot]
authored
fix: add support for const-only smart pointers (#5718)
* add support for const pointers in smart pointers * use c++11 compatible code * add template parameter in test * Make the const-removal clearly visible. This simplifies the production code changes significantly. For background see: https://claude.ai/share/4085d9ab-a859-44cc-bb56-450e472f817a * test without leaks * add namespace for test * rename test * fix test compilation * using namespace test_const_only_smart_ptr; * fix smartptr in test * smaller test body * move test * style: pre-commit fixes --------- Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 513d1f9 commit 7ed76e2

File tree

3 files changed

+35
-3
lines changed

3 files changed

+35
-3
lines changed

include/pybind11/detail/init.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class type_caster<value_and_holder> {
3737

3838
PYBIND11_NAMESPACE_BEGIN(initimpl)
3939

40-
inline void no_nullptr(void *ptr) {
40+
inline void no_nullptr(const void *ptr) {
4141
if (!ptr) {
4242
throw type_error("pybind11::init(): factory function returned nullptr");
4343
}
@@ -61,7 +61,7 @@ bool is_alias(Cpp<Class> *ptr) {
6161
}
6262
// Failing fallback version of the above for a no-alias class (always returns false)
6363
template <typename /*Class*/>
64-
constexpr bool is_alias(void *) {
64+
constexpr bool is_alias(const void *) {
6565
return false;
6666
}
6767

@@ -167,7 +167,12 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
167167
"is not an alias instance");
168168
}
169169

170-
v_h.value_ptr() = ptr;
170+
// Cast away constness to store in void* storage.
171+
// The value_and_holder storage is fundamentally untyped (void**), so we lose
172+
// const-correctness here by design. The const qualifier will be restored
173+
// when the pointer is later retrieved and cast back to the original type.
174+
// This explicit const_cast makes the const-removal clearly visible.
175+
v_h.value_ptr() = const_cast<void *>(static_cast<const void *>(ptr));
171176
v_h.type->init_instance(v_h.inst, &holder);
172177
}
173178

tests/test_smart_ptr.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ class unique_ptr_with_addressof_operator {
6969
T **operator&() { throw std::logic_error("Call of overloaded operator& is not expected"); }
7070
};
7171

72+
// Simple custom holder that imitates smart pointer, that always stores cpointer to const
73+
template <class T>
74+
class const_only_shared_ptr {
75+
std::shared_ptr<const T> ptr_;
76+
77+
public:
78+
const_only_shared_ptr() = default;
79+
explicit const_only_shared_ptr(const T *ptr) : ptr_(ptr) {}
80+
const T *get() const { return ptr_.get(); }
81+
82+
private:
83+
// for demonstration purpose only, this imitates smart pointer with a const-only pointer
84+
};
85+
7286
// Custom object with builtin reference counting (see 'object.h' for the implementation)
7387
class MyObject1 : public Object {
7488
public:
@@ -283,6 +297,7 @@ struct holder_helper<ref<T>> {
283297

284298
// Make pybind aware of the ref-counted wrapper type (s):
285299
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true)
300+
PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr<T>, true)
286301
// The following is not required anymore for std::shared_ptr, but it should compile without error:
287302
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
288303
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>)
@@ -397,6 +412,11 @@ TEST_SUBMODULE(smart_ptr, m) {
397412
m.def("print_myobject2_4",
398413
[](const std::shared_ptr<MyObject2> *obj) { py::print((*obj)->toString()); });
399414

415+
m.def("make_myobject2_3",
416+
[](int val) { return const_only_shared_ptr<MyObject2>(new MyObject2(val)); });
417+
m.def("print_myobject2_5",
418+
[](const const_only_shared_ptr<MyObject2> &obj) { py::print(obj.get()->toString()); });
419+
400420
py::class_<MyObject3, std::shared_ptr<MyObject3>>(m, "MyObject3").def(py::init<int>());
401421
m.def("make_myobject3_1", []() { return new MyObject3(8); });
402422
m.def("make_myobject3_2", []() { return std::make_shared<MyObject3>(9); });

tests/test_smart_ptr.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,10 @@ def test_move_only_holder_caster_shared_ptr_with_smart_holder_support_enabled():
350350
assert (
351351
m.return_std_unique_ptr_example_drvd() == "move_only_holder_caster_traits_test"
352352
)
353+
354+
355+
def test_const_only_holder(capture):
356+
o = m.make_myobject2_3(4)
357+
with capture:
358+
m.print_myobject2_5(o)
359+
assert capture == "MyObject2[4]\n"

0 commit comments

Comments
 (0)