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:
Jason Rhinelander 2017-05-12 13:40:05 -04:00
parent 7cdf9f1a68
commit acedd6c70c
3 changed files with 92 additions and 4 deletions

View File

@ -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>(); 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: 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) { 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>; 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) \ #define PYBIND11_TYPE_CASTER(type, py_name) \

View File

@ -200,6 +200,21 @@ struct NoAssign {
NoAssign &operator=(NoAssign &&) = delete; NoAssign &operator=(NoAssign &&) = delete;
}; };
// Increments on copy
struct IncrIntWrapper {
int i;
IncrIntWrapper(int i) : i(i) {}
IncrIntWrapper(const IncrIntWrapper &copy) : 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) { test_initializer python_types([](py::module &m) {
/* No constructor is explicitly defined below. An exception is raised when /* No constructor is explicitly defined below. An exception is raised when
trying to construct it directly from Python */ 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) + "]"; }); .def("__repr__", [](const IntWrapper &p) { return "IntWrapper[" + std::to_string(p.i) + "]"; });
// #171: Can't return reference wrappers (or STL datastructures containing them) // #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) { m.def("return_vec_of_reference_wrapper", [](std::reference_wrapper<IntWrapper> p4) {
IntWrapper *p1 = new IntWrapper{1}; IntWrapper *p1 = new IntWrapper{1};
IntWrapper *p2 = new IntWrapper{2}; IntWrapper *p2 = new IntWrapper{2};
@ -579,6 +595,41 @@ test_initializer python_types([](py::module &m) {
return v; 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) #if defined(_MSC_VER)

View File

@ -612,9 +612,35 @@ def test_reference_wrapper():
"""std::reference_wrapper<T> tests. """std::reference_wrapper<T> tests.
#171: Can't return reference wrappers (or STL data structures containing them) #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: # 171:
assert str(return_vec_of_reference_wrapper(IntWrapper(4))) == \ assert str(return_vec_of_reference_wrapper(IntWrapper(4))) == \
"[IntWrapper[1], IntWrapper[2], IntWrapper[3], 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]