Skip to content

Commit 6efc18e

Browse files
Use return_value_policy::reference for mutable lvalues.
1 parent f6f45dd commit 6efc18e

File tree

6 files changed

+130
-12
lines changed

6 files changed

+130
-12
lines changed

docs/advanced/functions.rst

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,17 @@ The following table provides an overview of available policies:
9292
+--------------------------------------------------+----------------------------------------------------------------------------+
9393
| :enum:`return_value_policy::automatic` | **Default policy.** This policy falls back to the policy |
9494
| | :enum:`return_value_policy::take_ownership` when the return value is a |
95-
| | pointer. Otherwise, it uses :enum:`return_value_policy::move` or |
96-
| | :enum:`return_value_policy::copy` for rvalue and lvalue references, |
97-
| | respectively. See above for a description of what all of these different |
98-
| | policies do. |
95+
| | pointer or :enum:`return_value_policy:reference` when the return value is |
96+
| | a mutable lvalue reference. Otherwise, it uses |
97+
| | :enum:`return_value_policy::move` or :enum:`return_value_policy::copy` for |
98+
| | rvalue and const lvalue references, respectively. See above for a |
99+
| | description of what all of these different policies do. |
99100
+--------------------------------------------------+----------------------------------------------------------------------------+
100101
| :enum:`return_value_policy::automatic_reference` | As above, but use policy :enum:`return_value_policy::reference` when the |
101-
| | return value is a pointer. This is the default conversion policy for |
102-
| | function arguments when calling Python functions manually from C++ code |
103-
| | (i.e. via handle::operator()). You probably won't need to use this. |
102+
| | return value is a pointer or mutable lvalue reference. This is the default |
103+
| | conversion policy for function arguments when calling Python functions |
104+
| | manually from C++ code (i.e. via handle::operator()). You probably won't |
105+
| | need to use this. |
104106
+--------------------------------------------------+----------------------------------------------------------------------------+
105107

106108
Return value policies can also be applied to properties:

include/pybind11/cast.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,12 @@ template <typename type> class type_caster_base : public type_caster_generic {
789789
return cast(&src, policy, parent);
790790
}
791791

792+
static handle cast(itype &src, return_value_policy policy, handle parent) {
793+
if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference)
794+
policy = return_value_policy::reference;
795+
return cast(&src, policy, parent);
796+
}
797+
792798
static handle cast(itype &&src, return_value_policy, handle parent) {
793799
return cast(&src, return_value_policy::move, parent);
794800
}

tests/test_callbacks.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ TEST_SUBMODULE(callbacks, m) {
159159
[](std::function<void(CppCopyable&)> f, CppCopyable& obj) {
160160
f(obj);
161161
});
162-
// Does not work as expected, because pybind will copy the instance when
163-
// binding.
162+
// Works as expected, because pybind will not copy the instance.
164163
m.def(
165164
"callback_mutate_copyable_cpp_ref",
166165
[](std::function<void(CppCopyable&)> f, int value) {

tests/test_callbacks.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@ def incr(obj):
118118
assert obj.value == 1
119119

120120
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-
121+
assert obj.value == 11
124122
obj = m.callback_mutate_copyable_cpp_ptr(incr, 10)
125123
assert obj.value == 11

tests/test_methods_and_attributes.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,60 @@ TEST_SUBMODULE(methods_and_attributes, m) {
451451
m.def("custom_caster_destroy_const", []() -> const DestructionTester * { return new DestructionTester(); },
452452
py::return_value_policy::take_ownership); // Likewise (const doesn't inhibit destruction)
453453
m.def("destruction_tester_cstats", &ConstructorStats::get<DestructionTester>, py::return_value_policy::reference);
454+
455+
// Test mutable lvalue references for return policies and `cast<>()` (#1200).
456+
struct Item {
457+
Item(int value_in) : value(value_in) { print_created(this, value); }
458+
Item(const Item& other) : value(other.value) {
459+
print_copy_created(this, value);
460+
}
461+
~Item() { print_destroyed(this); }
462+
int value{};
463+
};
464+
py::class_<Item>(m, "Item")
465+
.def(py::init<int>())
466+
.def_readwrite("value", &Item::value);
467+
468+
struct Container {
469+
Item* new_ptr() { return new Item{100}; }
470+
Item* get_ptr() { return &item; }
471+
Item& get_ref() { return item; }
472+
473+
// Test casting behavior.
474+
py::object cast_copy(int value) {
475+
Item item{value};
476+
return py::cast(item);
477+
}
478+
py::object cast_ptr() { return py::cast(&item); }
479+
py::object cast_ref_registered() { return py::cast(item); }
480+
py::object cast_ref_unregistered() {
481+
// This is different from above in that we have not exposed `item_cast_ref` prior to this.
482+
// NB. Writing `py::cast<Item&>(get_cast_ref())` will simply bind to the same overload as
483+
// `py::cast(get_cast_ref())`, which is `object cast(const Item&, ...)`.
484+
// Unfortunately, there is no way to overload `cast` to return references without danger of
485+
// returning references to temporaries (see `cast_copy`).
486+
return py::cast(get_cast_ref(), py::return_value_policy::reference);
487+
}
488+
489+
private:
490+
Item& get_cast_ref() {
491+
// Ensure that we return something that pybind has not seen yet.
492+
item_cast_ref_ptr.reset(new Item(20));
493+
return *item_cast_ref_ptr;
494+
}
495+
496+
Item item{10};
497+
std::unique_ptr<Item> item_cast_ref_ptr;
498+
};
499+
py::class_<Container>(m, "Container")
500+
.def(py::init<>())
501+
.def("new_ptr", &Container::new_ptr)
502+
.def("get_ptr_unsafe", &Container::get_ptr, py::return_value_policy::automatic_reference)
503+
.def("get_ref_unsafe", &Container::get_ref)
504+
.def("get_ptr", &Container::get_ptr, py::return_value_policy::reference_internal)
505+
.def("get_ref", &Container::get_ref, py::return_value_policy::reference_internal)
506+
.def("cast_copy", &Container::cast_copy)
507+
.def("cast_ptr", &Container::cast_ptr)
508+
.def("cast_ref_registered", &Container::cast_ref_registered)
509+
.def("cast_ref_unregistered", &Container::cast_ref_unregistered);
454510
}

tests/test_methods_and_attributes.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,60 @@ def test_custom_caster_destruction():
510510

511511
# Make sure we still only have the original object (from ..._no_destroy()) alive:
512512
assert cstats.alive() == 1
513+
514+
515+
def test_lvalue_reference_policies():
516+
cstats = ConstructorStats.get(m.Item)
517+
518+
c = m.Container()
519+
assert cstats.alive() == 1
520+
# Try new.
521+
obj = c.new_ptr()
522+
assert cstats.alive() == 2
523+
del obj
524+
pytest.gc_collect()
525+
assert cstats.alive() == 1
526+
527+
# Try references (unsafe).
528+
obj = c.get_ptr_unsafe()
529+
obj.value = 100
530+
assert cstats.alive() == 1
531+
obj = c.get_ref_unsafe()
532+
assert cstats.alive() == 1
533+
del obj
534+
535+
# Try references (safe).
536+
obj = c.get_ptr()
537+
assert cstats.alive() == 1
538+
obj = c.get_ref()
539+
assert cstats.alive() == 1
540+
541+
# Check casting behavior.
542+
obj = c.cast_copy(10)
543+
assert cstats.alive() == 2
544+
assert obj.value == 10
545+
del obj
546+
pytest.gc_collect()
547+
assert cstats.alive() == 1
548+
549+
obj = c.cast_ptr()
550+
assert cstats.alive() == 1
551+
assert obj is c.get_ptr()
552+
obj = c.cast_ref_registered()
553+
assert obj is c.get_ptr()
554+
assert cstats.alive() == 1
555+
# Check reference casting for an object previously unseen by pybind.
556+
obj = c.cast_ref_unregistered()
557+
assert cstats.alive() == 2
558+
assert obj.value == 20
559+
del obj
560+
pytest.gc_collect()
561+
# Object is owned by `c` internally.
562+
assert cstats.alive() == 2
563+
564+
# Ensure we have our original value.
565+
assert c.get_ref().value == 100
566+
567+
del c
568+
pytest.gc_collect()
569+
assert cstats.alive() == 0

0 commit comments

Comments
 (0)