Skip to content

Fix build failure when shared_ptr<T> points to private std::enable_shared_from_this base #5590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion include/pybind11/detail/struct_smart_holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 (generated by ChatGPT) uses SFINAE to skip enable_shared_from_this checks when the
// base is inaccessible (e.g. private inheritance).
template <typename T>
static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this<T> *) {
static auto type_has_shared_from_this(const T *ptr)
-> decltype(static_cast<const std::enable_shared_from_this<T> *>(ptr), true) {
return true;
}

// Inaccessible base → substitution failure → fallback overload selected
template <typename T>
static constexpr bool type_has_shared_from_this(const void *) {
return false;
}

struct guarded_delete {
std::weak_ptr<void> released_ptr; // Trick to keep the smart_holder memory footprint small.
std::function<void(void *)> del_fun; // Rare case.
Expand Down
4 changes: 4 additions & 0 deletions tests/pure_cpp/smart_holder_poc.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ namespace pybindit {
namespace memory {
namespace smart_holder_poc { // Proof-of-Concept implementations.

struct PrivateESFT : private std::enable_shared_from_this<PrivateESFT> {};
static_assert(!pybindit::memory::type_has_shared_from_this(static_cast<PrivateESFT *>(nullptr)),
"should detect inaccessible base");

template <typename T>
T &as_lvalue_ref(const smart_holder &hld) {
static const char *context = "as_lvalue_ref";
Expand Down
9 changes: 9 additions & 0 deletions tests/test_smart_ptr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,4 +473,13 @@ TEST_SUBMODULE(smart_ptr, m) {
}
return list;
});

class PrivateESFT : /* implicit private */ std::enable_shared_from_this<PrivateESFT> {};
struct ContainerUsingPrivateESFT {
std::shared_ptr<PrivateESFT> ptr;
};
py::class_<ContainerUsingPrivateESFT>(m, "ContainerUsingPrivateESFT")
.def(py::init<>())
.def_readwrite("ptr",
&ContainerUsingPrivateESFT::ptr); // <- access ESFT through shared_ptr
}
12 changes: 12 additions & 0 deletions tests/test_smart_ptr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> member where T privately inherits
# enable_shared_from_this<T> 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
Loading