diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index ff7d7cf8c2..edfab221b8 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -943,7 +943,9 @@ template struct polymorphic_type_hook_base::value>> { static const void *get(const itype *src, const std::type_info *&type) { type = src ? &typeid(*src) : nullptr; - return dynamic_cast(src); + const auto *downcasted = dynamic_cast(src); + assert(downcasted != nullptr || type == nullptr); + return downcasted; } }; template @@ -981,7 +983,7 @@ class type_caster_base : public type_caster_generic { const auto &cast_type = typeid(itype); const std::type_info *instance_type = nullptr; const void *vsrc = polymorphic_type_hook::get(src, instance_type); - if (instance_type && !same_type(cast_type, *instance_type)) { + if (instance_type && vsrc && !same_type(cast_type, *instance_type)) { // This is a base pointer to a derived type. If the derived type is registered // with pybind11, we want to make the full derived object available. // In the typical case where itype is polymorphic, we get the correct diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 329c9fe7b6..90aa22bf91 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -527,6 +527,125 @@ TEST_SUBMODULE(class_, m) { py::class_(gt, "OtherDuplicateNested"); py::class_(gt, "YetAnotherDuplicateNested"); }); + class Creature { + public: + virtual std::string typeName() const = 0; + virtual ~Creature() = default; + }; + + class Animal : public Creature { + public: + Animal() { std::cout << "Animal created: " << this << std::endl; } + + Animal(const Animal &animal) : Creature(animal) { + std::cout << "Animal copied: from " << &animal << " to " << this << std::endl; + } + + ~Animal() { std::cout << "Animal deleted: " << this << std::endl; } + + std::string typeName() const override { return _typeName; } + + private: + std::string _typeName = "animal"; + }; + + class TerrestrialAnimal : public virtual Animal { + public: + TerrestrialAnimal() { std::cout << "TerrestrialAnimal created: " << this << std::endl; } + + TerrestrialAnimal(const TerrestrialAnimal &animal) : Animal(animal) { + std::cout << "TerrestrialAnimal copied: from " << &animal << " to " << this + << std::endl; + } + + ~TerrestrialAnimal() { std::cout << "TerrestrialAnimal deleted: " << this << std::endl; } + + std::string typeName() const override { return _typeName; } + + private: + std::string _typeName = "terrestrial"; + }; + + class AquaticAnimal : public virtual Animal { + public: + AquaticAnimal() { std::cout << "AquaticAnimal created: " << this << std::endl; } + + AquaticAnimal(const AquaticAnimal &animal) : Animal(animal) { + std::cout << "AquaticAnimal copied: from " << &animal << " to " << this << std::endl; + } + + ~AquaticAnimal() { std::cout << "AquaticAnimal deleted: " << this << std::endl; } + + std::string typeName() const override { return _typeName; } + + private: + std::string _typeName = "aquatic"; + }; + + class Frog : public TerrestrialAnimal, public AquaticAnimal { + public: + Frog() { std::cout << "Frog created: " << this << std::endl; } + + Frog(const Frog &animal) + : Animal(animal), TerrestrialAnimal(animal), AquaticAnimal(animal) { + std::cout << "Frog copied: from " << &animal << " to " << this << std::endl; + } + + ~Frog() { std::cout << "Frog deleted: " << this << std::endl; } + + std::string typeName() const override { return _typeName; } + + private: + std::string _typeName = "frog"; + }; + + class AnimalUsage { + public: + AnimalUsage() { std::cout << "AnimalUsage created: " << this << std::endl; } + + AnimalUsage(const AnimalUsage &u) { + std::cout << "AnimalUsage copied: from " << &u << " to " << this << std::endl; + } + + ~AnimalUsage() { std::cout << "AnimalUsage deleted: " << this << std::endl; } + + const Animal &getAnimal() { return frog; } + + const AquaticAnimal &getAquaticAnimal() { return frog; } + + const Frog &getFrog() { return frog; } + + private: + Frog frog; + }; + + py::class_ animal(m, "Animal"); + + animal.def(py::init<>()); + animal.def("type_name", &Animal::typeName); + + py::class_ terrestrialAnimal(m, "TerrestrialAnimal"); + + terrestrialAnimal.def(py::init<>()); + terrestrialAnimal.def("type_name", &TerrestrialAnimal::typeName); + + py::class_ aquaticAnimal(m, "AquaticAnimal"); + + aquaticAnimal.def(py::init<>()); + aquaticAnimal.def("type_name", &AquaticAnimal::typeName); + + py::class_ frog(m, "Frog"); + + frog.def(py::init<>()); + frog.def("type_name", &Frog::typeName); + + py::class_ animalUsage(m, "AnimalUsage"); + + animalUsage.def(py::init<>()); + animalUsage.def(py::init(), py::arg("u")); + animalUsage.def("get_animal", &AnimalUsage::getAnimal); + animalUsage.def("get_aquatic_animal", &AnimalUsage::getAquaticAnimal); + animalUsage.def("get_frog", &AnimalUsage::getFrog); } template diff --git a/tests/test_class.py b/tests/test_class.py index ff9196f0f2..9d16b2e488 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -469,3 +469,9 @@ class ClassScope: m.register_duplicate_nested_class_type(ClassScope) expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!' assert str(exc_info.value) == expected + + +def test_virtual_base(): + usage = m.AnimalUsage() + a = usage.get_animal() + assert a == ""