mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45:12 +00:00
std::reference_wrapper: non-generic types; no None
This reimplements the std::reference_wrapper<T> caster to be a shell around the underlying T caster (rather than assuming T is a generic type), which lets it work for things like `std::reference_wrapper<int>` or anything else custom type caster with a lvalue cast operator. This also makes it properly fail when None is provided, just as an ordinary lvalue reference argument would similarly fail. This also adds a static assert to test that T has an appropriate type caster. It triggers for casters like `std::pair`, which have return-by-value cast operators. (In theory this could be supported by storing a local temporary for such types, but that's beyond the scope of this PR). This also replaces `automatic` or `take_ownership` return value policies with `automatic_reference` as taking ownership of a reference inside a reference_wrapper is not valid.
This commit is contained in:
parent
7cdf9f1a68
commit
acedd6c70c
@ -527,13 +527,24 @@ cast_op(make_caster<T> &&caster) {
|
||||
typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>();
|
||||
}
|
||||
|
||||
template <typename type> class type_caster<std::reference_wrapper<type>> : public type_caster_base<type> {
|
||||
template <typename type> class type_caster<std::reference_wrapper<type>> {
|
||||
private:
|
||||
using caster_t = make_caster<type>;
|
||||
caster_t subcaster;
|
||||
using subcaster_cast_op_type = typename caster_t::template cast_op_type<type>;
|
||||
static_assert(std::is_same<typename std::remove_const<type>::type &, subcaster_cast_op_type>::value,
|
||||
"std::reference_wrapper<T> caster requires T to have a caster with an `T &` operator");
|
||||
public:
|
||||
bool load(handle src, bool convert) { return subcaster.load(src, convert); }
|
||||
static PYBIND11_DESCR name() { return caster_t::name(); }
|
||||
static handle cast(const std::reference_wrapper<type> &src, return_value_policy policy, handle parent) {
|
||||
return type_caster_base<type>::cast(&src.get(), policy, parent);
|
||||
// It is definitely wrong to take ownership of this pointer, so mask that rvp
|
||||
if (policy == return_value_policy::take_ownership || policy == return_value_policy::automatic)
|
||||
policy = return_value_policy::automatic_reference;
|
||||
return caster_t::cast(&src.get(), policy, parent);
|
||||
}
|
||||
template <typename T> using cast_op_type = std::reference_wrapper<type>;
|
||||
operator std::reference_wrapper<type>() { return std::ref(*((type *) this->value)); }
|
||||
operator std::reference_wrapper<type>() { return subcaster.operator subcaster_cast_op_type&(); }
|
||||
};
|
||||
|
||||
#define PYBIND11_TYPE_CASTER(type, py_name) \
|
||||
|
@ -200,6 +200,21 @@ struct NoAssign {
|
||||
NoAssign &operator=(NoAssign &&) = delete;
|
||||
};
|
||||
|
||||
// Increments on copy
|
||||
struct IncrIntWrapper {
|
||||
int i;
|
||||
IncrIntWrapper(int i) : i(i) {}
|
||||
IncrIntWrapper(const IncrIntWrapper ©) : i(copy.i + 1) {}
|
||||
};
|
||||
|
||||
std::vector<std::reference_wrapper<IncrIntWrapper>> incr_int_wrappers() {
|
||||
static IncrIntWrapper x1(1), x2(2);
|
||||
std::vector<std::reference_wrapper<IncrIntWrapper>> r;
|
||||
r.emplace_back(x1);
|
||||
r.emplace_back(x2);
|
||||
return r;
|
||||
};
|
||||
|
||||
test_initializer python_types([](py::module &m) {
|
||||
/* No constructor is explicitly defined below. An exception is raised when
|
||||
trying to construct it directly from Python */
|
||||
@ -567,6 +582,7 @@ test_initializer python_types([](py::module &m) {
|
||||
.def("__repr__", [](const IntWrapper &p) { return "IntWrapper[" + std::to_string(p.i) + "]"; });
|
||||
|
||||
// #171: Can't return reference wrappers (or STL datastructures containing them)
|
||||
// Also used to test #848: reference_wrapper shouldn't allow None
|
||||
m.def("return_vec_of_reference_wrapper", [](std::reference_wrapper<IntWrapper> p4) {
|
||||
IntWrapper *p1 = new IntWrapper{1};
|
||||
IntWrapper *p2 = new IntWrapper{2};
|
||||
@ -579,6 +595,41 @@ test_initializer python_types([](py::module &m) {
|
||||
return v;
|
||||
});
|
||||
|
||||
// Reference-wrapper to non-generic type caster type:
|
||||
m.def("refwrap_int", [](std::reference_wrapper<int> p) { return 10 * p.get(); });
|
||||
|
||||
// Not currently supported (std::pair caster has return-by-value cast operator);
|
||||
// triggers static_assert failure.
|
||||
//m.def("refwrap_pair", [](std::reference_wrapper<std::pair<int, int>>) { });
|
||||
|
||||
// Test that copying/referencing is working as expected with reference_wrappers:
|
||||
py::class_<IncrIntWrapper>(m, "IncrIntWrapper")
|
||||
.def(py::init<int>())
|
||||
.def_readonly("i", &IncrIntWrapper::i);
|
||||
|
||||
m.def("refwrap_list_refs", []() {
|
||||
py::list l;
|
||||
for (auto &f : incr_int_wrappers()) l.append(py::cast(f, py::return_value_policy::reference));
|
||||
return l;
|
||||
});
|
||||
m.def("refwrap_list_copies", []() {
|
||||
py::list l;
|
||||
for (auto &f : incr_int_wrappers()) l.append(py::cast(f, py::return_value_policy::copy));
|
||||
return l;
|
||||
});
|
||||
m.def("refwrap_iiw", [](const IncrIntWrapper &w) { return w.i; });
|
||||
m.def("refwrap_call_iiw", [](IncrIntWrapper &w, py::function f) {
|
||||
py::list l;
|
||||
l.append(f(std::ref(w)));
|
||||
l.append(f(std::cref(w)));
|
||||
IncrIntWrapper x(w.i);
|
||||
l.append(f(std::ref(x)));
|
||||
IncrIntWrapper y(w.i);
|
||||
auto r3 = std::ref(y);
|
||||
l.append(f(r3));
|
||||
return l;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
@ -612,9 +612,35 @@ def test_reference_wrapper():
|
||||
"""std::reference_wrapper<T> tests.
|
||||
|
||||
#171: Can't return reference wrappers (or STL data structures containing them)
|
||||
#848: std::reference_wrapper accepts nullptr / None arguments [but shouldn't]
|
||||
(no issue): reference_wrappers should work for types with custom type casters
|
||||
"""
|
||||
from pybind11_tests import IntWrapper, return_vec_of_reference_wrapper
|
||||
from pybind11_tests import (IntWrapper, return_vec_of_reference_wrapper, refwrap_int,
|
||||
IncrIntWrapper, refwrap_iiw, refwrap_call_iiw,
|
||||
refwrap_list_copies, refwrap_list_refs)
|
||||
|
||||
# 171:
|
||||
assert str(return_vec_of_reference_wrapper(IntWrapper(4))) == \
|
||||
"[IntWrapper[1], IntWrapper[2], IntWrapper[3], IntWrapper[4]]"
|
||||
|
||||
# 848:
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
return_vec_of_reference_wrapper(None)
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
|
||||
assert refwrap_int(42) == 420
|
||||
|
||||
a1 = refwrap_list_copies()
|
||||
a2 = refwrap_list_copies()
|
||||
assert [x.i for x in a1] == [2, 3]
|
||||
assert [x.i for x in a2] == [2, 3]
|
||||
assert not a1[0] is a2[0] and not a1[1] is a2[1]
|
||||
|
||||
b1 = refwrap_list_refs()
|
||||
b2 = refwrap_list_refs()
|
||||
assert [x.i for x in b1] == [1, 2]
|
||||
assert [x.i for x in b2] == [1, 2]
|
||||
assert b1[0] is b2[0] and b1[1] is b2[1]
|
||||
|
||||
assert refwrap_iiw(IncrIntWrapper(5)) == 5
|
||||
assert refwrap_call_iiw(IncrIntWrapper(10), refwrap_iiw) == [10, 10, 10, 10]
|
||||
|
Loading…
Reference in New Issue
Block a user