diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0b0533da..0226a6ef 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -527,13 +527,24 @@ cast_op(make_caster &&caster) { typename make_caster::template cast_op_type::type>(); } -template class type_caster> : public type_caster_base { +template class type_caster> { +private: + using caster_t = make_caster; + caster_t subcaster; + using subcaster_cast_op_type = typename caster_t::template cast_op_type; + static_assert(std::is_same::type &, subcaster_cast_op_type>::value, + "std::reference_wrapper 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 &src, return_value_policy policy, handle parent) { - return type_caster_base::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 using cast_op_type = std::reference_wrapper; - operator std::reference_wrapper() { return std::ref(*((type *) this->value)); } + operator std::reference_wrapper() { return subcaster.operator subcaster_cast_op_type&(); } }; #define PYBIND11_TYPE_CASTER(type, py_name) \ diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index c9f35f7b..0d7ea502 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -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> incr_int_wrappers() { + static IncrIntWrapper x1(1), x2(2); + std::vector> 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 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 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>) { }); + + // Test that copying/referencing is working as expected with reference_wrappers: + py::class_(m, "IncrIntWrapper") + .def(py::init()) + .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) diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 0e563ee1..5e2761cb 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -612,9 +612,35 @@ def test_reference_wrapper(): """std::reference_wrapper 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]