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 <rgrossekunst@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Rosdf
2025-06-11 20:32:26 +04:00
committed by GitHub
parent 513d1f962e
commit 7ed76e2d50
3 changed files with 35 additions and 3 deletions

View File

@@ -37,7 +37,7 @@ private:
PYBIND11_NAMESPACE_BEGIN(initimpl)
inline void no_nullptr(void *ptr) {
inline void no_nullptr(const void *ptr) {
if (!ptr) {
throw type_error("pybind11::init(): factory function returned nullptr");
}
@@ -61,7 +61,7 @@ bool is_alias(Cpp<Class> *ptr) {
}
// Failing fallback version of the above for a no-alias class (always returns false)
template <typename /*Class*/>
constexpr bool is_alias(void *) {
constexpr bool is_alias(const void *) {
return false;
}
@@ -167,7 +167,12 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
"is not an alias instance");
}
v_h.value_ptr() = ptr;
// Cast away constness to store in void* storage.
// The value_and_holder storage is fundamentally untyped (void**), so we lose
// const-correctness here by design. The const qualifier will be restored
// when the pointer is later retrieved and cast back to the original type.
// This explicit const_cast makes the const-removal clearly visible.
v_h.value_ptr() = const_cast<void *>(static_cast<const void *>(ptr));
v_h.type->init_instance(v_h.inst, &holder);
}

View File

@@ -69,6 +69,20 @@ public:
T **operator&() { throw std::logic_error("Call of overloaded operator& is not expected"); }
};
// Simple custom holder that imitates smart pointer, that always stores cpointer to const
template <class T>
class const_only_shared_ptr {
std::shared_ptr<const T> ptr_;
public:
const_only_shared_ptr() = default;
explicit const_only_shared_ptr(const T *ptr) : ptr_(ptr) {}
const T *get() const { return ptr_.get(); }
private:
// for demonstration purpose only, this imitates smart pointer with a const-only pointer
};
// Custom object with builtin reference counting (see 'object.h' for the implementation)
class MyObject1 : public Object {
public:
@@ -283,6 +297,7 @@ struct holder_helper<ref<T>> {
// Make pybind aware of the ref-counted wrapper type (s):
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true)
PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr<T>, true)
// The following is not required anymore for std::shared_ptr, but it should compile without error:
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>)
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>)
@@ -397,6 +412,11 @@ TEST_SUBMODULE(smart_ptr, m) {
m.def("print_myobject2_4",
[](const std::shared_ptr<MyObject2> *obj) { py::print((*obj)->toString()); });
m.def("make_myobject2_3",
[](int val) { return const_only_shared_ptr<MyObject2>(new MyObject2(val)); });
m.def("print_myobject2_5",
[](const const_only_shared_ptr<MyObject2> &obj) { py::print(obj.get()->toString()); });
py::class_<MyObject3, std::shared_ptr<MyObject3>>(m, "MyObject3").def(py::init<int>());
m.def("make_myobject3_1", []() { return new MyObject3(8); });
m.def("make_myobject3_2", []() { return std::make_shared<MyObject3>(9); });

View File

@@ -350,3 +350,10 @@ def test_move_only_holder_caster_shared_ptr_with_smart_holder_support_enabled():
assert (
m.return_std_unique_ptr_example_drvd() == "move_only_holder_caster_traits_test"
)
def test_const_only_holder(capture):
o = m.make_myobject2_3(4)
with capture:
m.print_myobject2_5(o)
assert capture == "MyObject2[4]\n"