-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Passing reference arguments to trampoline methods [smart_holder] #2916
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: archive/smart_holder
Are you sure you want to change the base?
Passing reference arguments to trampoline methods [smart_holder] #2916
Conversation
b33bd59
to
03cd9d6
Compare
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.
Hi Robert, this looks great to me. My main question: is the change to include/pybind11/pytypes.h essential? Currently the smart_holder branch still uses the original from master.
Yes, it is. See #2915 where I request this change in master. |
03cd9d6
to
5583036
Compare
@rwgk, I think I have addressed all your feedback |
Thanks Robert! I'll run this through our global testing system. |
I'm seeing ~10 test failures in our global testing. It is running a very large number of tests, i.e. this is a very rare failure, but I verified manually that it is reproducible and also generates a
Drilling down, the signatures are:
It seems to boil down to I'm pretty sure (but have not verified) that this code does not use smart_holder. |
Not sure. Need to investigate. What's the failure? |
FYI: After I saw your message I tried to put together a minimal reproducer based on the string-return-by-value-combined-with-policy_move suspicion, but that works just fine. I started drilling down into the original failure (segfault without ASAN, |
Sharing the next observation:
- template <return_value_policy policy = return_value_policy::automatic_reference, typename... Args>
+ template <return_value_policy policy = return_value_policy::reference, typename... Args>
object operator()(Args &&...args) const;
- template <return_value_policy policy = return_value_policy::automatic_reference, typename... Args>
+ template <return_value_policy policy = return_value_policy::reference, typename... Args> I'm still (very) unclear how that change leads to the breakage. |
Below is the documentation for @rhaschke, do you have an idea for a minimal reproducer based on that theory? The failing test is very complex, I'm hoping you have an idea that saves me the effort trying to understand and reduce it.
|
I think the doc simply states that also pointers converted by reference, i.e. not taking ownership. The key difference in behavior is that everything is converted as |
I looked at the stack trace and code for almost two hours but I'm still confused, it's a very complex setup. |
I don't think strings are affected by the PR at all: The corresponding caster ignores the return_value_policy and always copies: pybind11/include/pybind11/cast.h Lines 410 to 416 in 1bafd5d
|
Ah. I just noticed that GetTypeName() is a virtual (even abstract) method, which is probably then implemented in Python. |
I couldn't find an issue when tracing the call hierarchy of this example: struct StringReturner {
virtual std::string get_string() { return std::string("foo"); }
};
py::classh<StringReturner, PyStringReturner>(m, "StringReturner")
.def(py::init<>())
.def("get_string", &StringReturner::get_string);
m.def("check_string", [](StringReturner &caller) {
return caller.get_string();
}, py::return_value_policy::move); class PyStringReturner(m.StringReturner):
def get_string(self):
return "bar" |
Returning a non-const reference from C++ to Python, shouldn't create a new copy. However, by design, the user explicitly needs to opt-in for reference-passing by providing return_value_policy::reference.
…ned back? This reveals that passing a unique_ptr const-reference from python to C++ doesn't work. unique_ptrs can only be moved from Python to C++ (requiring that Python actually owns the object). The tests also reveal that passing a unique_ptr non-const reference from C++ to Python is possible. However, this is bad programming style (it's not clear whether ownership is transferred or not). Shouldn't we suppress this? Removed redundant test_unique_ptr_cref_roundtrip() Renamed uconsumer -> consumer
Add tests to ensure that reference arguments passed to trampoline methods (from C++ -> Python -> C++) are actually passed by reference (and not copied), which is the default return_value_policy to pass from C++ to Python. Being able to pass by reference is essential to allow object modifications performed in Python code to become visible in C++.
… D>> - forbid to pass a non-const unique_ptr reference - forbid return_value_policy::reference_internal for unique_ptr&&: It's always moved!
9d4f1d7
to
f2d285a
Compare
…ence Otherwise, modifications applied by Python-coded method overrides would be applied to copies, even though the parameters were passed by pointer or reference.
f2d285a
to
1691a84
Compare
This may be affected by the whitespace changes under PR #3073. Sorry for the trouble. I looked into rebasing but it's not easy even without the whitespace changes (I think). There are merge conflicts in these two files: tests/test_class_sh_basic.cpp |
55d9281
to
68a11bb
Compare
This replaces #2911 and mimics #2915 in master.
This introduces a set of new unittests revealing that reference arguments passed to trampoline methods,
i.e. Python-overridden virtual methods of C++ classes, are not actually passed by reference, but by copy.
This is due to the fact that
return_value_policy::copy
is used by default to pass from C++ to Python.Using
return_value_policy::reference
for calls from those trampoline methods essentially solves the issue.