Skip to content

Commit cbd16a8

Browse files
authored
stl.h: propagate return value policies to type-specific casters (#1455)
* stl.h: propagate return value policies to type-specific casters Return value policies for containers like those handled in in 'stl.h' are currently broken. The problem is that detail::return_value_policy_override<C>::policy() always returns 'move' when given a non-pointer/reference type, e.g. 'std::vector<...>'. This is sensible behavior for custom types that are exposed via 'py::class_<>', but it does not make sense for types that are handled by other type casters (STL containers, Eigen matrices, etc.). This commit changes the behavior so that detail::return_value_policy_override only becomes active when the type caster derives from type_caster_generic. Furthermore, the override logic is called recursively in STL type casters to enable key/value-specific behavior.
1 parent b4719a6 commit cbd16a8

File tree

6 files changed

+43
-13
lines changed

6 files changed

+43
-13
lines changed

include/pybind11/cast.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,9 +1606,13 @@ template <typename type> using cast_is_temporary_value_reference = bool_constant
16061606

16071607
// When a value returned from a C++ function is being cast back to Python, we almost always want to
16081608
// force `policy = move`, regardless of the return value policy the function/method was declared
1609-
// with. Some classes (most notably Eigen::Ref and related) need to avoid this, and so can do so by
1610-
// specializing this struct.
1609+
// with.
16111610
template <typename Return, typename SFINAE = void> struct return_value_policy_override {
1611+
static return_value_policy policy(return_value_policy p) { return p; }
1612+
};
1613+
1614+
template <typename Return> struct return_value_policy_override<Return,
1615+
detail::enable_if_t<std::is_base_of<type_caster_generic, make_caster<Return>>::value, void>> {
16121616
static return_value_policy policy(return_value_policy p) {
16131617
return !std::is_lvalue_reference<Return>::value && !std::is_pointer<Return>::value
16141618
? return_value_policy::move : p;

include/pybind11/eigen.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,14 +353,6 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
353353
Type value;
354354
};
355355

356-
// Eigen Ref/Map classes have slightly different policy requirements, meaning we don't want to force
357-
// `move` when a Ref/Map rvalue is returned; we treat Ref<> sort of like a pointer (we care about
358-
// the underlying data, not the outer shell).
359-
template <typename Return>
360-
struct return_value_policy_override<Return, enable_if_t<is_eigen_dense_map<Return>::value>> {
361-
static return_value_policy policy(return_value_policy p) { return p; }
362-
};
363-
364356
// Base class for casting reference/map/block/etc. objects back to python.
365357
template <typename MapType> struct eigen_map_caster {
366358
private:

include/pybind11/pybind11.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class cpp_function : public function {
145145
capture *cap = const_cast<capture *>(reinterpret_cast<const capture *>(data));
146146

147147
/* Override policy for rvalues -- usually to enforce rvp::move on an rvalue */
148-
const auto policy = return_value_policy_override<Return>::policy(call.func.policy);
148+
return_value_policy policy = return_value_policy_override<Return>::policy(call.func.policy);
149149

150150
/* Function scope guard -- defaults to the compile-to-nothing `void_type` */
151151
using Guard = extract_guard_t<Extra...>;

include/pybind11/stl.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ template <typename Type, typename Key> struct set_caster {
8383

8484
template <typename T>
8585
static handle cast(T &&src, return_value_policy policy, handle parent) {
86+
policy = return_value_policy_override<Key>::policy(policy);
8687
pybind11::set s;
8788
for (auto &&value : src) {
8889
auto value_ = reinterpret_steal<object>(key_conv::cast(forward_like<T>(value), policy, parent));
@@ -118,9 +119,11 @@ template <typename Type, typename Key, typename Value> struct map_caster {
118119
template <typename T>
119120
static handle cast(T &&src, return_value_policy policy, handle parent) {
120121
dict d;
122+
return_value_policy policy_key = return_value_policy_override<Key>::policy(policy);
123+
return_value_policy policy_value = return_value_policy_override<Value>::policy(policy);
121124
for (auto &&kv : src) {
122-
auto key = reinterpret_steal<object>(key_conv::cast(forward_like<T>(kv.first), policy, parent));
123-
auto value = reinterpret_steal<object>(value_conv::cast(forward_like<T>(kv.second), policy, parent));
125+
auto key = reinterpret_steal<object>(key_conv::cast(forward_like<T>(kv.first), policy_key, parent));
126+
auto value = reinterpret_steal<object>(value_conv::cast(forward_like<T>(kv.second), policy_value, parent));
124127
if (!key || !value)
125128
return handle();
126129
d[key] = value;
@@ -158,6 +161,7 @@ template <typename Type, typename Value> struct list_caster {
158161
public:
159162
template <typename T>
160163
static handle cast(T &&src, return_value_policy policy, handle parent) {
164+
policy = return_value_policy_override<Value>::policy(policy);
161165
list l(src.size());
162166
size_t index = 0;
163167
for (auto &&value : src) {
@@ -252,6 +256,7 @@ template<typename T> struct optional_caster {
252256
static handle cast(T_ &&src, return_value_policy policy, handle parent) {
253257
if (!src)
254258
return none().inc_ref();
259+
policy = return_value_policy_override<typename T::value_type>::policy(policy);
255260
return value_conv::cast(*std::forward<T_>(src), policy, parent);
256261
}
257262

@@ -356,6 +361,7 @@ struct variant_caster<V<Ts...>> {
356361
template <typename... Ts>
357362
struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> { };
358363
#endif
364+
359365
NAMESPACE_END(detail)
360366

361367
inline std::ostream &operator<<(std::ostream &os, const handle &obj) {

tests/test_stl.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
#include "pybind11_tests.h"
11+
#include "constructor_stats.h"
1112
#include <pybind11/stl.h>
1213

1314
// Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14
@@ -235,4 +236,21 @@ TEST_SUBMODULE(stl, m) {
235236

236237
// test_stl_pass_by_pointer
237238
m.def("stl_pass_by_pointer", [](std::vector<int>* v) { return *v; }, "v"_a=nullptr);
239+
240+
class Placeholder {
241+
public:
242+
Placeholder() { print_created(this); }
243+
Placeholder(const Placeholder &) = delete;
244+
~Placeholder() { print_destroyed(this); }
245+
};
246+
py::class_<Placeholder>(m, "Placeholder");
247+
248+
/// test_stl_vector_ownership
249+
m.def("test_stl_ownership",
250+
[]() {
251+
std::vector<Placeholder *> result;
252+
result.push_back(new Placeholder());
253+
return result;
254+
},
255+
py::return_value_policy::take_ownership);
238256
}

tests/test_stl.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pybind11_tests import stl as m
44
from pybind11_tests import UserType
5+
from pybind11_tests import ConstructorStats
56

67

78
def test_vector(doc):
@@ -198,3 +199,12 @@ def test_missing_header_message():
198199
with pytest.raises(TypeError) as excinfo:
199200
cm.missing_header_return()
200201
assert expected_message in str(excinfo.value)
202+
203+
204+
def test_stl_ownership():
205+
cstats = ConstructorStats.get(m.Placeholder)
206+
assert cstats.alive() == 0
207+
r = m.test_stl_ownership()
208+
assert len(r) == 1
209+
del r
210+
assert cstats.alive() == 0

0 commit comments

Comments
 (0)