Merge pull request #297 from jagerman/move-python-return-value

Move support for return values of called Python functions
This commit is contained in:
Wenzel Jakob 2016-08-09 15:14:31 +02:00 committed by GitHub
commit bb1ee389fd
5 changed files with 196 additions and 1 deletions

View File

@ -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<int> 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_<NonCopyable>(m, "NonCopyable")
.def(py::init<int, int>())
;
py::class_<Movable>(m, "Movable")
.def(py::init<int, int>())
;
py::class_<NCVirt, std::unique_ptr<NCVirt>, 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);

View File

@ -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")

View File

@ -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..

View File

@ -809,9 +809,36 @@ public:
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::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<T>::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 <typename T, typename SFINAE = void> struct move_is_plain_type : std::false_type {};
template <typename T> struct move_is_plain_type<T, typename std::enable_if<
!std::is_void<T>::value && !std::is_pointer<T>::value && !std::is_reference<T>::value && !std::is_const<T>::value
>::type> : std::true_type {};
template <typename T, typename SFINAE = void> struct move_always : std::false_type {};
template <typename T> struct move_always<T, typename std::enable_if<
move_is_plain_type<T>::value &&
!std::is_copy_constructible<T>::value && std::is_move_constructible<T>::value &&
std::is_same<decltype(std::declval<type_caster<T>>().operator T&()), T&>::value
>::type> : std::true_type {};
template <typename T, typename SFINAE = void> struct move_if_unreferenced : std::false_type {};
template <typename T> struct move_if_unreferenced<T, typename std::enable_if<
move_is_plain_type<T>::value &&
!move_always<T>::value && std::is_move_constructible<T>::value &&
std::is_same<decltype(std::declval<type_caster<T>>().operator T&()), T&>::value
>::type> : std::true_type {};
template <typename T> using move_never = std::integral_constant<bool, !move_always<T>::value && !move_if_unreferenced<T>::value>;
NAMESPACE_END(detail)
template <typename T> T cast(handle handle) {
template <typename T> T cast(const handle &handle) {
typedef detail::type_caster<typename detail::intrinsic_type<T>::type> type_caster;
type_caster conv;
if (!conv.load(handle, true)) {
@ -838,6 +865,57 @@ template <typename T> object cast(const T &value,
template <typename T> T handle::cast() const { return pybind11::cast<T>(*this); }
template <> inline void handle::cast() const { return; }
template <typename T>
typename std::enable_if<detail::move_always<T>::value || detail::move_if_unreferenced<T>::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<T>() + " instance: instance has multiple references");
#endif
typedef detail::type_caster<T> 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<T>() + "''");
#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 T> typename std::enable_if<detail::move_always<T>::value, T>::type cast(object &&object) {
return move<T>(std::move(object));
}
template <typename T> typename std::enable_if<detail::move_if_unreferenced<T>::value, T>::type cast(object &&object) {
if (object.ref_count() > 1)
return cast<T>(object);
else
return move<T>(std::move(object));
}
template <typename T> typename std::enable_if<detail::move_never<T>::value, T>::type cast(object &&object) {
return cast<T>(object);
}
template <typename T> T object::cast() const & { return pybind11::cast<T>(*this); }
template <typename T> T object::cast() && { return pybind11::cast<T>(std::move(*this)); }
template <> inline void object::cast() const & { return; }
template <> inline void object::cast() && { return; }
template <return_value_policy policy = return_value_policy::automatic_reference,
typename... Args> tuple make_tuple(Args&&... args_) {
const size_t size = sizeof...(Args);

View File

@ -89,6 +89,11 @@ public:
}
return *this;
}
// Calling cast() on an object lvalue just copies (via handle::cast)
template <typename T> T cast() const &;
// Calling on an object rvalue does a move, if needed and/or possible
template <typename T> T cast() &&;
};
NAMESPACE_BEGIN(detail)