Skip to content

Commit b4dd7c4

Browse files
committed
Differentiate between copying and moving of custom types based on the function signature
So far, using the movable_cast_op_type, always enabled move semantics, even when copy semantics would be expected from the function signature. While C++ decides about copy vs move semantics based on the type of the passed argument, Python doesn't know about lvalue and rvalue references and thus cannot decide based on the passed argument. Instead, the wrapper needs to explicitly define the desired semantics of argument passing as follows: - f(T&) passes an lvalue reference - f(T&&) passes an rvalue reference - f(T) passes an lvalue reference by default (triggering copy construction) but falls back to an rvalue reference if copying is not supported (thus triggering move construction) This commit essentially makes call_impl() pass the argument casters to cast_op() by lvalue references, thus enabling the distinction between copy and move semantics based on the argument's type.
1 parent 396cde5 commit b4dd7c4

File tree

3 files changed

+34
-18
lines changed

3 files changed

+34
-18
lines changed

include/pybind11/cast.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ PYBIND11_NAMESPACE_BEGIN(detail)
5050
template <typename type, typename SFINAE = void> class type_caster : public type_caster_base<type> { };
5151
template <typename type> using make_caster = type_caster<intrinsic_t<type>>;
5252

53-
// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
53+
// Shortcuts for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
54+
// cast_op operating on an lvalue-reference caster, enables moving only if required by the actual function argument
5455
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
5556
return caster.operator typename make_caster<T>::template cast_op_type<T>();
5657
}
58+
// cast_op operating on an rvalue-referenced caster enforces an rvalue-reference for the cast_op type as well
5759
template <typename T> typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>
5860
cast_op(make_caster<T> &&caster) {
59-
return std::move(caster).operator
60-
typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>();
61+
return caster.operator typename make_caster<T>::template cast_op_type<typename std::add_rvalue_reference<T>::type>();
6162
}
6263

6364
template <typename type> class type_caster<std::reference_wrapper<type>> {
@@ -101,7 +102,7 @@ template <typename type> class type_caster<std::reference_wrapper<type>> {
101102
} \
102103
operator type*() { return &value; } \
103104
operator type&() { return value; } \
104-
operator type&&() && { return std::move(value); } \
105+
operator type&&() { return std::move(value); } \
105106
template <typename T_> using cast_op_type = pybind11::detail::movable_cast_op_type<T_>
106107

107108

@@ -795,8 +796,8 @@ class type_caster<T, enable_if_t<is_pyobject<T>::value>> : public pyobject_caste
795796
// - type_caster<T>::operator T&() must exist
796797
// - the type must be move constructible (obviously)
797798
// At run-time:
798-
// - if the type is non-copy-constructible, the object must be the sole owner of the type (i.e. it
799-
// must have ref_count() == 1)h
799+
// - if the type is non-copy-constructible, the object must be the sole owner of the type
800+
// (i.e. it must have ref_count() == 1)
800801
// If any of the above are not satisfied, we fall back to copying.
801802
template <typename T> using move_is_plain_type = satisfies_none_of<T,
802803
std::is_void, std::is_pointer, std::is_reference, std::is_const
@@ -1163,7 +1164,7 @@ class argument_loader {
11631164

11641165
template <typename Return, typename Func, size_t... Is, typename Guard>
11651166
Return call_impl(Func &&f, index_sequence<Is...>, Guard &&) && {
1166-
return std::forward<Func>(f)(cast_op<Args>(std::move(std::get<Is>(argcasters)))...);
1167+
return std::forward<Func>(f)(cast_op<Args>(std::get<Is>(argcasters))...);
11671168
}
11681169

11691170
std::tuple<make_caster<Args>...> argcasters;

include/pybind11/detail/type_caster_base.h

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -760,29 +760,44 @@ class type_caster_generic {
760760
* Determine suitable casting operator for pointer-or-lvalue-casting type casters. The type caster
761761
* needs to provide `operator T*()` and `operator T&()` operators.
762762
*
763-
* If the type supports moving the value away via an `operator T&&() &&` method, it should use
764-
* `movable_cast_op_type` instead.
763+
* If the type supports moving, it should use `movable_cast_op_type` instead.
765764
*/
766765
template <typename T>
767766
using cast_op_type =
768767
conditional_t<std::is_pointer<remove_reference_t<T>>::value,
769768
typename std::add_pointer<intrinsic_t<T>>::type,
770769
typename std::add_lvalue_reference<intrinsic_t<T>>::type>;
771770

771+
// A by-value argument will use lvalue references by default (triggering copy construction)
772+
// except the type is move-only and thus should use move semantics.
773+
template <typename T> using cast_op_type_for_byvalue =
774+
conditional_t<std::is_copy_constructible<intrinsic_t<T>>::value,
775+
typename std::add_lvalue_reference<intrinsic_t<T>>::type,
776+
typename std::add_rvalue_reference<intrinsic_t<T>>::type >;
777+
772778
/**
773-
* Determine suitable casting operator for a type caster with a movable value. Such a type caster
774-
* needs to provide `operator T*()`, `operator T&()`, and `operator T&&() &&`. The latter will be
775-
* called in appropriate contexts where the value can be moved rather than copied.
779+
* Determine suitable casting operator for movable types.
780+
* While C++ decides about copy vs. move semantics based on the type of the passed argument,
781+
* Python doesn't know about lvalue and rvalue references and thus cannot decide based on the
782+
* passed argument. Instead, the wrapper needs to define the desired semantics of argument passing
783+
* explicitly:
784+
* - By specifying an rvalue reference (T&&), move semantics is enforced.
785+
* - By default, lvalue references (T&) are passed, triggering copy semantics if necessary.
786+
* An exception are move-only types: These are passed via move semantics.
776787
*
777-
* These operator are automatically provided when using the PYBIND11_TYPE_CASTER macro.
788+
* The corresponding caster needs to provide the following operators:
789+
* `operator T*()`, `operator T&()`, and `operator T&&()`.
790+
* These operators are automatically provided when using the PYBIND11_TYPE_CASTER macro.
778791
*/
779792
template <typename T>
780793
using movable_cast_op_type =
781794
conditional_t<std::is_pointer<typename std::remove_reference<T>::type>::value,
782795
typename std::add_pointer<intrinsic_t<T>>::type,
783-
conditional_t<std::is_rvalue_reference<T>::value,
784-
typename std::add_rvalue_reference<intrinsic_t<T>>::type,
785-
typename std::add_lvalue_reference<intrinsic_t<T>>::type>>;
796+
conditional_t<std::is_rvalue_reference<T>::value,
797+
typename std::add_rvalue_reference<intrinsic_t<T>>::type,
798+
conditional_t<std::is_lvalue_reference<T>::value,
799+
typename std::add_lvalue_reference<intrinsic_t<T>>::type,
800+
cast_op_type_for_byvalue<T> > > >;
786801

787802
// std::is_copy_constructible isn't quite enough: it lets std::vector<T> (and similar) through when
788803
// T is non-copyable, but code containing such a copy constructor fails to actually compile.
@@ -917,7 +932,7 @@ template <typename type> class type_caster_base : public type_caster_generic {
917932

918933
operator itype*() { return (type *) value; }
919934
operator itype&() { if (!value) throw reference_cast_error(); return *((itype *) value); }
920-
operator itype&&() && { if (!value) throw reference_cast_error(); return std::move(*((itype *) value)); }
935+
operator itype&&() { if (!value) throw reference_cast_error(); return std::move(*((itype *) value)); }
921936

922937
protected:
923938
using Constructor = void *(*)(const void *);

include/pybind11/eigen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
346346

347347
operator Type*() { return &value; }
348348
operator Type&() { return value; }
349-
operator Type&&() && { return std::move(value); }
349+
operator Type&&() { return std::move(value); }
350350
template <typename T> using cast_op_type = movable_cast_op_type<T>;
351351

352352
private:

0 commit comments

Comments
 (0)