-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add py::potentially_slicing_shared_ptr(handle)
function
#5624
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
base: master
Are you sure you want to change the base?
Conversation
virtual ~WpBase() = default; | ||
}; | ||
|
||
struct PyWpBase : WpBase { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct PyWpBase : WpBase, py::trampoline_self_life_support {
Does that help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I just fixed an oversight in my previous comment.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct PyWpBase : WpBase { | |
struct PyWpBase : WpBase, py::trampoline_self_life_support { |
I simplified the reproducer and changed the variable names to make them more intuitive (to me). I'm guessing we're building an intermediate I wrote that code four years ago. Quite likely this will take me several hours to re-learn what exactly is happening. Not sure when I'll get to it. @nsoblath How did you arrive at that test? Did you have code that worked with |
…s holder). Tweak test_class_sh_trampoline_weak_ptr.py to pass, with `# THIS NEEDS FIXING` comment.
On that suspicion, I added test_class_sp_trampoline_weak_ptr.cpp,py, and it seems to work: it certainly works locally, but I'm waiting for GitHub Actions to finish. Assuming the new test works reliably on all platforms, I'd guess that it's feasible to fix the smart_holder equivalent. |
``` /__w/pybind11/pybind11/tests/test_class_sh_trampoline_weak_ptr.cpp:23:43: error: the parameter 'sp' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param,-warnings-as-errors] 23 | void set_wp(std::shared_ptr<VirtBase> sp) { wp = sp; } | ^ | const & 4443 warnings generated. ``` ``` /__w/pybind11/pybind11/tests/test_class_sp_trampoline_weak_ptr.cpp:23:43: error: the parameter 'sp' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param,-warnings-as-errors] 23 | void set_wp(std::shared_ptr<VirtBase> sp) { wp = sp; } | ^ | const & 4430 warnings generated. ```
Before I wrote:
That's here (conclusive):
Note that the
So in line 782 we need to return a
Any ideas, please let me know. (I haven't thought about it much yet; the penny didn't want to drop immediately.) @virtuald for visibility |
I copied the test_class_sh_trampoline_shared_ptr_cpp_arg test, modified it to store a |
I've now turned this over in my mind for a couple hours, and consulted ChatGPT, which I believe got very confused/confusing, but please see for yourself here. Stepping out of the box:
Solving this is a real brain teaser. I'm not sure I can help. I'm not sure if there is a viable solution. I have a couple ideas, but each of them might take hours of work only to find out it leads to a dead end. Could you try to work around this limitation? For example, instead of owning the C++ |
…s does not inherit from this class, preserve established Inheritance Slicing behavior. rwgk reached this point with the help of ChatGPT: * https://chatgpt.com/share/68056498-7d94-8008-8ff0-232e2aba451c The only production code change in this commit is: ``` diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index d4f9a41..f3d4530 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -776,6 +776,14 @@ struct load_helper : value_and_holder_helper { if (released_ptr) { return std::shared_ptr<T>(released_ptr, type_raw_ptr); } + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(type_raw_ptr); + if (self_life_support == nullptr) { + std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>(); + std::shared_ptr<T> to_be_released(void_shd_ptr, type_raw_ptr); + vptr_gd_ptr->released_ptr = to_be_released; + return to_be_released; + } std::shared_ptr<T> to_be_released( type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); vptr_gd_ptr->released_ptr = to_be_released; ```
I'm testing commit 4638e01 to see if that works on all platforms. See https://chatgpt.com/share/68056498-7d94-8008-8ff0-232e2aba451c for how I got there / rationale. I'm not sure if 4638e01 is a wise twist to add. Fundamentally, it'd be better to support |
``` 11>D:\a\pybind11\pybind11\tests\test_class_sp_trampoline_weak_ptr.cpp(44,50): error C2220: the following warning is treated as an error [D:\a\pybind11\pybind11\build\tests\pybind11_tests.vcxproj] 11>D:\a\pybind11\pybind11\tests\test_class_sp_trampoline_weak_ptr.cpp(44,50): warning C4458: declaration of 'sp' hides class member [D:\a\pybind11\pybind11\build\tests\pybind11_tests.vcxproj] D:\a\pybind11\pybind11\tests\test_class_sp_trampoline_weak_ptr.cpp(54,31): see declaration of 'pybind11_tests::class_sp_trampoline_weak_ptr::SpOwner::sp' ```
@nsoblath I feel I'm pretty clear now about the situation. I think it's best not to change the smart_holder code, but to go with the approach outlined before:
I'll explain more when I get to it, probably next weekend. (The only thing I want to change, probably, is to add a |
@rwgk Thank you very much for your time and attention looking into this. I really appreciate it. Though my understanding of the internals of pybind11 is quite incomplete, my thoughts about what might be going wrong were similar to what you suggested: that the control block for the In your later message you said
Does that mean you're preferring to not use the change in 4638e01? Is there a downside to making sure that the control block of the returned Regarding the alternative solution you proposed:
I believe I can implement something to this effect. It's not ideal because the library where I'm using this is sometimes used as a pure-C++ library, sometimes used with its Python bindings. |
Yes.
Yes, it'll be very confusing to many (I think), while only being useful in a few niche cases. It is not obvious at all that the I feel it'll be better to keep the smart_holder feature simpler and the behavior consistent. It will be easier to reason about the bindings. We could probably add an interface that makes it relatively easy for you to get the inheritance-slicing- What I didn't explain: I convinced myself that it is impossible to get a
Reason: This would introduce a hard reference cycle, because the Python object ( |
@nsoblath If you can wait a week or two, I think (although I'm not sure) that I can give you something like this: py::classh<WpOwner>(m, "WpOwner")
...
.def("set_wp", [](WpOwner& self, py::handle obj) {
self.set_wp(py::slicing_shared_ptr_from_smart_holder<VirtBase>(obj));
})
... |
@rwgk I can certainly wait, and if that works, it'd be great. Thank you! Incidentally I have been working on the workaround we talked about, but this would be a cleaner solution (at least on the side of my application). |
I wouldn't call the proposed "slicing" option the cleaner solution. It's only easier. — You might still get a |
Also undo the corresponding test change in test_class_sh_trampoline_weak_ptr.py But keep all extra debugging code for now.
…cast to `std::shared_ptr<VirtBase>` for now.
…ssage and add test.
…h smart_holder and std::shared_ptr as holder.
… it got lost accidentally in commit 56d23dc
std::weak_ptr
used with derived Python class and py::smart_holder
py::potentially_slicing_shared_ptr(handle)
function
``` /__w/pybind11/pybind11/tests/test_potentially_slicing_shared_ptr.cpp:30:9: error: 'magic_token' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer,-warnings-as-errors] 29 | trampoline_is_alive_simple(const trampoline_is_alive_simple &other) { | : magic_token(other.magic_token) 30 | magic_token = other.magic_token; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /__w/pybind11/pybind11/tests/test_potentially_slicing_shared_ptr.cpp:33:9: error: 'magic_token' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer,-warnings-as-errors] 32 | trampoline_is_alive_simple(trampoline_is_alive_simple &&other) noexcept { | : magic_token(other.magic_token) 33 | magic_token = other.magic_token; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ```
@nsoblath I'm mostly done, if you want to take a look. The only part missing is an addition to the trampoline documentation. |
… surprises" section in advanced/classes.rst
@nsoblath All done now. Could you please review the added documentation? @virtuald Is there a chance that you could help with a review? — The production code changes are small and nearly trivial. Most of my effort for this PR went into the new stress-test level unit test, documentation, and source code comments. |
Description
Closes #5623.
Please see the added documentation and source code comments for more information.
For quick access, this is the new section in the documentation: