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:
Jason Rhinelander 2017-05-14 15:57:26 -04:00
parent fe0cf8b73b
commit 813d7e8687
9 changed files with 342 additions and 92 deletions

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

@ -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);
});

View File

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