Skip to content

Commit f7f73cd

Browse files
committed
Make sure obj.cast<T>() works as expected when T is a Python type
`obj.cast<T>()` should be the same as `T(obj)`, i.e. it should convert the given object to a different Python type. However, `obj.cast<T>()` usually calls `type_caster::load()` which only checks the type without doing any actual conversion. That causes a very unexpected `cast_error`. This commit makes it so that `obj.cast<T>()` and `T(obj)` are the same when T is a Python type.
1 parent 1a37e8c commit f7f73cd

File tree

3 files changed

+26
-5
lines changed

3 files changed

+26
-5
lines changed

include/pybind11/cast.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,16 +1033,20 @@ template <typename T> make_caster<T> load_type(const handle &handle) {
10331033

10341034
NAMESPACE_END(detail)
10351035

1036-
template <typename T> T cast(const handle &handle) {
1036+
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
1037+
T cast(const handle &handle) {
10371038
static_assert(!detail::cast_is_temporary_value_reference<T>::value,
10381039
"Unable to cast type to reference: value is local to type caster");
10391040
using type_caster = detail::make_caster<T>;
10401041
return detail::load_type<T>(handle).operator typename type_caster::template cast_op_type<T>();
10411042
}
10421043

1043-
template <typename T> object cast(const T &value,
1044-
return_value_policy policy = return_value_policy::automatic_reference,
1045-
handle parent = handle()) {
1044+
template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
1045+
T cast(const handle &handle) { return {handle, true}; }
1046+
1047+
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
1048+
object cast(const T &value, return_value_policy policy = return_value_policy::automatic_reference,
1049+
handle parent = handle()) {
10461050
if (policy == return_value_policy::automatic)
10471051
policy = std::is_pointer<T>::value ? return_value_policy::take_ownership : return_value_policy::copy;
10481052
else if (policy == return_value_policy::automatic_reference)

tests/test_python_types.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,19 @@ test_initializer python_types([](py::module &m) {
316316
"memoryview"_a=py::memoryview(d["memoryview"])
317317
);
318318
});
319+
320+
m.def("test_cast_functions", [](py::dict d) {
321+
// When converting between Python types, obj.cast<T>() should be the same as T(obj)
322+
return py::dict(
323+
"str"_a=d["str"].cast<py::str>(),
324+
"bool"_a=d["bool"].cast<py::bool_>(),
325+
"int"_a=d["int"].cast<py::int_>(),
326+
"float"_a=d["float"].cast<py::float_>(),
327+
"tuple"_a=d["tuple"].cast<py::tuple>(),
328+
"list"_a=d["list"].cast<py::list>(),
329+
"dict"_a=d["dict"].cast<py::dict>(),
330+
"set"_a=d["set"].cast<py::set>(),
331+
"memoryview"_a=d["memoryview"].cast<py::memoryview>()
332+
);
333+
});
319334
});

tests/test_python_types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,8 @@ def func(self, x, *args):
299299

300300
def test_constructors():
301301
"""C++ default and converting constructors are equivalent to type calls in Python"""
302-
from pybind11_tests import test_default_constructors, test_converting_constructors
302+
from pybind11_tests import (test_default_constructors, test_converting_constructors,
303+
test_cast_functions)
303304

304305
types = [str, bool, int, float, tuple, list, dict, set]
305306
expected = {t.__name__: t() for t in types}
@@ -319,3 +320,4 @@ def test_constructors():
319320
inputs = {k.__name__: v for k, v in data.items()}
320321
expected = {k.__name__: k(v) for k, v in data.items()}
321322
assert test_converting_constructors(inputs) == expected
323+
assert test_cast_functions(inputs) == expected

0 commit comments

Comments
 (0)