diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index aa993f495d..913515c17b 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -37,7 +37,7 @@ class type_caster { 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 *ptr) { } // Failing fallback version of the above for a no-alias class (always returns false) template -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 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(static_cast(ptr)); v_h.type->init_instance(v_h.inst, &holder); } diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 10a65f535f..7d009c9a84 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -69,6 +69,20 @@ class unique_ptr_with_addressof_operator { 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 const_only_shared_ptr { + std::shared_ptr 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> { // Make pybind aware of the ref-counted wrapper type (s): PYBIND11_DECLARE_HOLDER_TYPE(T, ref, true) +PYBIND11_DECLARE_HOLDER_TYPE(T, const_only_shared_ptr, 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) PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr) @@ -397,6 +412,11 @@ TEST_SUBMODULE(smart_ptr, m) { m.def("print_myobject2_4", [](const std::shared_ptr *obj) { py::print((*obj)->toString()); }); + m.def("make_myobject2_3", + [](int val) { return const_only_shared_ptr(new MyObject2(val)); }); + m.def("print_myobject2_5", + [](const const_only_shared_ptr &obj) { py::print(obj.get()->toString()); }); + py::class_>(m, "MyObject3").def(py::init()); m.def("make_myobject3_1", []() { return new MyObject3(8); }); m.def("make_myobject3_2", []() { return std::make_shared(9); }); diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index 3c20458755..e1d51ca06c 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -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"