Skip to content

Commit f6f45dd

Browse files
Capture issue #1200: Mutable lvalue references are copied by pybind when the object is not already registered.
1 parent 0826b3c commit f6f45dd

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

tests/test_callbacks.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,34 @@ TEST_SUBMODULE(callbacks, m) {
146146
py::class_<CppBoundMethodTest>(m, "CppBoundMethodTest")
147147
.def(py::init<>())
148148
.def("triple", [](CppBoundMethodTest &, int val) { return 3 * val; });
149+
150+
struct CppCopyable {
151+
int value{};
152+
};
153+
py::class_<CppCopyable>(m, "CppCopyable")
154+
.def(py::init<>())
155+
.def_readwrite("value", &CppCopyable::value);
156+
// Works fine, as pybind is aware of the existing instance.
157+
m.def(
158+
"callback_mutate_copyable_py",
159+
[](std::function<void(CppCopyable&)> f, CppCopyable& obj) {
160+
f(obj);
161+
});
162+
// Does not work as expected, because pybind will copy the instance when
163+
// binding.
164+
m.def(
165+
"callback_mutate_copyable_cpp_ref",
166+
[](std::function<void(CppCopyable&)> f, int value) {
167+
CppCopyable obj{value};
168+
f(obj);
169+
return obj;
170+
});
171+
// Works as expected, because pybind will not copy the instance.
172+
m.def(
173+
"callback_mutate_copyable_cpp_ptr",
174+
[](std::function<void(CppCopyable*)> f, int value) {
175+
CppCopyable obj{value};
176+
f(&obj);
177+
return obj;
178+
});
149179
}

tests/test_callbacks.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,29 @@ def test_cpp_function_roundtrip():
9797
assert any(s in str(excinfo.value) for s in ("missing 1 required positional argument",
9898
"takes exactly 2 arguments"))
9999

100-
101100
def test_function_signatures(doc):
102101
assert doc(m.test_callback3) == "test_callback3(arg0: Callable[[int], int]) -> str"
103102
assert doc(m.test_callback4) == "test_callback4() -> Callable[[int], int]"
104103

105104

106105
def test_movable_object():
107106
assert m.callback_with_movable(lambda _: None) is True
107+
108+
109+
def test_object_mutation():
110+
# Issue #1200
111+
def incr(obj):
112+
obj.value += 1
113+
114+
obj = m.CppCopyable()
115+
assert obj.value == 0
116+
117+
m.callback_mutate_copyable_py(incr, obj)
118+
assert obj.value == 1
119+
120+
obj = m.callback_mutate_copyable_cpp_ref(incr, 10)
121+
# WARNING: This this creates a COPY when passing to callback.
122+
assert obj.value == 10
123+
124+
obj = m.callback_mutate_copyable_cpp_ptr(incr, 10)
125+
assert obj.value == 11

0 commit comments

Comments
 (0)