Skip to content

Commit 813d7e8

Browse files
committed
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.
1 parent fe0cf8b commit 813d7e8

9 files changed

+342
-92
lines changed

include/pybind11/cast.h

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,33 @@ class type_caster_generic {
381381
object temp;
382382
};
383383

384-
/* Determine suitable casting operator */
384+
/**
385+
* Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster
386+
* needs to provide `operator T*()` and `operator T&()` operators.
387+
*
388+
* If the type supports moving the value away via an `operator T&&() &&` method, it should use
389+
* `movable_cast_op_type` instead.
390+
*/
385391
template <typename T>
386-
using cast_op_type = typename std::conditional<std::is_pointer<typename std::remove_reference<T>::type>::value,
387-
typename std::add_pointer<intrinsic_t<T>>::type,
388-
typename std::add_lvalue_reference<intrinsic_t<T>>::type>::type;
392+
using cast_op_type =
393+
conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value,
394+
typename std::add_pointer<intrinsic_t<T>>::type,
395+
typename std::add_lvalue_reference<intrinsic_t<T>>::type>;
396+
397+
/**
398+
* Determine suitable casting operator for a type caster with a movable value. Such a type caster
399+
* needs to provide `operator T*()`, `operator T&()`, and `operator T&&() &&`. The latter will be
400+
* called in appropriate contexts where the value can be moved rather than copied.
401+
*
402+
* These operator are automatically provided when using the PYBIND11_TYPE_CASTER macro.
403+
*/
404+
template <typename T>
405+
using movable_cast_op_type =
406+
conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value,
407+
typename std::add_pointer<intrinsic_t<T>>::type,
408+
conditional_t<std::is_rvalue_reference<T>::value,
409+
typename std::add_rvalue_reference<intrinsic_t<T>>::type,
410+
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>;
389411

390412
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
391413
// T is non-copyable, but code containing such a copy constructor fails to actually compile.
@@ -462,7 +484,7 @@ template <typename type> class type_caster_base : public type_caster_generic {
462484
nullptr, nullptr, holder);
463485
}
464486

465-
template <typename T> using cast_op_type = pybind11::detail::cast_op_type<T>;
487+
template <typename T> using cast_op_type = cast_op_type<T>;
466488

467489
operator itype*() { return (type *) value; }
468490
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>>;
498520
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
499521
return caster.operator typename make_caster<T>::template cast_op_type<T>();
500522
}
501-
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &&caster) {
502-
return cast_op<T>(caster);
523+
template <typename T> typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>
524+
cast_op(make_caster<T> &&caster) {
525+
return std::move(caster).operator
526+
typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>();
503527
}
504528

505529
template <typename type> class type_caster<std::reference_wrapper<type>> : public type_caster_base<type> {
@@ -522,7 +546,8 @@ template <typename type> class type_caster<std::reference_wrapper<type>> : publi
522546
} \
523547
operator type*() { return &value; } \
524548
operator type&() { return value; } \
525-
template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>
549+
operator type&&() && { return std::move(value); } \
550+
template <typename _T> using cast_op_type = pybind11::detail::movable_cast_op_type<_T>
526551

527552

528553
template <typename CharT> using is_std_char_type = any_of<
@@ -892,9 +917,8 @@ template <typename T1, typename T2> class type_caster<std::pair<T1, T2>> {
892917

893918
template <typename T> using cast_op_type = type;
894919

895-
operator type() {
896-
return type(cast_op<T1>(first), cast_op<T2>(second));
897-
}
920+
operator type() & { return type(cast_op<T1>(first), cast_op<T2>(second)); }
921+
operator type() && { return type(cast_op<T1>(std::move(first)), cast_op<T2>(std::move(second))); }
898922
protected:
899923
make_caster<T1> first;
900924
make_caster<T2> second;
@@ -925,17 +949,21 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> {
925949

926950
template <typename T> using cast_op_type = type;
927951

928-
operator type() { return implicit_cast(indices{}); }
952+
operator type() & { return implicit_cast(indices{}); }
953+
operator type() && { return std::move(*this).implicit_cast(indices{}); }
929954

930955
protected:
931956
template <size_t... Is>
932-
type implicit_cast(index_sequence<Is...>) { return type(cast_op<Tuple>(std::get<Is>(value))...); }
957+
type implicit_cast(index_sequence<Is...>) & { return type(cast_op<Tuple>(std::get<Is>(subcasters))...); }
958+
template <size_t... Is>
959+
type implicit_cast(index_sequence<Is...>) && { return type(cast_op<Tuple>(std::move(std::get<Is>(subcasters)))...); }
960+
933961

934962
static constexpr bool load_impl(const sequence &, bool, index_sequence<>) { return true; }
935963

936964
template <size_t... Is>
937965
bool load_impl(const sequence &seq, bool convert, index_sequence<Is...>) {
938-
for (bool r : {std::get<Is>(value).load(seq[Is], convert)...})
966+
for (bool r : {std::get<Is>(subcasters).load(seq[Is], convert)...})
939967
if (!r)
940968
return false;
941969
return true;
@@ -960,7 +988,7 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> {
960988
return result.release();
961989
}
962990

963-
std::tuple<make_caster<Tuple>...> value;
991+
std::tuple<make_caster<Tuple>...> subcasters;
964992
};
965993

966994
/// Helper class which abstracts away certain actions. Users can provide specializations for
@@ -1465,13 +1493,13 @@ class argument_loader {
14651493
}
14661494

14671495
template <typename Return, typename Guard, typename Func>
1468-
enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) {
1469-
return call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
1496+
enable_if_t<!std::is_void<Return>::value, Return> call(Func &&f) && {
1497+
return std::move(*this).template call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
14701498
}
14711499

14721500
template <typename Return, typename Guard, typename Func>
1473-
enable_if_t<std::is_void<Return>::value, void_type> call(Func &&f) {
1474-
call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
1501+
enable_if_t<std::is_void<Return>::value, void_type> call(Func &&f) && {
1502+
std::move(*this).template call_impl<Return>(std::forward<Func>(f), indices{}, Guard{});
14751503
return void_type();
14761504
}
14771505

@@ -1481,18 +1509,18 @@ class argument_loader {
14811509

14821510
template <size_t... Is>
14831511
bool load_impl_sequence(function_call &call, index_sequence<Is...>) {
1484-
for (bool r : {std::get<Is>(value).load(call.args[Is], call.args_convert[Is])...})
1512+
for (bool r : {std::get<Is>(argcasters).load(call.args[Is], call.args_convert[Is])...})
14851513
if (!r)
14861514
return false;
14871515
return true;
14881516
}
14891517

14901518
template <typename Return, typename Func, size_t... Is, typename Guard>
14911519
Return call_impl(Func &&f, index_sequence<Is...>, Guard &&) {
1492-
return std::forward<Func>(f)(cast_op<Args>(std::get<Is>(value))...);
1520+
return std::forward<Func>(f)(cast_op<Args>(std::move(std::get<Is>(argcasters)))...);
14931521
}
14941522

1495-
std::tuple<make_caster<Args>...> value;
1523+
std::tuple<make_caster<Args>...> argcasters;
14961524
};
14971525

14981526
/// Helper class which collects only positional arguments for a Python function call.

include/pybind11/eigen.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
341341

342342
operator Type*() { return &value; }
343343
operator Type&() { return value; }
344-
template <typename T> using cast_op_type = cast_op_type<T>;
344+
operator Type&&() && { return std::move(value); }
345+
template <typename T> using cast_op_type = movable_cast_op_type<T>;
345346

346347
private:
347348
Type value;

include/pybind11/pybind11.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ class cpp_function : public function {
150150
using Guard = detail::extract_guard_t<Extra...>;
151151

152152
/* Perform the function call */
153-
handle result = cast_out::cast(args_converter.template call<Return, Guard>(cap->f),
154-
policy, call.parent);
153+
handle result = cast_out::cast(
154+
std::move(args_converter).template call<Return, Guard>(cap->f), policy, call.parent);
155155

156156
/* Invoke call policy post-call hook */
157157
detail::process_attributes<Extra...>::postcall(call, result);

include/pybind11/stl.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ template <typename Type, typename Key> struct set_caster {
6060
return false;
6161
auto s = reinterpret_borrow<pybind11::set>(src);
6262
value.clear();
63-
key_conv conv;
6463
for (auto entry : s) {
64+
key_conv conv;
6565
if (!conv.load(entry, convert))
6666
return false;
67-
value.insert(cast_op<Key>(conv));
67+
value.insert(cast_op<Key &&>(std::move(conv)));
6868
}
6969
return true;
7070
}
@@ -90,14 +90,14 @@ template <typename Type, typename Key, typename Value> struct map_caster {
9090
if (!isinstance<dict>(src))
9191
return false;
9292
auto d = reinterpret_borrow<dict>(src);
93-
key_conv kconv;
94-
value_conv vconv;
9593
value.clear();
9694
for (auto it : d) {
95+
key_conv kconv;
96+
value_conv vconv;
9797
if (!kconv.load(it.first.ptr(), convert) ||
9898
!vconv.load(it.second.ptr(), convert))
9999
return false;
100-
value.emplace(cast_op<Key>(kconv), cast_op<Value>(vconv));
100+
value.emplace(cast_op<Key &&>(std::move(kconv)), cast_op<Value &&>(std::move(vconv)));
101101
}
102102
return true;
103103
}
@@ -124,13 +124,13 @@ template <typename Type, typename Value> struct list_caster {
124124
if (!isinstance<sequence>(src))
125125
return false;
126126
auto s = reinterpret_borrow<sequence>(src);
127-
value_conv conv;
128127
value.clear();
129128
reserve_maybe(s, &value);
130129
for (auto it : s) {
130+
value_conv conv;
131131
if (!conv.load(it, convert))
132132
return false;
133-
value.push_back(cast_op<Value>(conv));
133+
value.push_back(cast_op<Value &&>(std::move(conv)));
134134
}
135135
return true;
136136
}
@@ -185,12 +185,12 @@ template <typename ArrayType, typename Value, bool Resizable, size_t Size = 0> s
185185
auto l = reinterpret_borrow<list>(src);
186186
if (!require_size(l.size()))
187187
return false;
188-
value_conv conv;
189188
size_t ctr = 0;
190189
for (auto it : l) {
190+
value_conv conv;
191191
if (!conv.load(it, convert))
192192
return false;
193-
value[ctr++] = cast_op<Value>(conv);
193+
value[ctr++] = cast_op<Value &&>(std::move(conv));
194194
}
195195
return true;
196196
}
@@ -249,7 +249,7 @@ template<typename T> struct optional_caster {
249249
if (!inner_caster.load(src, convert))
250250
return false;
251251

252-
value.emplace(cast_op<typename T::value_type>(inner_caster));
252+
value.emplace(cast_op<typename T::value_type &&>(std::move(inner_caster)));
253253
return true;
254254
}
255255

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ set(PYBIND11_TEST_FILES
3333
test_chrono.cpp
3434
test_class_args.cpp
3535
test_constants_and_functions.cpp
36-
test_copy_move_policies.cpp
36+
test_copy_move.cpp
3737
test_docstring_options.cpp
3838
test_eigen.cpp
3939
test_enum.cpp

0 commit comments

Comments
 (0)