Skip to content

Commit 8726ed2

Browse files
rwgkhenryiii
andauthored
Fix build failure when shared_ptr<T> points to private std::enable_shared_from_this base (#5590)
* Squashed private_esft/manuscript — 3f2b120 — 2025-03-30 12:38:30 -0700 [Browse private_esft/manuscript tree](https://github.com/rwgk/pybind11/tree/3f2b1201b830d9e431448bd8f5fe577afaa02dbf) [Browse private_esft/manuscript commits](https://github.com/rwgk/pybind11/commits/3f2b1201b830d9e431448bd8f5fe577afaa02dbf/) * Remove mention of ChatGPT Co-authored-by: Henry Schreiner <[email protected]> --------- Co-authored-by: Henry Schreiner <[email protected]>
1 parent e7e5d6e commit 8726ed2

File tree

4 files changed

+36
-1
lines changed

4 files changed

+36
-1
lines changed

include/pybind11/detail/struct_smart_holder.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,23 @@ High-level aspects:
6262
namespace pybindit {
6363
namespace memory {
6464

65+
// Default fallback.
6566
static constexpr bool type_has_shared_from_this(...) { return false; }
6667

68+
// This overload uses SFINAE to skip enable_shared_from_this checks when the
69+
// base is inaccessible (e.g. private inheritance).
6770
template <typename T>
68-
static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this<T> *) {
71+
static auto type_has_shared_from_this(const T *ptr)
72+
-> decltype(static_cast<const std::enable_shared_from_this<T> *>(ptr), true) {
6973
return true;
7074
}
7175

76+
// Inaccessible base → substitution failure → fallback overload selected
77+
template <typename T>
78+
static constexpr bool type_has_shared_from_this(const void *) {
79+
return false;
80+
}
81+
7282
struct guarded_delete {
7383
std::weak_ptr<void> released_ptr; // Trick to keep the smart_holder memory footprint small.
7484
std::function<void(void *)> del_fun; // Rare case.

tests/pure_cpp/smart_holder_poc.h

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ namespace pybindit {
1010
namespace memory {
1111
namespace smart_holder_poc { // Proof-of-Concept implementations.
1212

13+
struct PrivateESFT : private std::enable_shared_from_this<PrivateESFT> {};
14+
static_assert(!pybindit::memory::type_has_shared_from_this(static_cast<PrivateESFT *>(nullptr)),
15+
"should detect inaccessible base");
16+
1317
template <typename T>
1418
T &as_lvalue_ref(const smart_holder &hld) {
1519
static const char *context = "as_lvalue_ref";

tests/test_smart_ptr.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -473,4 +473,13 @@ TEST_SUBMODULE(smart_ptr, m) {
473473
}
474474
return list;
475475
});
476+
477+
class PrivateESFT : /* implicit private */ std::enable_shared_from_this<PrivateESFT> {};
478+
struct ContainerUsingPrivateESFT {
479+
std::shared_ptr<PrivateESFT> ptr;
480+
};
481+
py::class_<ContainerUsingPrivateESFT>(m, "ContainerUsingPrivateESFT")
482+
.def(py::init<>())
483+
.def_readwrite("ptr",
484+
&ContainerUsingPrivateESFT::ptr); // <- access ESFT through shared_ptr
476485
}

tests/test_smart_ptr.py

+12
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,15 @@ def test_shared_ptr_gc():
326326
pytest.gc_collect()
327327
for i, v in enumerate(el.get()):
328328
assert i == v.value()
329+
330+
331+
def test_private_esft_tolerance():
332+
# Regression test: binding a shared_ptr<T> member where T privately inherits
333+
# enable_shared_from_this<T> must not cause a C++ compile error.
334+
c = m.ContainerUsingPrivateESFT()
335+
# The ptr member is not actually usable in any way, but this is how the
336+
# pybind11 v2 release series worked.
337+
with pytest.raises(TypeError):
338+
_ = c.ptr # getattr
339+
with pytest.raises(TypeError):
340+
c.ptr = None # setattr

0 commit comments

Comments
 (0)