Passing reference arguments to trampoline methods [smart_holder]#2916
Passing reference arguments to trampoline methods [smart_holder]#2916rhaschke wants to merge 6 commits intopybind:archive/smart_holderfrom
Conversation
b33bd59 to
03cd9d6
Compare
rwgk
left a comment
There was a problem hiding this comment.
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::copyis used by default to pass from C++ to Python.Using
return_value_policy::referencefor calls from those trampoline methods essentially solves the issue.