Skip to content

Commit 41e4e62

Browse files
authored
Merge pull request #182 from jashshah999/perf/reference-return-policy
perf: add reference_internal policy for const-ref returns
2 parents 1740473 + 7d2ae64 commit 41e4e62

3 files changed

Lines changed: 53 additions & 4 deletions

File tree

gtwrap/pybind_wrapper.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,17 @@ def _wrap_method(self,
251251
method,
252252
(parser.StaticMethod, instantiator.InstantiatedStaticMethod))
253253
return_void = method.return_type.is_void()
254+
return_ref = getattr(
255+
getattr(method.return_type, 'type1', None), 'is_ref', False)
256+
257+
# For methods returning const T&, use reference_internal policy
258+
# to avoid unnecessary copies and keep the returned reference alive.
259+
if return_ref and is_method:
260+
lambda_ret = ' -> const auto&'
261+
ref_policy = ', py::return_value_policy::reference_internal'
262+
else:
263+
lambda_ret = ''
264+
ref_policy = ''
254265

255266
caller = cpp_class + "::" if not is_method else "self->"
256267
function_call = ('{opt_return} {caller}{method_name}'
@@ -263,10 +274,10 @@ def _wrap_method(self,
263274

264275
result = (
265276
'{prefix}.{cdef}("{py_method}",'
266-
'[]({opt_self}{opt_comma}{args_signature_with_names}){{'
277+
'[]({opt_self}{opt_comma}{args_signature_with_names}){lambda_ret}{{'
267278
'{function_call}'
268279
'}}'
269-
'{py_args_names}{docstring}){suffix}'.format(
280+
'{ref_policy}{py_args_names}{docstring}){suffix}'.format(
270281
prefix=prefix,
271282
cdef="def_static" if is_static else "def",
272283
py_method=py_method,
@@ -275,7 +286,9 @@ def _wrap_method(self,
275286
opt_comma=', '
276287
if is_method and args_signature_with_names else '',
277288
args_signature_with_names=args_signature_with_names,
289+
lambda_ret=lambda_ret,
278290
function_call=function_call,
291+
ref_policy=ref_policy,
279292
py_args_names=py_args_names,
280293
suffix=suffix,
281294
# Try to get the function's docstring from the Doxygen XML.

tests/expected/python/class_pybind.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ PYBIND11_MODULE(class_py, m_) {
4343
.def("return_matrix1",[](Test* self, const gtsam::Matrix& value){return self->return_matrix1(value);}, py::arg("value"))
4444
.def("return_vector2",[](Test* self, const gtsam::Vector& value){return self->return_vector2(value);}, py::arg("value"))
4545
.def("return_matrix2",[](Test* self, const gtsam::Matrix& value){return self->return_matrix2(value);}, py::arg("value"))
46-
.def("return_vector2",[](Test* self, const gtsam::Vector& value){return self->return_vector2(value);}, py::arg("value"))
47-
.def("return_matrix2",[](Test* self, const gtsam::Matrix& value){return self->return_matrix2(value);}, py::arg("value"))
46+
.def("return_vector2",[](Test* self, const gtsam::Vector& value) -> const auto&{return self->return_vector2(value);}, py::return_value_policy::reference_internal, py::arg("value"))
47+
.def("return_matrix2",[](Test* self, const gtsam::Matrix& value) -> const auto&{return self->return_matrix2(value);}, py::return_value_policy::reference_internal, py::arg("value"))
4848
.def("arg_EigenConstRef",[](Test* self, const gtsam::Matrix& value){ self->arg_EigenConstRef(value);}, py::arg("value"))
4949
.def("push_back",[](Test* self, gtsam::Key key){ self->push_back(key);}, py::arg("key"))
5050
.def("return_field",[](Test* self, const Test& t){return self->return_field(t);}, py::arg("t"))

tests/test_pybind_wrapper.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,41 @@ def test_enum(self):
160160
self.compare_and_diff('enum_pybind.cpp', output)
161161

162162

163+
def test_const_ref_return_policy(self):
164+
"""Test that methods returning const T& emit reference_internal policy.
165+
166+
Without this policy, pybind11 defaults to copying the returned reference.
167+
With the policy, the binding keeps the reference alive via the parent object.
168+
169+
Expected emitted code difference:
170+
Before: [](Cls* self, ...){return self->method(...);}, py::arg(...))
171+
After: [](Cls* self, ...) -> const auto&{return self->method(...);},
172+
py::return_value_policy::reference_internal, py::arg(...))
173+
"""
174+
source = osp.join(self.INTERFACE_DIR, 'class.i')
175+
output = self.wrap_content([source], 'class_py',
176+
self.PYTHON_ACTUAL_DIR)
177+
178+
with open(output, 'r') as f:
179+
content = f.read()
180+
181+
# const Vector& return_vector2 should have reference_internal
182+
self.assertIn('-> const auto&{return self->return_vector2', content)
183+
self.assertIn('py::return_value_policy::reference_internal', content)
184+
185+
# const Matrix& return_matrix2 should also have reference_internal
186+
self.assertIn('-> const auto&{return self->return_matrix2', content)
187+
188+
# Non-ref returns (e.g. return_vector1 which returns by value) should NOT
189+
lines = content.split('\n')
190+
for line in lines:
191+
if 'return_vector1' in line:
192+
self.assertNotIn('reference_internal', line)
193+
self.assertNotIn('-> const auto&', line)
194+
if 'return_matrix1' in line:
195+
self.assertNotIn('reference_internal', line)
196+
self.assertNotIn('-> const auto&', line)
197+
198+
163199
if __name__ == '__main__':
164200
unittest.main()

0 commit comments

Comments
 (0)