diff --git a/example/example-virtual-functions.cpp b/example/example-virtual-functions.cpp index d08c782af..55a087109 100644 --- a/example/example-virtual-functions.cpp +++ b/example/example-virtual-functions.cpp @@ -69,6 +69,53 @@ public: } }; +class NonCopyable { +public: + NonCopyable(int a, int b) : value{new int(a*b)} {} + NonCopyable(NonCopyable &&) = default; + NonCopyable(const NonCopyable &) = delete; + NonCopyable() = delete; + void operator=(const NonCopyable &) = delete; + void operator=(NonCopyable &&) = delete; + std::string get_value() const { + if (value) return std::to_string(*value); else return "(null)"; + } + ~NonCopyable() { std::cout << "NonCopyable destructor @ " << this << "; value = " << get_value() << std::endl; } + +private: + std::unique_ptr value; +}; + +// This is like the above, but is both copy and movable. In effect this means it should get moved +// when it is not referenced elsewhere, but copied if it is still referenced. +class Movable { +public: + Movable(int a, int b) : value{a+b} {} + Movable(const Movable &m) { value = m.value; std::cout << "Movable @ " << this << " copy constructor" << std::endl; } + Movable(Movable &&m) { value = std::move(m.value); std::cout << "Movable @ " << this << " move constructor" << std::endl; } + int get_value() const { return value; } + ~Movable() { std::cout << "Movable destructor @ " << this << "; value = " << get_value() << std::endl; } +private: + int value; +}; + +class NCVirt { +public: + virtual NonCopyable get_noncopyable(int a, int b) { return NonCopyable(a, b); } + virtual Movable get_movable(int a, int b) = 0; + + void print_nc(int a, int b) { std::cout << get_noncopyable(a, b).get_value() << std::endl; } + void print_movable(int a, int b) { std::cout << get_movable(a, b).get_value() << std::endl; } +}; +class NCVirtTrampoline : public NCVirt { + virtual NonCopyable get_noncopyable(int a, int b) { + PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b); + } + virtual Movable get_movable(int a, int b) { + PYBIND11_OVERLOAD_PURE(Movable, NCVirt, get_movable, a, b); + } +}; + int runExampleVirt(ExampleVirt *ex, int value) { return ex->run(value); } @@ -240,6 +287,20 @@ void init_ex_virtual_functions(py::module &m) { .def("run_bool", &ExampleVirt::run_bool) .def("pure_virtual", &ExampleVirt::pure_virtual); + py::class_(m, "NonCopyable") + .def(py::init()) + ; + py::class_(m, "Movable") + .def(py::init()) + ; + py::class_, NCVirtTrampoline>(m, "NCVirt") + .def(py::init<>()) + .def("get_noncopyable", &NCVirt::get_noncopyable) + .def("get_movable", &NCVirt::get_movable) + .def("print_nc", &NCVirt::print_nc) + .def("print_movable", &NCVirt::print_movable) + ; + m.def("runExampleVirt", &runExampleVirt); m.def("runExampleVirtBool", &runExampleVirtBool); m.def("runExampleVirtVirtual", &runExampleVirtVirtual); diff --git a/example/example-virtual-functions.py b/example/example-virtual-functions.py index 1f7196570..121f330c8 100644 --- a/example/example-virtual-functions.py +++ b/example/example-virtual-functions.py @@ -5,6 +5,8 @@ sys.path.append('.') from example import ExampleVirt, runExampleVirt, runExampleVirtVirtual, runExampleVirtBool from example import A_Repeat, B_Repeat, C_Repeat, D_Repeat, A_Tpl, B_Tpl, C_Tpl, D_Tpl +from example import NCVirt, NonCopyable, Movable + class ExtendedExampleVirt(ExampleVirt): def __init__(self, state): @@ -87,3 +89,36 @@ for cl in classes: if hasattr(obj, "lucky_number"): print("Lucky = %.2f" % obj.lucky_number()) +class NCVirtExt(NCVirt): + def get_noncopyable(self, a, b): + # Constructs and returns a new instance: + nc = NonCopyable(a*a, b*b) + return nc + def get_movable(self, a, b): + # Return a referenced copy + self.movable = Movable(a, b) + return self.movable + +class NCVirtExt2(NCVirt): + def get_noncopyable(self, a, b): + # Keep a reference: this is going to throw an exception + self.nc = NonCopyable(a, b) + return self.nc + def get_movable(self, a, b): + # Return a new instance without storing it + return Movable(a, b) + +ncv1 = NCVirtExt() +print("2^2 * 3^2 =") +ncv1.print_nc(2, 3) +print("4 + 5 =") +ncv1.print_movable(4, 5) +ncv2 = NCVirtExt2() +print("7 + 7 =") +ncv2.print_movable(7, 7) +try: + ncv2.print_nc(9, 9) + print("Something's wrong: exception not raised!") +except RuntimeError as e: + # Don't print the exception message here because it differs under debug/non-debug mode + print("Caught expected exception") diff --git a/example/example-virtual-functions.ref b/example/example-virtual-functions.ref index 795f7b03f..c5c9223de 100644 --- a/example/example-virtual-functions.ref +++ b/example/example-virtual-functions.ref @@ -77,5 +77,21 @@ VI_DT: VI_DT says: quack quack quack Unlucky = 1234 Lucky = -4.25 +2^2 * 3^2 = +NonCopyable destructor @ 0x1a6c3f0; value = (null) +36 +NonCopyable destructor @ 0x7ffc6d1fbaa8; value = 36 +4 + 5 = +Movable @ 0x7ffc6d1fbacc copy constructor +9 +Movable destructor @ 0x7ffc6d1fbacc; value = 9 +7 + 7 = +Movable @ 0x7ffc6d1fbacc move constructor +Movable destructor @ 0x1a6c4d0; value = 14 +14 +Movable destructor @ 0x7ffc6d1fbacc; value = 14 +Caught expected exception +NonCopyable destructor @ 0x29a64b0; value = 81 +Movable destructor @ 0x1a6c410; value = 9 Destructing ExampleVirt.. Destructing ExampleVirt.. diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 1127abcc8..34dbb28e1 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -809,9 +809,36 @@ public: PYBIND11_TYPE_CASTER(type, handle_type_name::name()); }; +// Our conditions for enabling moving are quite restrictive: +// At compile time: +// - T needs to be a non-const, non-pointer, non-reference type +// - type_caster::operator T&() must exist +// - the type must be move constructible (obviously) +// At run-time: +// - if the type is non-copy-constructible, the object must be the sole owner of the type (i.e. it +// must have ref_count() == 1)h +// If any of the above are not satisfied, we fall back to copying. +template struct move_is_plain_type : std::false_type {}; +template struct move_is_plain_type::value && !std::is_pointer::value && !std::is_reference::value && !std::is_const::value + >::type> : std::true_type {}; +template struct move_always : std::false_type {}; +template struct move_always::value && + !std::is_copy_constructible::value && std::is_move_constructible::value && + std::is_same>().operator T&()), T&>::value + >::type> : std::true_type {}; +template struct move_if_unreferenced : std::false_type {}; +template struct move_if_unreferenced::value && + !move_always::value && std::is_move_constructible::value && + std::is_same>().operator T&()), T&>::value + >::type> : std::true_type {}; +template using move_never = std::integral_constant::value && !move_if_unreferenced::value>; + NAMESPACE_END(detail) -template T cast(handle handle) { +template T cast(const handle &handle) { typedef detail::type_caster::type> type_caster; type_caster conv; if (!conv.load(handle, true)) { @@ -838,6 +865,57 @@ template object cast(const T &value, template T handle::cast() const { return pybind11::cast(*this); } template <> inline void handle::cast() const { return; } +template +typename std::enable_if::value || detail::move_if_unreferenced::value, T>::type move(object &&obj) { + if (obj.ref_count() > 1) +#if defined(NDEBUG) + throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references" + " (compile in debug mode for details)"); +#else + throw cast_error("Unable to move from Python " + (std::string) obj.get_type().str() + + " instance to C++ " + type_id() + " instance: instance has multiple references"); +#endif + + typedef detail::type_caster type_caster; + type_caster conv; + if (!conv.load(obj, true)) +#if defined(NDEBUG) + throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); +#else + throw cast_error("Unable to cast Python instance of type " + + (std::string) obj.get_type().str() + " to C++ type '" + type_id() + "''"); +#endif + + // Move into a temporary and return that, because the reference may be a local value of `conv` + T ret = std::move(conv.operator T&()); + return ret; +} + +// Calling cast() on an rvalue calls pybind::cast with the object rvalue, which does: +// - If we have to move (because T has no copy constructor), do it. This will fail if the moved +// object has multiple references, but trying to copy will fail to compile. +// - If both movable and copyable, check ref count: if 1, move; otherwise copy +// - Otherwise (not movable), copy. +template typename std::enable_if::value, T>::type cast(object &&object) { + return move(std::move(object)); +} +template typename std::enable_if::value, T>::type cast(object &&object) { + if (object.ref_count() > 1) + return cast(object); + else + return move(std::move(object)); +} +template typename std::enable_if::value, T>::type cast(object &&object) { + return cast(object); +} + +template T object::cast() const & { return pybind11::cast(*this); } +template T object::cast() && { return pybind11::cast(std::move(*this)); } +template <> inline void object::cast() const & { return; } +template <> inline void object::cast() && { return; } + + + template tuple make_tuple(Args&&... args_) { const size_t size = sizeof...(Args); diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index c8a90f871..0a0f50878 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -89,6 +89,11 @@ public: } return *this; } + + // Calling cast() on an object lvalue just copies (via handle::cast) + template T cast() const &; + // Calling on an object rvalue does a move, if needed and/or possible + template T cast() &&; }; NAMESPACE_BEGIN(detail)