mirror of
https://github.com/pybind/pybind11.git
synced 2025-02-16 13:47:53 +00:00
Add movable cast support to type casters
This commit allows type_casters to allow their local values to be moved away, rather than copied, when the type caster instance itself is an rvalue. This only applies (automatically) to type casters using PYBIND11_TYPE_CASTER; the generic type type casters don't own their own pointer, and various value casters (e.g. std::string, std::pair, arithmetic types) already cast to an rvalue (i.e. they return by value). This updates various calling code to attempt to get a movable value whenever the value is itself coming from a type caster about to be destroyed: for example, when constructing an std::pair or various stl.h containers. For types that don't support value moving, the cast_op falls back to an lvalue cast. There wasn't an obvious place to add the tests, so I added them to test_copy_move_policies, but also renamed it to drop the _policies as it now tests more than just policies.
This commit is contained in:
parent
fe0cf8b73b
commit
813d7e8687
@ -381,11 +381,33 @@ protected:
|
||||
object temp;
|
||||
};
|
||||
|
||||
/* Determine suitable casting operator */
|
||||
/**
|
||||
* Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster
|
||||
* needs to provide `operator T*()` and `operator T&()` operators.
|
||||
*
|
||||
* If the type supports moving the value away via an `operator T&&() &&` method, it should use
|
||||
* `movable_cast_op_type` instead.
|
||||
*/
|
||||
template <typename T>
|
||||
using cast_op_type = typename std::conditional<std::is_pointer<typename std::remove_reference<T>::type>::value,
|
||||
typename std::add_pointer<intrinsic_t<T>>::type,
|
||||
typename std::add_lvalue_reference<intrinsic_t<T>>::type>::type;
|
||||
using cast_op_type =
|
||||
conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value,
|
||||
typename std::add_pointer<intrinsic_t<T>>::type,
|
||||
typename std::add_lvalue_reference<intrinsic_t<T>>::type>;
|
||||
|
||||
/**
|
||||
* Determine suitable casting operator for a type caster with a movable value. Such a type caster
|
||||
* needs to provide `operator T*()`, `operator T&()`, and `operator T&&() &&`. The latter will be
|
||||
* called in appropriate contexts where the value can be moved rather than copied.
|
||||
*
|
||||
* These operator are automatically provided when using the PYBIND11_TYPE_CASTER macro.
|
||||
*/
|
||||
template <typename T>
|
||||
using movable_cast_op_type =
|
||||
conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value,
|
||||
typename std::add_pointer<intrinsic_t<T>>::type,
|
||||
conditional_t<std::is_rvalue_reference<T>::value,
|
||||
typename std::add_rvalue_reference<intrinsic_t<T>>::type,
|
||||
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>;
|
||||
|
||||
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
|
||||
// T is non-copyable, but code containing such a copy constructor fails to actually compile.
|
||||
@ -462,7 +484,7 @@ public:
|
||||
nullptr, nullptr, holder);
|
||||
}
|
||||
|
||||
template <typename T> using cast_op_type = pybind11::detail::cast_op_type<T>;
|
||||
template <typename T> using cast_op_type = cast_op_type<T>;
|
||||
|
||||
operator itype*() { return (type *) value; }
|
||||
operator itype&() { if (!value) throw reference_cast_error(); return *((itype *) value); }
|
||||
@ -498,8 +520,10 @@ template <typename type> using make_caster = type_caster<intrinsic_t<type>>;
|
||||
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
|
||||
return caster.operator typename make_caster<T>::template cast_op_type<T>();
|
||||
}
|
||||
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &&caster) {
|
||||
return cast_op<T>(caster);
|
||||
template <typename T> typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>
|
||||
cast_op(make_caster<T> &&caster) {
|
||||
return std::move(caster).operator
|
||||
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> {
|
||||
@ -522,7 +546,8 @@ public:
|
||||
} \
|
||||
operator type*() { return &value; } \
|
||||
operator type&() { return value; } \
|
||||
template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>
|
||||
operator type&&() && { return std::move(value); } \
|
||||
template <typename _T> using cast_op_type = pybind11::detail::movable_cast_op_type<_T>
|
||||
|
||||
|
||||
template <typename CharT> using is_std_char_type = any_of<
|
||||
@ -892,9 +917,8 @@ public:
|
||||
|
||||
template <typename T> using cast_op_type = type;
|
||||
|
||||
operator type() {
|
||||
return type(cast_op<T1>(first), cast_op<T2>(second));
|
||||
}
|
||||
operator type() & { return type(cast_op<T1>(first), cast_op<T2>(second)); }
|
||||
operator type() && { return type(cast_op<T1>(std::move(first)), cast_op<T2>(std::move(second))); }
|
||||
protected:
|
||||
make_caster<T1> first;
|
||||
make_caster<T2> second;
|
||||
@ -925,17 +949,21 @@ public:
|
||||
|
||||
template <typename T> using cast_op_type = type;
|
||||
|
||||
operator type() { return implicit_cast(indices{}); }
|
||||
operator type() & { return implicit_cast(indices{}); }
|
||||
operator type() && { return std::move(*this).implicit_cast(indices{}); }
|
||||
|
||||
protected:
|
||||
template <size_t... Is>
|
||||
type implicit_cast(index_sequence<Is...>) { return type(cast_op<Tuple>(std::get<Is>(value))...); }
|
||||
type implicit_cast(index_sequence<Is...>) & { return type(cast_op<Tuple>(std::get<Is>(subcasters))...); }
|
||||
template <size_t... Is>
|
||||
type implicit_cast(index_sequence<Is...>) && { return type(cast_op<Tuple>(std::move(std::get<Is>(subcasters)))...); }
|
||||
|
||||
|
||||
static constexpr bool load_impl(const sequence &, bool, index_sequence<>) { return true; }
|
||||
|
||||
template <size_t... Is>
|
||||
bool load_impl(const sequence &seq, bool convert, index_sequence<Is...>) {
|
||||
for (bool r : {std::get<Is>(value).load(seq[Is], convert)...})
|
||||
for (bool r : {std::get<Is>(subcasters).load(seq[Is], convert)...})
|
||||
if (!r)
|
||||
return false;
|
||||
return true;
|
||||
@ -960,7 +988,7 @@ protected:
|
||||
return result.release();
|
||||
}
|
||||
|
||||
std::tuple<make_caster<Tuple>...> value;
|
||||
std::tuple<make_caster<Tuple>...> subcasters;
|
||||
};
|
||||
|
||||
/// Helper class which abstracts away certain actions. Users can provide specializations for
|
||||
@ -1465,13 +1493,13 @@ public:
|
||||
}
|
||||
|
||||
template <typename Return, typename Guard, typename Func>
|
||||
enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) {
|
||||
return call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
|
||||
enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) && {
|
||||
return std::move(*this).template call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
|
||||
}
|
||||
|
||||
template <typename Return, typename Guard, typename Func>
|
||||
enable_if_t<std::is_void<Return>::value, void_type> call(Func &&f) {
|
||||
call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
|
||||
enable_if_t<std::is_void<Return>::value, void_type> call(Func &&f) && {
|
||||
std::move(*this).template call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
|
||||
return void_type();
|
||||
}
|
||||
|
||||
@ -1481,7 +1509,7 @@ private:
|
||||
|
||||
template <size_t... Is>
|
||||
bool load_impl_sequence(function_call &call, index_sequence<Is...>) {
|
||||
for (bool r : {std::get<Is>(value).load(call.args[Is], call.args_convert[Is])...})
|
||||
for (bool r : {std::get<Is>(argcasters).load(call.args[Is], call.args_convert[Is])...})
|
||||
if (!r)
|
||||
return false;
|
||||
return true;
|
||||
@ -1489,10 +1517,10 @@ private:
|
||||
|
||||
template <typename Return, typename Func, size_t... Is, typename Guard>
|
||||
Return call_impl(Func &&f, index_sequence<Is...>, Guard &&) {
|
||||
return std::forward<Func>(f)(cast_op<Args>(std::get<Is>(value))...);
|
||||
return std::forward<Func>(f)(cast_op<Args>(std::move(std::get<Is>(argcasters)))...);
|
||||
}
|
||||
|
||||
std::tuple<make_caster<Args>...> value;
|
||||
std::tuple<make_caster<Args>...> argcasters;
|
||||
};
|
||||
|
||||
/// Helper class which collects only positional arguments for a Python function call.
|
||||
|
@ -341,7 +341,8 @@ public:
|
||||
|
||||
operator Type*() { return &value; }
|
||||
operator Type&() { return value; }
|
||||
template <typename T> using cast_op_type = cast_op_type<T>;
|
||||
operator Type&&() && { return std::move(value); }
|
||||
template <typename T> using cast_op_type = movable_cast_op_type<T>;
|
||||
|
||||
private:
|
||||
Type value;
|
||||
|
@ -150,8 +150,8 @@ protected:
|
||||
using Guard = detail::extract_guard_t<Extra...>;
|
||||
|
||||
/* Perform the function call */
|
||||
handle result = cast_out::cast(args_converter.template call<Return, Guard>(cap->f),
|
||||
policy, call.parent);
|
||||
handle result = cast_out::cast(
|
||||
std::move(args_converter).template call<Return, Guard>(cap->f), policy, call.parent);
|
||||
|
||||
/* Invoke call policy post-call hook */
|
||||
detail::process_attributes<Extra...>::postcall(call, result);
|
||||
|
@ -60,11 +60,11 @@ template <typename Type, typename Key> struct set_caster {
|
||||
return false;
|
||||
auto s = reinterpret_borrow<pybind11::set>(src);
|
||||
value.clear();
|
||||
key_conv conv;
|
||||
for (auto entry : s) {
|
||||
key_conv conv;
|
||||
if (!conv.load(entry, convert))
|
||||
return false;
|
||||
value.insert(cast_op<Key>(conv));
|
||||
value.insert(cast_op<Key &&>(std::move(conv)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -90,14 +90,14 @@ template <typename Type, typename Key, typename Value> struct map_caster {
|
||||
if (!isinstance<dict>(src))
|
||||
return false;
|
||||
auto d = reinterpret_borrow<dict>(src);
|
||||
key_conv kconv;
|
||||
value_conv vconv;
|
||||
value.clear();
|
||||
for (auto it : d) {
|
||||
key_conv kconv;
|
||||
value_conv vconv;
|
||||
if (!kconv.load(it.first.ptr(), convert) ||
|
||||
!vconv.load(it.second.ptr(), convert))
|
||||
return false;
|
||||
value.emplace(cast_op<Key>(kconv), cast_op<Value>(vconv));
|
||||
value.emplace(cast_op<Key &&>(std::move(kconv)), cast_op<Value &&>(std::move(vconv)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -124,13 +124,13 @@ template <typename Type, typename Value> struct list_caster {
|
||||
if (!isinstance<sequence>(src))
|
||||
return false;
|
||||
auto s = reinterpret_borrow<sequence>(src);
|
||||
value_conv conv;
|
||||
value.clear();
|
||||
reserve_maybe(s, &value);
|
||||
for (auto it : s) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert))
|
||||
return false;
|
||||
value.push_back(cast_op<Value>(conv));
|
||||
value.push_back(cast_op<Value &&>(std::move(conv)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -185,12 +185,12 @@ public:
|
||||
auto l = reinterpret_borrow<list>(src);
|
||||
if (!require_size(l.size()))
|
||||
return false;
|
||||
value_conv conv;
|
||||
size_t ctr = 0;
|
||||
for (auto it : l) {
|
||||
value_conv conv;
|
||||
if (!conv.load(it, convert))
|
||||
return false;
|
||||
value[ctr++] = cast_op<Value>(conv);
|
||||
value[ctr++] = cast_op<Value &&>(std::move(conv));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -249,7 +249,7 @@ template<typename T> struct optional_caster {
|
||||
if (!inner_caster.load(src, convert))
|
||||
return false;
|
||||
|
||||
value.emplace(cast_op<typename T::value_type>(inner_caster));
|
||||
value.emplace(cast_op<typename T::value_type &&>(std::move(inner_caster)));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ set(PYBIND11_TEST_FILES
|
||||
test_chrono.cpp
|
||||
test_class_args.cpp
|
||||
test_constants_and_functions.cpp
|
||||
test_copy_move_policies.cpp
|
||||
test_copy_move.cpp
|
||||
test_docstring_options.cpp
|
||||
test_eigen.cpp
|
||||
test_enum.cpp
|
||||
|
177
tests/test_copy_move.cpp
Normal file
177
tests/test_copy_move.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
tests/test_copy_move_policies.cpp -- 'copy' and 'move' return value policies
|
||||
and related tests
|
||||
|
||||
Copyright (c) 2016 Ben North <ben@redfrontdoor.org>
|
||||
|
||||
All rights reserved. Use of this source code is governed by a
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
#include "constructor_stats.h"
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
template <typename derived>
|
||||
struct empty {
|
||||
static const derived& get_one() { return instance_; }
|
||||
static derived instance_;
|
||||
};
|
||||
|
||||
struct lacking_copy_ctor : public empty<lacking_copy_ctor> {
|
||||
lacking_copy_ctor() {}
|
||||
lacking_copy_ctor(const lacking_copy_ctor& other) = delete;
|
||||
};
|
||||
|
||||
template <> lacking_copy_ctor empty<lacking_copy_ctor>::instance_ = {};
|
||||
|
||||
struct lacking_move_ctor : public empty<lacking_move_ctor> {
|
||||
lacking_move_ctor() {}
|
||||
lacking_move_ctor(const lacking_move_ctor& other) = delete;
|
||||
lacking_move_ctor(lacking_move_ctor&& other) = delete;
|
||||
};
|
||||
|
||||
template <> lacking_move_ctor empty<lacking_move_ctor>::instance_ = {};
|
||||
|
||||
/* Custom type caster move/copy test classes */
|
||||
class MoveOnlyInt {
|
||||
public:
|
||||
MoveOnlyInt() { print_default_created(this); }
|
||||
MoveOnlyInt(int v) : value{std::move(v)} { print_created(this, value); }
|
||||
MoveOnlyInt(MoveOnlyInt &&m) { print_move_created(this, m.value); std::swap(value, m.value); }
|
||||
MoveOnlyInt &operator=(MoveOnlyInt &&m) { print_move_assigned(this, m.value); std::swap(value, m.value); return *this; }
|
||||
MoveOnlyInt(const MoveOnlyInt &) = delete;
|
||||
MoveOnlyInt &operator=(const MoveOnlyInt &) = delete;
|
||||
~MoveOnlyInt() { print_destroyed(this); }
|
||||
|
||||
int value;
|
||||
};
|
||||
class MoveOrCopyInt {
|
||||
public:
|
||||
MoveOrCopyInt() { print_default_created(this); }
|
||||
MoveOrCopyInt(int v) : value{std::move(v)} { print_created(this, value); }
|
||||
MoveOrCopyInt(MoveOrCopyInt &&m) { print_move_created(this, m.value); std::swap(value, m.value); }
|
||||
MoveOrCopyInt &operator=(MoveOrCopyInt &&m) { print_move_assigned(this, m.value); std::swap(value, m.value); return *this; }
|
||||
MoveOrCopyInt(const MoveOrCopyInt &c) { print_copy_created(this, c.value); value = c.value; }
|
||||
MoveOrCopyInt &operator=(const MoveOrCopyInt &c) { print_copy_assigned(this, c.value); value = c.value; return *this; }
|
||||
~MoveOrCopyInt() { print_destroyed(this); }
|
||||
|
||||
int value;
|
||||
};
|
||||
class CopyOnlyInt {
|
||||
public:
|
||||
CopyOnlyInt() { print_default_created(this); }
|
||||
CopyOnlyInt(int v) : value{std::move(v)} { print_created(this, value); }
|
||||
CopyOnlyInt(const CopyOnlyInt &c) { print_copy_created(this, c.value); value = c.value; }
|
||||
CopyOnlyInt &operator=(const CopyOnlyInt &c) { print_copy_assigned(this, c.value); value = c.value; return *this; }
|
||||
~CopyOnlyInt() { print_destroyed(this); }
|
||||
|
||||
int value;
|
||||
};
|
||||
namespace pybind11 { namespace detail {
|
||||
template <> struct type_caster<MoveOnlyInt> {
|
||||
PYBIND11_TYPE_CASTER(MoveOnlyInt, _("MoveOnlyInt"));
|
||||
bool load(handle src, bool) { value = MoveOnlyInt(src.cast<int>()); return true; }
|
||||
static handle cast(const MoveOnlyInt &m, return_value_policy r, handle p) { return pybind11::cast(m.value, r, p); }
|
||||
};
|
||||
|
||||
template <> struct type_caster<MoveOrCopyInt> {
|
||||
PYBIND11_TYPE_CASTER(MoveOrCopyInt, _("MoveOrCopyInt"));
|
||||
bool load(handle src, bool) { value = MoveOrCopyInt(src.cast<int>()); return true; }
|
||||
static handle cast(const MoveOrCopyInt &m, return_value_policy r, handle p) { return pybind11::cast(m.value, r, p); }
|
||||
};
|
||||
|
||||
template <> struct type_caster<CopyOnlyInt> {
|
||||
protected:
|
||||
CopyOnlyInt value;
|
||||
public:
|
||||
static PYBIND11_DESCR name() { return _("CopyOnlyInt"); }
|
||||
bool load(handle src, bool) { value = CopyOnlyInt(src.cast<int>()); return true; }
|
||||
static handle cast(const CopyOnlyInt &m, return_value_policy r, handle p) { return pybind11::cast(m.value, r, p); }
|
||||
static handle cast(const CopyOnlyInt *src, return_value_policy policy, handle parent) {
|
||||
if (!src) return none().release();
|
||||
return cast(*src, policy, parent);
|
||||
}
|
||||
operator CopyOnlyInt*() { return &value; }
|
||||
operator CopyOnlyInt&() { return value; }
|
||||
template <typename T> using cast_op_type = pybind11::detail::cast_op_type<T>;
|
||||
};
|
||||
}}
|
||||
|
||||
|
||||
test_initializer copy_move_policies([](py::module &m) {
|
||||
py::class_<lacking_copy_ctor>(m, "lacking_copy_ctor")
|
||||
.def_static("get_one", &lacking_copy_ctor::get_one,
|
||||
py::return_value_policy::copy);
|
||||
py::class_<lacking_move_ctor>(m, "lacking_move_ctor")
|
||||
.def_static("get_one", &lacking_move_ctor::get_one,
|
||||
py::return_value_policy::move);
|
||||
|
||||
m.def("move_only", [](MoveOnlyInt m) {
|
||||
return m.value;
|
||||
});
|
||||
m.def("move_or_copy", [](MoveOrCopyInt m) {
|
||||
return m.value;
|
||||
});
|
||||
m.def("copy_only", [](CopyOnlyInt m) {
|
||||
return m.value;
|
||||
});
|
||||
m.def("move_and_copy_casts", [](py::object o) {
|
||||
int r = 0;
|
||||
r += py::cast<MoveOrCopyInt>(o).value; /* moves */
|
||||
r += py::cast<MoveOnlyInt>(o).value; /* moves */
|
||||
r += py::cast<CopyOnlyInt>(o).value; /* copies */
|
||||
MoveOrCopyInt m1(py::cast<MoveOrCopyInt>(o)); /* moves */
|
||||
MoveOnlyInt m2(py::cast<MoveOnlyInt>(o)); /* moves */
|
||||
CopyOnlyInt m3(py::cast<CopyOnlyInt>(o)); /* copies */
|
||||
r += m1.value + m2.value + m3.value;
|
||||
|
||||
return r;
|
||||
});
|
||||
m.def("move_pair", [](std::pair<MoveOnlyInt, MoveOrCopyInt> p) {
|
||||
return p.first.value + p.second.value;
|
||||
});
|
||||
m.def("move_tuple", [](std::tuple<MoveOnlyInt, MoveOrCopyInt, MoveOnlyInt> t) {
|
||||
return std::get<0>(t).value + std::get<1>(t).value + std::get<2>(t).value;
|
||||
});
|
||||
m.def("copy_tuple", [](std::tuple<CopyOnlyInt, CopyOnlyInt> t) {
|
||||
return std::get<0>(t).value + std::get<1>(t).value;
|
||||
});
|
||||
m.def("move_copy_nested", [](std::pair<MoveOnlyInt, std::pair<std::tuple<MoveOrCopyInt, CopyOnlyInt, std::tuple<MoveOnlyInt>>, MoveOrCopyInt>> x) {
|
||||
return x.first.value + std::get<0>(x.second.first).value + std::get<1>(x.second.first).value +
|
||||
std::get<0>(std::get<2>(x.second.first)).value + x.second.second.value;
|
||||
});
|
||||
m.def("move_and_copy_cstats", []() {
|
||||
ConstructorStats::gc();
|
||||
// Reset counts to 0 so that previous tests don't affect later ones:
|
||||
auto &mc = ConstructorStats::get<MoveOrCopyInt>();
|
||||
mc.move_assignments = mc.move_constructions = mc.copy_assignments = mc.copy_constructions = 0;
|
||||
auto &mo = ConstructorStats::get<MoveOnlyInt>();
|
||||
mo.move_assignments = mo.move_constructions = mo.copy_assignments = mo.copy_constructions = 0;
|
||||
auto &co = ConstructorStats::get<CopyOnlyInt>();
|
||||
co.move_assignments = co.move_constructions = co.copy_assignments = co.copy_constructions = 0;
|
||||
py::dict d;
|
||||
d["MoveOrCopyInt"] = py::cast(mc, py::return_value_policy::reference);
|
||||
d["MoveOnlyInt"] = py::cast(mo, py::return_value_policy::reference);
|
||||
d["CopyOnlyInt"] = py::cast(co, py::return_value_policy::reference);
|
||||
return d;
|
||||
});
|
||||
#ifdef PYBIND11_HAS_OPTIONAL
|
||||
m.attr("has_optional") = true;
|
||||
m.def("move_optional", [](std::optional<MoveOnlyInt> o) {
|
||||
return o->value;
|
||||
});
|
||||
m.def("move_or_copy_optional", [](std::optional<MoveOrCopyInt> o) {
|
||||
return o->value;
|
||||
});
|
||||
m.def("copy_optional", [](std::optional<CopyOnlyInt> o) {
|
||||
return o->value;
|
||||
});
|
||||
m.def("move_optional_tuple", [](std::optional<std::tuple<MoveOrCopyInt, MoveOnlyInt, CopyOnlyInt>> x) {
|
||||
return std::get<0>(*x).value + std::get<1>(*x).value + std::get<2>(*x).value;
|
||||
});
|
||||
#else
|
||||
m.attr("has_optional") = false;
|
||||
#endif
|
||||
|
||||
});
|
100
tests/test_copy_move.py
Normal file
100
tests/test_copy_move.py
Normal file
@ -0,0 +1,100 @@
|
||||
import pytest
|
||||
from pybind11_tests import has_optional
|
||||
|
||||
|
||||
def test_lacking_copy_ctor():
|
||||
from pybind11_tests import lacking_copy_ctor
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
lacking_copy_ctor.get_one()
|
||||
assert "the object is non-copyable!" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_lacking_move_ctor():
|
||||
from pybind11_tests import lacking_move_ctor
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
lacking_move_ctor.get_one()
|
||||
assert "the object is neither movable nor copyable!" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_move_and_copy_casts():
|
||||
"""Cast some values in C++ via custom type casters and count the number of moves/copies."""
|
||||
from pybind11_tests import move_and_copy_casts, move_and_copy_cstats
|
||||
|
||||
cstats = move_and_copy_cstats()
|
||||
c_m, c_mc, c_c = cstats["MoveOnlyInt"], cstats["MoveOrCopyInt"], cstats["CopyOnlyInt"]
|
||||
|
||||
# The type move constructions/assignments below each get incremented: the move assignment comes
|
||||
# from the type_caster load; the move construction happens when extracting that via a cast or
|
||||
# loading into an argument.
|
||||
assert move_and_copy_casts(3) == 18
|
||||
assert c_m.copy_assignments + c_m.copy_constructions == 0
|
||||
assert c_m.move_assignments == 2
|
||||
assert c_m.move_constructions == 2
|
||||
assert c_mc.alive() == 0
|
||||
assert c_mc.copy_assignments + c_mc.copy_constructions == 0
|
||||
assert c_mc.move_assignments == 2
|
||||
assert c_mc.move_constructions == 2
|
||||
assert c_c.alive() == 0
|
||||
assert c_c.copy_assignments == 2
|
||||
assert c_c.copy_constructions == 2
|
||||
assert c_m.alive() + c_mc.alive() + c_c.alive() == 0
|
||||
|
||||
|
||||
def test_move_and_copy_loads():
|
||||
"""Call some functions that load arguments via custom type casters and count the number of
|
||||
moves/copies."""
|
||||
from pybind11_tests import (move_and_copy_cstats, move_only, move_or_copy, copy_only,
|
||||
move_pair, move_tuple, copy_tuple, move_copy_nested)
|
||||
|
||||
cstats = move_and_copy_cstats()
|
||||
c_m, c_mc, c_c = cstats["MoveOnlyInt"], cstats["MoveOrCopyInt"], cstats["CopyOnlyInt"]
|
||||
|
||||
assert move_only(10) == 10 # 1 move, c_m
|
||||
assert move_or_copy(11) == 11 # 1 move, c_mc
|
||||
assert copy_only(12) == 12 # 1 copy, c_c
|
||||
assert move_pair((13, 14)) == 27 # 1 c_m move, 1 c_mc move
|
||||
assert move_tuple((15, 16, 17)) == 48 # 2 c_m moves, 1 c_mc move
|
||||
assert copy_tuple((18, 19)) == 37 # 2 c_c copies
|
||||
# Direct constructions: 2 c_m moves, 2 c_mc moves, 1 c_c copy
|
||||
# Extra moves/copies when moving pairs/tuples: 3 c_m, 3 c_mc, 2 c_c
|
||||
assert move_copy_nested((1, ((2, 3, (4,)), 5))) == 15
|
||||
|
||||
assert c_m.copy_assignments + c_m.copy_constructions == 0
|
||||
assert c_m.move_assignments == 6
|
||||
assert c_m.move_constructions == 9
|
||||
assert c_mc.copy_assignments + c_mc.copy_constructions == 0
|
||||
assert c_mc.move_assignments == 5
|
||||
assert c_mc.move_constructions == 8
|
||||
assert c_c.copy_assignments == 4
|
||||
assert c_c.copy_constructions == 6
|
||||
assert c_m.alive() + c_mc.alive() + c_c.alive() == 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_optional, reason='no <optional>')
|
||||
def test_move_and_copy_load_optional():
|
||||
"""Tests move/copy loads of std::optional arguments"""
|
||||
from pybind11_tests import (move_and_copy_cstats, move_optional, move_or_copy_optional,
|
||||
copy_optional, move_optional_tuple)
|
||||
|
||||
cstats = move_and_copy_cstats()
|
||||
c_m, c_mc, c_c = cstats["MoveOnlyInt"], cstats["MoveOrCopyInt"], cstats["CopyOnlyInt"]
|
||||
|
||||
# The extra move/copy constructions below come from the std::optional move (which has to move
|
||||
# its arguments):
|
||||
assert move_optional(10) == 10 # c_m: 1 move assign, 2 move construct
|
||||
assert move_or_copy_optional(11) == 11 # c_mc: 1 move assign, 2 move construct
|
||||
assert copy_optional(12) == 12 # c_c: 1 copy assign, 2 copy construct
|
||||
# 1 move assign + move construct moves each of c_m, c_mc, 1 c_c copy
|
||||
# +1 move/copy construct each from moving the tuple
|
||||
# +1 move/copy construct each from moving the optional (which moves the tuple again)
|
||||
assert move_optional_tuple((3, 4, 5)) == 12
|
||||
|
||||
assert c_m.copy_assignments + c_m.copy_constructions == 0
|
||||
assert c_m.move_assignments == 2
|
||||
assert c_m.move_constructions == 5
|
||||
assert c_mc.copy_assignments + c_mc.copy_constructions == 0
|
||||
assert c_mc.move_assignments == 2
|
||||
assert c_mc.move_constructions == 5
|
||||
assert c_c.copy_assignments == 2
|
||||
assert c_c.copy_constructions == 5
|
||||
assert c_m.alive() + c_mc.alive() + c_c.alive() == 0
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
tests/test_copy_move_policies.cpp -- 'copy' and 'move'
|
||||
return value policies
|
||||
|
||||
Copyright (c) 2016 Ben North <ben@redfrontdoor.org>
|
||||
|
||||
All rights reserved. Use of this source code is governed by a
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
template <typename derived>
|
||||
struct empty {
|
||||
static const derived& get_one() { return instance_; }
|
||||
static derived instance_;
|
||||
};
|
||||
|
||||
struct lacking_copy_ctor : public empty<lacking_copy_ctor> {
|
||||
lacking_copy_ctor() {}
|
||||
lacking_copy_ctor(const lacking_copy_ctor& other) = delete;
|
||||
};
|
||||
|
||||
template <> lacking_copy_ctor empty<lacking_copy_ctor>::instance_ = {};
|
||||
|
||||
struct lacking_move_ctor : public empty<lacking_move_ctor> {
|
||||
lacking_move_ctor() {}
|
||||
lacking_move_ctor(const lacking_move_ctor& other) = delete;
|
||||
lacking_move_ctor(lacking_move_ctor&& other) = delete;
|
||||
};
|
||||
|
||||
template <> lacking_move_ctor empty<lacking_move_ctor>::instance_ = {};
|
||||
|
||||
test_initializer copy_move_policies([](py::module &m) {
|
||||
py::class_<lacking_copy_ctor>(m, "lacking_copy_ctor")
|
||||
.def_static("get_one", &lacking_copy_ctor::get_one,
|
||||
py::return_value_policy::copy);
|
||||
py::class_<lacking_move_ctor>(m, "lacking_move_ctor")
|
||||
.def_static("get_one", &lacking_move_ctor::get_one,
|
||||
py::return_value_policy::move);
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_lacking_copy_ctor():
|
||||
from pybind11_tests import lacking_copy_ctor
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
lacking_copy_ctor.get_one()
|
||||
assert "the object is non-copyable!" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_lacking_move_ctor():
|
||||
from pybind11_tests import lacking_move_ctor
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
lacking_move_ctor.get_one()
|
||||
assert "the object is neither movable nor copyable!" in str(excinfo.value)
|
Loading…
Reference in New Issue
Block a user