Skip to content

Commit a2c5971

Browse files
type_caster_generic: add cast_sources abstraction (#5866)
* type_caster_generic: add cast_sources abstraction * Respond to code review comments * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent fc423c9 commit a2c5971

File tree

2 files changed

+144
-100
lines changed

2 files changed

+144
-100
lines changed

include/pybind11/cast.h

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,15 +1011,12 @@ struct copyable_holder_caster<
10111011
static handle
10121012
cast(const std::shared_ptr<type> &src, return_value_policy policy, handle parent) {
10131013
const auto *ptr = src.get();
1014-
auto st = type_caster_base<type>::src_and_type(ptr);
1015-
if (st.second == nullptr) {
1016-
return handle(); // no type info: error will be set already
1017-
}
1018-
if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
1014+
typename type_caster_base<type>::cast_sources srcs{ptr};
1015+
if (srcs.creates_smart_holder()) {
10191016
return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
1020-
src, policy, parent, st);
1017+
src, policy, parent, srcs.result);
10211018
}
1022-
return type_caster_base<type>::cast_holder(ptr, &src);
1019+
return type_caster_base<type>::cast_holder(srcs, &src);
10231020
}
10241021

10251022
// This function will succeed even if the `responsible_parent` does not own the
@@ -1195,21 +1192,12 @@ struct move_only_holder_caster<
11951192
static handle
11961193
cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
11971194
auto *ptr = src.get();
1198-
auto st = type_caster_base<type>::src_and_type(ptr);
1199-
if (st.second == nullptr) {
1200-
return handle(); // no type info: error will be set already
1201-
}
1202-
if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
1195+
typename type_caster_base<type>::cast_sources srcs{ptr};
1196+
if (srcs.creates_smart_holder()) {
12031197
return smart_holder_type_caster_support::smart_holder_from_unique_ptr(
1204-
std::move(src), policy, parent, st);
1205-
}
1206-
return type_caster_generic::cast(st.first,
1207-
return_value_policy::take_ownership,
1208-
{},
1209-
st.second,
1210-
nullptr,
1211-
nullptr,
1212-
std::addressof(src));
1198+
std::move(src), policy, parent, srcs.result);
1199+
}
1200+
return type_caster_base<type>::cast_holder(srcs, &src);
12131201
}
12141202

12151203
static handle

include/pybind11/detail/type_caster_base.h

Lines changed: 135 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,74 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
545545
});
546546
}
547547

548+
// Information about how type_caster_generic::cast() can obtain its source object
549+
struct cast_sources {
550+
// A type-erased pointer and the type it points to
551+
struct raw_source {
552+
const void *cppobj;
553+
const std::type_info *cpptype;
554+
};
555+
556+
// A C++ pointer and the Python type info we will convert it to;
557+
// we expect that cppobj points to something of type tinfo->cpptype
558+
struct resolved_source {
559+
const void *cppobj;
560+
const type_info *tinfo;
561+
};
562+
563+
// Use the given pointer with its compile-time type, possibly downcast
564+
// via polymorphic_type_hook()
565+
template <typename itype>
566+
cast_sources(const itype *ptr); // NOLINT(google-explicit-constructor)
567+
568+
// Use the given pointer and type
569+
// NOLINTNEXTLINE(google-explicit-constructor)
570+
cast_sources(const raw_source &orig) : original(orig) { result = resolve(); }
571+
572+
// Use the given object and pybind11 type info. NB: if tinfo is null,
573+
// this does not provide enough information to use a foreign type or
574+
// to render a useful error message
575+
cast_sources(const void *obj, const detail::type_info *tinfo)
576+
: original{obj, tinfo ? tinfo->cpptype : nullptr}, result{obj, tinfo} {}
577+
578+
// The object passed to cast(), with its static type.
579+
// original.type must not be null if resolve() will be called.
580+
// original.obj may be null if we're converting nullptr to a Python None
581+
raw_source original;
582+
583+
// A more-derived version of `original` provided by a
584+
// polymorphic_type_hook. downcast.type may be null if this is not
585+
// a relevant concept for the current cast.
586+
raw_source downcast{};
587+
588+
// The source to use for this cast, and the corresponding pybind11
589+
// type_info. If the type_info is null, then pybind11 doesn't know
590+
// about this type.
591+
resolved_source result;
592+
593+
// Returns true if the cast will use a pybind11 type that uses
594+
// a smart holder.
595+
bool creates_smart_holder() const {
596+
return result.tinfo != nullptr
597+
&& result.tinfo->holder_enum_v == detail::holder_enum_t::smart_holder;
598+
}
599+
600+
private:
601+
resolved_source resolve() {
602+
if (downcast.cpptype) {
603+
if (same_type(*original.cpptype, *downcast.cpptype)) {
604+
downcast.cpptype = nullptr;
605+
} else if (const auto *tpi = get_type_info(*downcast.cpptype)) {
606+
return {downcast.cppobj, tpi};
607+
}
608+
}
609+
if (const auto *tpi = get_type_info(*original.cpptype)) {
610+
return {original.cppobj, tpi};
611+
}
612+
return {nullptr, nullptr};
613+
}
614+
};
615+
548616
// Forward declarations
549617
void keep_alive_impl(handle nurse, handle patient);
550618
inline PyObject *make_new_instance(PyTypeObject *type);
@@ -607,18 +675,18 @@ template <typename T, typename D>
607675
handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
608676
return_value_policy policy,
609677
handle parent,
610-
const std::pair<const void *, const type_info *> &st) {
678+
const cast_sources::resolved_source &cs) {
611679
if (policy == return_value_policy::copy) {
612680
throw cast_error("return_value_policy::copy is invalid for unique_ptr.");
613681
}
614682
if (!src) {
615683
return none().release();
616684
}
617-
// st.first is the subobject pointer appropriate for tinfo (may differ from src.get()
685+
// cs.cppobj is the subobject pointer appropriate for tinfo (may differ from src.get()
618686
// under MI/VI). Use this for Python identity/registration, but keep ownership on T*.
619-
void *src_raw_void_ptr = const_cast<void *>(st.first);
620-
assert(st.second != nullptr);
621-
const detail::type_info *tinfo = st.second;
687+
void *src_raw_void_ptr = const_cast<void *>(cs.cppobj);
688+
assert(cs.tinfo != nullptr);
689+
const detail::type_info *tinfo = cs.tinfo;
622690
if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
623691
auto *self_life_support = tinfo->get_trampoline_self_life_support(src.get());
624692
if (self_life_support != nullptr) {
@@ -665,20 +733,20 @@ template <typename T, typename D>
665733
handle smart_holder_from_unique_ptr(std::unique_ptr<T const, D> &&src,
666734
return_value_policy policy,
667735
handle parent,
668-
const std::pair<const void *, const type_info *> &st) {
736+
const cast_sources::resolved_source &cs) {
669737
return smart_holder_from_unique_ptr(
670738
std::unique_ptr<T, D>(const_cast<T *>(src.release()),
671739
std::move(src.get_deleter())), // Const2Mutbl
672740
policy,
673741
parent,
674-
st);
742+
cs);
675743
}
676744

677745
template <typename T>
678746
handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src,
679747
return_value_policy policy,
680748
handle parent,
681-
const std::pair<const void *, const type_info *> &st) {
749+
const cast_sources::resolved_source &cs) {
682750
switch (policy) {
683751
case return_value_policy::automatic:
684752
case return_value_policy::automatic_reference:
@@ -697,11 +765,11 @@ handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src,
697765
return none().release();
698766
}
699767

700-
// st.first is the subobject pointer appropriate for tinfo (may differ from src.get()
768+
// cs.cppobj is the subobject pointer appropriate for tinfo (may differ from src.get()
701769
// under MI/VI). Use this for Python identity/registration, but keep ownership on T*.
702-
void *src_raw_void_ptr = const_cast<void *>(st.first);
703-
assert(st.second != nullptr);
704-
const detail::type_info *tinfo = st.second;
770+
void *src_raw_void_ptr = const_cast<void *>(cs.cppobj);
771+
assert(cs.tinfo != nullptr);
772+
const detail::type_info *tinfo = cs.tinfo;
705773
if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
706774
// PYBIND11:REMINDER: MISSING: Enforcement of consistency with existing smart_holder.
707775
// PYBIND11:REMINDER: MISSING: keep_alive.
@@ -728,11 +796,11 @@ template <typename T>
728796
handle smart_holder_from_shared_ptr(const std::shared_ptr<T const> &src,
729797
return_value_policy policy,
730798
handle parent,
731-
const std::pair<const void *, const type_info *> &st) {
799+
const cast_sources::resolved_source &cs) {
732800
return smart_holder_from_shared_ptr(std::const_pointer_cast<T>(src), // Const2Mutbl
733801
policy,
734802
parent,
735-
st);
803+
cs);
736804
}
737805

738806
struct shared_ptr_parent_life_support {
@@ -925,21 +993,39 @@ class type_caster_generic {
925993

926994
bool load(handle src, bool convert) { return load_impl<type_caster_generic>(src, convert); }
927995

928-
PYBIND11_NOINLINE static handle cast(const void *_src,
996+
static handle cast(const void *src,
997+
return_value_policy policy,
998+
handle parent,
999+
const detail::type_info *tinfo,
1000+
void *(*copy_constructor)(const void *),
1001+
void *(*move_constructor)(const void *),
1002+
const void *existing_holder = nullptr) {
1003+
cast_sources srcs{src, tinfo};
1004+
return cast(srcs, policy, parent, copy_constructor, move_constructor, existing_holder);
1005+
}
1006+
1007+
PYBIND11_NOINLINE static handle cast(const cast_sources &srcs,
9291008
return_value_policy policy,
9301009
handle parent,
931-
const detail::type_info *tinfo,
9321010
void *(*copy_constructor)(const void *),
9331011
void *(*move_constructor)(const void *),
9341012
const void *existing_holder = nullptr) {
935-
if (!tinfo) { // no type info: error will be set already
1013+
if (!srcs.result.tinfo) {
1014+
// No pybind11 type info. Raise an exception.
1015+
std::string tname = srcs.downcast.cpptype ? srcs.downcast.cpptype->name()
1016+
: srcs.original.cpptype ? srcs.original.cpptype->name()
1017+
: "<unspecified>";
1018+
detail::clean_type_id(tname);
1019+
std::string msg = "Unregistered type : " + tname;
1020+
set_error(PyExc_TypeError, msg.c_str());
9361021
return handle();
9371022
}
9381023

939-
void *src = const_cast<void *>(_src);
1024+
void *src = const_cast<void *>(srcs.result.cppobj);
9401025
if (src == nullptr) {
9411026
return none().release();
9421027
}
1028+
const type_info *tinfo = srcs.result.tinfo;
9431029

9441030
if (handle registered_inst = find_registered_python_instance(src, tinfo)) {
9451031
return registered_inst;
@@ -1212,25 +1298,6 @@ class type_caster_generic {
12121298
return false;
12131299
}
12141300

1215-
// Called to do type lookup and wrap the pointer and type in a pair when a dynamic_cast
1216-
// isn't needed or can't be used. If the type is unknown, sets the error and returns a pair
1217-
// with .second = nullptr. (p.first = nullptr is not an error: it becomes None).
1218-
PYBIND11_NOINLINE static std::pair<const void *, const type_info *>
1219-
src_and_type(const void *src,
1220-
const std::type_info &cast_type,
1221-
const std::type_info *rtti_type = nullptr) {
1222-
if (auto *tpi = get_type_info(cast_type)) {
1223-
return {src, const_cast<const type_info *>(tpi)};
1224-
}
1225-
1226-
// Not found, set error:
1227-
std::string tname = rtti_type ? rtti_type->name() : cast_type.name();
1228-
detail::clean_type_id(tname);
1229-
std::string msg = "Unregistered type : " + tname;
1230-
set_error(PyExc_TypeError, msg.c_str());
1231-
return {nullptr, nullptr};
1232-
}
1233-
12341301
const type_info *typeinfo = nullptr;
12351302
const std::type_info *cpptype = nullptr;
12361303
void *value = nullptr;
@@ -1525,6 +1592,20 @@ struct polymorphic_type_hook : public polymorphic_type_hook_base<itype> {};
15251592

15261593
PYBIND11_NAMESPACE_BEGIN(detail)
15271594

1595+
template <typename itype>
1596+
cast_sources::cast_sources(const itype *ptr) : original{ptr, &typeid(itype)} {
1597+
// If this is a base pointer to a derived type, and the derived type is
1598+
// registered with pybind11, we want to make the full derived object
1599+
// available. In the typical case where itype is polymorphic, we get the
1600+
// correct derived pointer (which may be != base pointer) by a dynamic_cast
1601+
// to most derived type. If itype is not polymorphic, a user-provided
1602+
// specialization of polymorphic_type_hook can do the same thing.
1603+
// If there is no downcast to perform, then the default hook will leave
1604+
// derived.type set to nullptr, which causes us to ignore derived.obj.
1605+
downcast.cppobj = polymorphic_type_hook<itype>::get(ptr, downcast.cpptype);
1606+
result = resolve();
1607+
}
1608+
15281609
/// Generic type caster for objects stored on the heap
15291610
template <typename type>
15301611
class type_caster_base : public type_caster_generic {
@@ -1536,6 +1617,14 @@ class type_caster_base : public type_caster_generic {
15361617
type_caster_base() : type_caster_base(typeid(type)) {}
15371618
explicit type_caster_base(const std::type_info &info) : type_caster_generic(info) {}
15381619

1620+
// Wrap the generic cast_sources to be only constructible from the type
1621+
// that's correct in this context, so you can't use type_caster_base<A>
1622+
// to convert an unrelated B* to Python.
1623+
struct cast_sources : detail::cast_sources {
1624+
// NOLINTNEXTLINE(google-explicit-constructor)
1625+
cast_sources(const itype *ptr) : detail::cast_sources(ptr) {}
1626+
};
1627+
15391628
static handle cast(const itype &src, return_value_policy policy, handle parent) {
15401629
if (policy == return_value_policy::automatic
15411630
|| policy == return_value_policy::automatic_reference) {
@@ -1548,50 +1637,17 @@ class type_caster_base : public type_caster_generic {
15481637
return cast(std::addressof(src), return_value_policy::move, parent);
15491638
}
15501639

1551-
// Returns a (pointer, type_info) pair taking care of necessary type lookup for a
1552-
// polymorphic type (using RTTI by default, but can be overridden by specializing
1553-
// polymorphic_type_hook). If the instance isn't derived, returns the base version.
1554-
static std::pair<const void *, const type_info *> src_and_type(const itype *src) {
1555-
const auto &cast_type = typeid(itype);
1556-
const std::type_info *instance_type = nullptr;
1557-
const void *vsrc = polymorphic_type_hook<itype>::get(src, instance_type);
1558-
if (instance_type && !same_type(cast_type, *instance_type)) {
1559-
// This is a base pointer to a derived type. If the derived type is registered
1560-
// with pybind11, we want to make the full derived object available.
1561-
// In the typical case where itype is polymorphic, we get the correct
1562-
// derived pointer (which may be != base pointer) by a dynamic_cast to
1563-
// most derived type. If itype is not polymorphic, we won't get here
1564-
// except via a user-provided specialization of polymorphic_type_hook,
1565-
// and the user has promised that no this-pointer adjustment is
1566-
// required in that case, so it's OK to use static_cast.
1567-
if (const auto *tpi = get_type_info(*instance_type)) {
1568-
return {vsrc, tpi};
1569-
}
1570-
}
1571-
// Otherwise we have either a nullptr, an `itype` pointer, or an unknown derived pointer,
1572-
// so don't do a cast
1573-
return type_caster_generic::src_and_type(src, cast_type, instance_type);
1574-
}
1575-
1576-
static handle cast(const itype *src, return_value_policy policy, handle parent) {
1577-
auto st = src_and_type(src);
1578-
return type_caster_generic::cast(st.first,
1640+
static handle cast(const cast_sources &srcs, return_value_policy policy, handle parent) {
1641+
return type_caster_generic::cast(srcs,
15791642
policy,
15801643
parent,
1581-
st.second,
1582-
make_copy_constructor(src),
1583-
make_move_constructor(src));
1584-
}
1585-
1586-
static handle cast_holder(const itype *src, const void *holder) {
1587-
auto st = src_and_type(src);
1588-
return type_caster_generic::cast(st.first,
1589-
return_value_policy::take_ownership,
1590-
{},
1591-
st.second,
1592-
nullptr,
1593-
nullptr,
1594-
holder);
1644+
make_copy_constructor((const itype *) nullptr),
1645+
make_move_constructor((const itype *) nullptr));
1646+
}
1647+
1648+
static handle cast_holder(const cast_sources &srcs, const void *holder) {
1649+
auto policy = return_value_policy::take_ownership;
1650+
return type_caster_generic::cast(srcs, policy, {}, nullptr, nullptr, holder);
15951651
}
15961652

15971653
template <typename T>

0 commit comments

Comments
 (0)