diff --git a/include/pybind11/detail/struct_smart_holder.h b/include/pybind11/detail/struct_smart_holder.h index 980fc3699b..84ad8e00f6 100644 --- a/include/pybind11/detail/struct_smart_holder.h +++ b/include/pybind11/detail/struct_smart_holder.h @@ -62,13 +62,23 @@ High-level aspects: namespace pybindit { namespace memory { +// Default fallback. static constexpr bool type_has_shared_from_this(...) { return false; } +// This overload uses SFINAE to skip enable_shared_from_this checks when the +// base is inaccessible (e.g. private inheritance). template -static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this *) { +static auto type_has_shared_from_this(const T *ptr) + -> decltype(static_cast *>(ptr), true) { return true; } +// Inaccessible base → substitution failure → fallback overload selected +template +static constexpr bool type_has_shared_from_this(const void *) { + return false; +} + struct guarded_delete { std::weak_ptr released_ptr; // Trick to keep the smart_holder memory footprint small. std::function del_fun; // Rare case. diff --git a/tests/pure_cpp/smart_holder_poc.h b/tests/pure_cpp/smart_holder_poc.h index 320311b7d6..20067b43ee 100644 --- a/tests/pure_cpp/smart_holder_poc.h +++ b/tests/pure_cpp/smart_holder_poc.h @@ -10,6 +10,10 @@ namespace pybindit { namespace memory { namespace smart_holder_poc { // Proof-of-Concept implementations. +struct PrivateESFT : private std::enable_shared_from_this {}; +static_assert(!pybindit::memory::type_has_shared_from_this(static_cast(nullptr)), + "should detect inaccessible base"); + template T &as_lvalue_ref(const smart_holder &hld) { static const char *context = "as_lvalue_ref"; diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 4ab43953f1..7c9f35371f 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -473,4 +473,13 @@ TEST_SUBMODULE(smart_ptr, m) { } return list; }); + + class PrivateESFT : /* implicit private */ std::enable_shared_from_this {}; + struct ContainerUsingPrivateESFT { + std::shared_ptr ptr; + }; + py::class_(m, "ContainerUsingPrivateESFT") + .def(py::init<>()) + .def_readwrite("ptr", + &ContainerUsingPrivateESFT::ptr); // <- access ESFT through shared_ptr } diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index ab8a1ce62e..0b6f9b57a2 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -326,3 +326,15 @@ def test_shared_ptr_gc(): pytest.gc_collect() for i, v in enumerate(el.get()): assert i == v.value() + + +def test_private_esft_tolerance(): + # Regression test: binding a shared_ptr member where T privately inherits + # enable_shared_from_this must not cause a C++ compile error. + c = m.ContainerUsingPrivateESFT() + # The ptr member is not actually usable in any way, but this is how the + # pybind11 v2 release series worked. + with pytest.raises(TypeError): + _ = c.ptr # getattr + with pytest.raises(TypeError): + c.ptr = None # setattr