diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index bceef4fd9..b5827247f 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -350,6 +350,27 @@ class TypeInfo template static TypeInfo Create() { + // store the base class typeid if specialized + if constexpr(is_shared_ptr::value) + { + using Elem = typename T::element_type; + using Base = typename any_cast_base::type; + + if constexpr(!std::is_same_v) + { + using RootBase = root_base_t; + + static_assert(is_polymorphic_safe_v, "TypeInfo Base trait " + "specialization " + "must be " + "polymorphic"); + + using Chain = compute_base_chain; + auto base_chain = to_type_index_vector(Chain{}); + + return TypeInfo(typeid(T), GetAnyFromStringFunctor(), std::move(base_chain)); + } + } return TypeInfo{ typeid(T), GetAnyFromStringFunctor() }; } @@ -360,6 +381,14 @@ class TypeInfo : type_info_(type_info), converter_(conv), type_str_(BT::demangle(type_info)) {} + TypeInfo(std::type_index type_info, StringConverter conv, + std::vector&& base_chain) + : type_info_(type_info) + , converter_(conv) + , type_str_(BT::demangle(type_info)) + , base_chain_(std::move(base_chain)) + {} + [[nodiscard]] const std::type_index& type() const; [[nodiscard]] const std::string& typeName() const; @@ -385,10 +414,21 @@ class TypeInfo return converter_; } + [[nodiscard]] bool isConvertibleFrom(const std::type_index& other) const + { + return std::find(base_chain_.begin(), base_chain_.end(), other) != base_chain_.end(); + } + + [[nodiscard]] const std::vector& baseChain() const + { + return base_chain_; + } + private: std::type_index type_info_; StringConverter converter_; std::string type_str_; + std::vector base_chain_; }; class PortInfo : public TypeInfo @@ -402,6 +442,14 @@ class PortInfo : public TypeInfo : TypeInfo(type_info, conv), direction_(direction) {} + PortInfo(PortDirection direction, const TypeInfo& type_info) + : TypeInfo(type_info), direction_(direction) + {} + + PortInfo(PortDirection direction, TypeInfo&& type_info) + : TypeInfo(std::move(type_info)), direction_(direction) + {} + [[nodiscard]] PortDirection direction() const; void setDescription(StringView description); @@ -452,7 +500,8 @@ template } else { - out = { sname, PortInfo(direction, typeid(T), GetAnyFromStringFunctor()) }; + auto type_info = TypeInfo::Create(); + out = { sname, PortInfo(direction, std::move(type_info)) }; } if(!description.empty()) { @@ -501,7 +550,6 @@ BidirectionalPort(StringView name, StringView description = {}) namespace details { - template [[nodiscard]] inline std::pair PortWithDefault(PortDirection direction, StringView name, const DefaultT& default_value, diff --git a/include/behaviortree_cpp/blackboard.h b/include/behaviortree_cpp/blackboard.h index 1c3aa96c6..151b2fc16 100644 --- a/include/behaviortree_cpp/blackboard.h +++ b/include/behaviortree_cpp/blackboard.h @@ -13,7 +13,6 @@ namespace BT { - /// This type contains a pointer to Any, protected /// with a locked mutex as long as the object is in scope using AnyPtrLocked = LockedPtr; diff --git a/include/behaviortree_cpp/utils/safe_any.hpp b/include/behaviortree_cpp/utils/safe_any.hpp index 999e7d526..3af974c88 100644 --- a/include/behaviortree_cpp/utils/safe_any.hpp +++ b/include/behaviortree_cpp/utils/safe_any.hpp @@ -28,9 +28,149 @@ namespace BT { - static std::type_index UndefinedAnyType = typeid(nullptr); +template +struct any_cast_base +{ + using type = void; // Default: no base known, fallback to default any storage +}; + +// C++17 backport of std::type_identity +template +struct type_identity +{ + using type = T; +}; + +// Trait to check if a type has a valid cast base +template +struct has_valid_cast_base : std::false_type +{ +}; + +template +struct has_valid_cast_base::type>> +{ + static constexpr bool value = + !std::is_same::type, void>::value; +}; + +// Recursive helper (non-self-recursive, SFINAE-safe) +template +struct resolve_root_base_helper +{ + using Base = typename any_cast_base::type; + + using type = typename std::conditional::value, type_identity, + resolve_root_base_helper>::type::type; +}; + +// Public interface with guard +template +struct root_base_resolver +{ + using type = typename std::conditional::value, + resolve_root_base_helper, + type_identity>::type::type; +}; + +template +using root_base_t = typename root_base_resolver::type; + +// Trait to detect std::shared_ptr types. +template +struct is_shared_ptr : std::false_type +{ +}; + +template +struct is_shared_ptr> : std::true_type +{ +}; + +// Trait to detect if a type is complete +template +struct is_complete : std::false_type +{ +}; + +template +struct is_complete : std::true_type +{ +}; + +// Trait to detect if a trait is complete and polymorphic +template +struct is_polymorphic_safe : std::false_type +{ +}; + +// Specialization only enabled if T is complete +template +struct is_polymorphic_safe::value>> + : std::integral_constant::value> +{ +}; + +template +inline constexpr bool is_polymorphic_safe_v = is_polymorphic_safe::value; + +// Compute and store the base class inheritance chain of a type T +// from derived to root base order, e.g. SphynxCat -> Cat -> Animal +template +struct type_list +{ + template + using prepend = type_list; +}; + +template +std::vector to_type_index_vector(type_list) +{ + return { std::type_index(typeid(Ts))... }; +} + +template +struct compute_base_chain_impl +{ + using type = type_list; +}; + +// Base case: recursion ends when base is void or equal to self +template +struct compute_base_chain_impl< + T, std::enable_if_t::type, void> || + std::is_same_v::type, T>>> +{ + using type = type_list; +}; + +// Recursive case +template +struct compute_base_chain_impl< + T, std::enable_if_t::value && + !std::is_same_v::type, void> && + !std::is_same_v::type, T> && + is_polymorphic_safe_v>> +{ +private: + using Base = typename any_cast_base::type; + using BaseChain = typename compute_base_chain_impl::type; + +public: + using type = typename BaseChain::template prepend; +}; + +template +using compute_base_chain = typename compute_base_chain_impl::type; + +template +std::vector get_base_chain_type_index() +{ + return to_type_index_vector(compute_base_chain{}); +} + // Rational: since type erased numbers will always use at least 8 bytes // it is faster to cast everything to either double, uint64_t or int64_t. class Any @@ -107,6 +247,29 @@ class Any Any(const std::type_index& type) : _original_type(type) {} + // default for shared pointers + template + explicit Any(const std::shared_ptr& value) + : _original_type(typeid(std::shared_ptr)) + { + using Base = typename any_cast_base::type; + + // store as base class if specialized + if constexpr(!std::is_same_v) + { + using RootBase = root_base_t; + + static_assert(is_polymorphic_safe_v, "Any Base trait specialization must " + "be " + "polymorphic"); + _any = std::static_pointer_cast(value); + } + else + { + _any = value; + } + } + // default for other custom types template explicit Any(const T& value, EnableNonIntegral = 0) @@ -158,6 +321,9 @@ class Any // Method to access the value by pointer. // It will return nullptr, if the user try to cast it to a // wrong type or if Any was empty. + // + // WARNING: The returned pointer may alias internal cache and be invalidated by subsequent castPtr() calls. + // Do not store it long-term. Applies only to shared_ptr where Derived is polymorphic and base-registered. template [[nodiscard]] T* castPtr() { @@ -189,6 +355,61 @@ class Any "tea" "d"); + // Special case: applies only when requesting shared_ptr and Derived is polymorphic + // with a registered base via any_cast_base. + if constexpr(is_shared_ptr::value) + { + using Derived = typename T::element_type; + using Base = typename any_cast_base::type; + + if constexpr(is_polymorphic_safe_v && !std::is_same_v) + { + using RootBase = root_base_t; + + try + { + // Case 1: If Base and Derived are the same, no casting is needed + if constexpr(std::is_same_v) + { + return linb::any_cast>(&_any); + } + + // Case 2: Previously cached + if(_cached_type == typeid(T)) + { + return reinterpret_cast(&_cached_derived_ptr); + } + + // Attempt to retrieve the stored shared_ptr from the Any container + auto base_ptr = linb::any_cast>(&_any); + if(!base_ptr) + return nullptr; + + // Case 3: Stored as Derived originally + if(_original_type == typeid(std::shared_ptr)) + { + _cached_derived_ptr = std::static_pointer_cast(*base_ptr); + } + // Case 4: Fallback to dynamic cast + else + { + auto derived_ptr = std::dynamic_pointer_cast(*base_ptr); + if(!derived_ptr) + return nullptr; + _cached_derived_ptr = derived_ptr; + } + _cached_type = typeid(T); + return reinterpret_cast(&_cached_derived_ptr); + } + catch(...) + { + return nullptr; + } + + return nullptr; + } + } + return _any.empty() ? nullptr : linb::any_cast(&_any); } @@ -212,6 +433,8 @@ class Any private: linb::any _any; std::type_index _original_type; + mutable std::shared_ptr _cached_derived_ptr = nullptr; + mutable std::type_index _cached_type = typeid(void); //---------------------------- @@ -513,6 +736,46 @@ inline nonstd::expected Any::tryCast() const throw std::runtime_error("Any::cast failed because it is empty"); } + // special case: T is a shared_ptr to a registered polymorphic type. + // The stored value is a shared_ptr, but the user is requesting shared_ptr. + // Perform safe downcasting (static or dynamic) from Base to Derived if applicable. + if constexpr(is_shared_ptr::value) + { + using Derived = typename T::element_type; + using Base = typename any_cast_base::type; + + if constexpr(is_polymorphic_safe_v && !std::is_same_v) + { + using RootBase = root_base_t; + + // Attempt to retrieve the stored shared_ptr from the Any container + auto base_ptr = linb::any_cast>(_any); + if(!base_ptr) + { + throw std::runtime_error("Any::cast cannot cast to shared_ptr class"); + } + + // Case 1: If Base and Derived are the same, no casting is needed + if constexpr(std::is_same_v>) + { + return base_ptr; + } + + // Case 2: If the original stored type was shared_ptr, we can safely static_cast + if(_original_type == typeid(std::shared_ptr)) + { + return std::static_pointer_cast(base_ptr); + } + + // Case 3: Otherwise, attempt a dynamic cast from Base to Derived + auto derived_ptr = std::dynamic_pointer_cast(base_ptr); + if(!derived_ptr) + throw std::runtime_error("Any::cast Dynamic cast failed, types are not related"); + + return derived_ptr; + } + } + if(castedType() == typeid(T)) { return linb::any_cast(_any); diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index 885244ed9..06366b579 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -825,10 +825,20 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element, if(auto prev_info = blackboard->entryInfo(port_key)) { // Check consistency of types. - bool const port_type_mismatch = + bool port_type_mismatch = (prev_info->isStronglyTyped() && port_info.isStronglyTyped() && prev_info->type() != port_info.type()); + // allow mismatch if the previous type appears in the base_chain + // of the current port (i.e., valid polymorphic upcast) and the + // port direction is an INPUT. + if(port_type_mismatch && port_info.direction() == PortDirection::INPUT && + !prev_info->baseChain().empty() && !port_info.baseChain().empty() && + prev_info->isConvertibleFrom(port_info.baseChain()[0])) + { + port_type_mismatch = false; + } + // special case related to convertFromString bool const string_input = (prev_info->type() == typeid(std::string)); diff --git a/tests/gtest_any.cpp b/tests/gtest_any.cpp index 395551fdb..414b706bc 100644 --- a/tests/gtest_any.cpp +++ b/tests/gtest_any.cpp @@ -17,6 +17,7 @@ #include #include +#include "animal_hierarchy_test.h" using namespace BT; @@ -249,4 +250,35 @@ TEST(Any, Cast) Any a(v); EXPECT_EQ(a.cast>(), v); } + + /// Issue 943 + // Type casting: polymorphic class w/ registered base class + { + auto animal = std::make_shared(); + Any any_animal(animal); + EXPECT_NO_THROW(auto res = any_animal.cast()); + EXPECT_ANY_THROW(auto res = any_animal.cast()); + EXPECT_ANY_THROW(auto res = any_animal.cast()); + EXPECT_TRUE(any_animal.castPtr()); + EXPECT_FALSE(any_animal.castPtr()); + EXPECT_FALSE(any_animal.castPtr()); + + auto cat = std::make_shared(); + Any any_cat(cat); + EXPECT_NO_THROW(auto res = any_cat.cast()); + EXPECT_NO_THROW(auto res = any_cat.cast()); + EXPECT_ANY_THROW(auto res = any_cat.cast()); + EXPECT_TRUE(any_cat.castPtr()); + EXPECT_TRUE(any_cat.castPtr()); + EXPECT_FALSE(any_cat.castPtr()); + + auto sphynx = std::make_shared(); + Any any_sphynx(sphynx); + EXPECT_NO_THROW(auto res = any_sphynx.cast()); + EXPECT_NO_THROW(auto res = any_sphynx.cast()); + EXPECT_NO_THROW(auto res = any_sphynx.cast()); + EXPECT_TRUE(any_sphynx.castPtr()); + EXPECT_TRUE(any_sphynx.castPtr()); + EXPECT_TRUE(any_sphynx.castPtr()); + } } diff --git a/tests/gtest_blackboard.cpp b/tests/gtest_blackboard.cpp index 35be37dab..64fae5d9e 100644 --- a/tests/gtest_blackboard.cpp +++ b/tests/gtest_blackboard.cpp @@ -15,6 +15,7 @@ #include "behaviortree_cpp/blackboard.h" #include "../sample_nodes/dummy_nodes.h" +#include "animal_hierarchy_test.h" using namespace BT; @@ -854,3 +855,345 @@ TEST(BlackboardTest, SetBlackboard_WithPortRemapping) // Tick till the end with no crashes ASSERT_NO_THROW(tree.tickWhileRunning();); } + +class CreateAnimal : public SyncActionNode +{ +public: + CreateAnimal(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + auto animal = std::make_shared(); + + setOutput("out_animal", animal); + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::OutputPort("out_animal") }; + } +}; + +class CreateCat : public SyncActionNode +{ +public: + CreateCat(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + auto cat = std::make_shared(); + + setOutput("out_cat", cat); + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::OutputPort("out_cat") }; + } +}; + +class CreateSphynx : public SyncActionNode +{ +public: + CreateSphynx(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + auto sphynx = std::make_shared(); + + setOutput("out_sphynx", sphynx); + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::OutputPort("out_sphynx") }; + } +}; + +class CreateDog : public SyncActionNode +{ +public: + CreateDog(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + auto dog = std::make_shared(); + + setOutput("out_dog", dog); + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::OutputPort("out_dog") }; + } +}; + +class PrintAnimalName : public SyncActionNode +{ +public: + PrintAnimalName(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + Animal::Ptr animal{}; + getInput("in_animal", animal); + + std::cout << animal->name() << std::endl; + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::InputPort("in_animal") }; + } +}; + +class PrintCatName : public SyncActionNode +{ +public: + PrintCatName(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + Cat::Ptr cat{}; + getInput("in_cat", cat); + + std::cout << cat->name() << std::endl; + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::InputPort("in_cat") }; + } +}; + +class PrintDogName : public SyncActionNode +{ +public: + PrintDogName(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + Dog::Ptr dog{}; + getInput("in_dog", dog); + + std::cout << dog->name() << std::endl; + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::InputPort("in_dog") }; + } +}; + +class PrintSphynxName : public SyncActionNode +{ +public: + PrintSphynxName(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + Sphynx::Ptr sphynx{}; + getInput("in_sphynx", sphynx); + + std::cout << sphynx->name() << std::endl; + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::InputPort("in_sphynx") }; + } +}; + +class ModifyAnimalName : public SyncActionNode +{ +public: + ModifyAnimalName(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + Animal::Ptr animal{}; + getInput("inout_animal", animal); + + animal->setName("Animal"); + + setOutput("inout_animal", animal); + + std::cout << animal->name() << std::endl; + + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::BidirectionalPort("inout_animal") }; + } +}; + +TEST(BlackboardTest, Upcasting_Issue943) +{ + { + auto bb = BT::Blackboard::create(); + + auto cat_original = std::make_shared(); + bb->set("cat", cat_original); + + std::shared_ptr animal{}; + ASSERT_TRUE(bb->get("cat", animal)); + ASSERT_STREQ("Cat", animal->name().c_str()); + + std::shared_ptr cat{}; + ASSERT_TRUE(bb->get("cat", cat)); + ASSERT_STREQ("Cat", cat->name().c_str()); + + std::shared_ptr sphynx{}; + ASSERT_ANY_THROW(auto rc = bb->get("cat", sphynx)); + + ASSERT_ANY_THROW(bb->set("cat", animal)); + ASSERT_NO_THROW(bb->set("cat", cat)); + ASSERT_ANY_THROW(bb->set("cat", sphynx)); + } + + // This test verifies that polymorphic upcasting works correctly during tree creation. + // The port "cat" is produced as Cat and later consumed as both Cat and its base type Animal. + { + std::string xml_txt = R"( + + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("CreateCat"); + factory.registerNodeType("PrintCatName"); + factory.registerNodeType("PrintAnimalName"); + + auto tree = factory.createTreeFromText(xml_txt); + + NodeStatus status = tree.tickWhileRunning(); + + ASSERT_EQ(status, NodeStatus::SUCCESS); + } + + // This test ensures that an invalid polymorphic upcast is correctly detected + // during tree creation. The port "cat" is first created with Cat, then later + // expected as Dog, which is invalid. + { + std::string xml_txt = R"( + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("CreateCat"); + factory.registerNodeType("PrintDogName"); + + ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_txt)); + } + + // This test ensures that an invalid polymorphic downcast is correctly detected + // during tree creation. The port "cat" is first created with Cat, then later + // expected as Sphynx (a more derived type), which fails. + { + std::string xml_txt = R"( + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("CreateCat"); + factory.registerNodeType("PrintSphynxName"); + + ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_txt)); + } + + // This test ensures that no polymorphic upcast can be used when supplied + // to an Output port. The port "cat" is first created with Cat, then later + // expected as an Animal output port, which fails. + { + std::string xml_txt = R"( + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("CreateAnimal"); + factory.registerNodeType("CreateCat"); + + ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_txt)); + } + + // This test ensures that no polymorphic upcast can be used when supplied + // to an Bidirectional port. The port "cat" is first created with Cat, then later + // expected as an Animal bidirectional port, which fails. + { + std::string xml_txt = R"( + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("CreateCat"); + factory.registerNodeType("ModifyAnimalName"); + + ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_txt)); + } +} diff --git a/tests/gtest_ports.cpp b/tests/gtest_ports.cpp index 69c351594..686de5c33 100644 --- a/tests/gtest_ports.cpp +++ b/tests/gtest_ports.cpp @@ -3,6 +3,7 @@ #include "behaviortree_cpp/bt_factory.h" #include "behaviortree_cpp/xml_parsing.h" #include "behaviortree_cpp/json_export.h" +#include "animal_hierarchy_test.h" using namespace BT; @@ -730,3 +731,66 @@ TEST(PortTest, DefaultWronglyOverriden) // This is correct ASSERT_NO_THROW(auto tree = factory.createTreeFromText(xml_txt_correct)); } + +class Plant +{ +public: + using Ptr = std::shared_ptr; +}; + +TEST(PortTest, Upcasting_Issue943) +{ + using namespace BT; + + auto plant = PortInfo::Create(); // not registered + auto animal = PortInfo::Create(); + auto cat = PortInfo::Create(); + auto dog = PortInfo::Create(); + auto sphynx = PortInfo::Create(); + + ASSERT_EQ(0, plant.baseChain().size()); + ASSERT_EQ(1, animal.baseChain().size()); + ASSERT_EQ(2, cat.baseChain().size()); + ASSERT_EQ(2, dog.baseChain().size()); + ASSERT_EQ(3, sphynx.baseChain().size()); + + ASSERT_EQ(std::type_index(typeid(Animal)), animal.baseChain()[0]); + ASSERT_EQ(std::type_index(typeid(Cat)), cat.baseChain()[0]); + ASSERT_EQ(std::type_index(typeid(Animal)), cat.baseChain()[1]); + ASSERT_EQ(std::type_index(typeid(Dog)), dog.baseChain()[0]); + ASSERT_EQ(std::type_index(typeid(Animal)), dog.baseChain()[1]); + + ASSERT_EQ(std::type_index(typeid(Sphynx)), sphynx.baseChain()[0]); + ASSERT_EQ(std::type_index(typeid(Cat)), sphynx.baseChain()[1]); + ASSERT_EQ(std::type_index(typeid(Animal)), sphynx.baseChain()[2]); + + ASSERT_FALSE(plant.isConvertibleFrom(std::type_index(typeid(Plant)))); + ASSERT_FALSE(plant.isConvertibleFrom(std::type_index(typeid(Animal)))); + ASSERT_FALSE(plant.isConvertibleFrom(std::type_index(typeid(Cat)))); + ASSERT_FALSE(plant.isConvertibleFrom(std::type_index(typeid(Dog)))); + ASSERT_FALSE(plant.isConvertibleFrom(std::type_index(typeid(Sphynx)))); + + ASSERT_FALSE(animal.isConvertibleFrom(std::type_index(typeid(Plant)))); + ASSERT_TRUE(animal.isConvertibleFrom(std::type_index(typeid(Animal)))); + ASSERT_FALSE(animal.isConvertibleFrom(std::type_index(typeid(Cat)))); + ASSERT_FALSE(animal.isConvertibleFrom(std::type_index(typeid(Dog)))); + ASSERT_FALSE(animal.isConvertibleFrom(std::type_index(typeid(Sphynx)))); + + ASSERT_FALSE(cat.isConvertibleFrom(std::type_index(typeid(Plant)))); + ASSERT_TRUE(cat.isConvertibleFrom(std::type_index(typeid(Animal)))); + ASSERT_TRUE(cat.isConvertibleFrom(std::type_index(typeid(Cat)))); + ASSERT_FALSE(cat.isConvertibleFrom(std::type_index(typeid(Dog)))); + ASSERT_FALSE(cat.isConvertibleFrom(std::type_index(typeid(Sphynx)))); + + ASSERT_FALSE(dog.isConvertibleFrom(std::type_index(typeid(Plant)))); + ASSERT_TRUE(dog.isConvertibleFrom(std::type_index(typeid(Animal)))); + ASSERT_FALSE(dog.isConvertibleFrom(std::type_index(typeid(Cat)))); + ASSERT_TRUE(dog.isConvertibleFrom(std::type_index(typeid(Dog)))); + ASSERT_FALSE(dog.isConvertibleFrom(std::type_index(typeid(Sphynx)))); + + ASSERT_FALSE(sphynx.isConvertibleFrom(std::type_index(typeid(Plant)))); + ASSERT_TRUE(sphynx.isConvertibleFrom(std::type_index(typeid(Animal)))); + ASSERT_TRUE(sphynx.isConvertibleFrom(std::type_index(typeid(Cat)))); + ASSERT_FALSE(sphynx.isConvertibleFrom(std::type_index(typeid(Dog)))); + ASSERT_TRUE(sphynx.isConvertibleFrom(std::type_index(typeid(Sphynx)))); +} diff --git a/tests/include/animal_hierarchy_test.h b/tests/include/animal_hierarchy_test.h new file mode 100644 index 000000000..ceac7c3e2 --- /dev/null +++ b/tests/include/animal_hierarchy_test.h @@ -0,0 +1,81 @@ +#ifndef ANIMALHIERARCHY_H_ +#define ANIMALHIERARCHY_H_ + +#include + +class Animal +{ +public: + using Ptr = std::shared_ptr; + + virtual std::string name() const + { + return name_; + } + + void setName(const std::string& name) + { + name_ = name; + } + + virtual ~Animal() = default; + +private: + std::string name_ = "Unknown"; +}; + +class Cat : public Animal +{ +public: + using Ptr = std::shared_ptr; + std::string name() const override + { + return "Cat"; + } +}; + +class Sphynx : public Cat +{ +public: + using Ptr = std::shared_ptr; + std::string name() const override + { + return "Sphynx"; + } +}; + +class Dog : public Animal +{ +public: + using Ptr = std::shared_ptr; + std::string name() const override + { + return "Dog"; + } +}; + +template <> +struct BT::any_cast_base +{ + using type = Animal; +}; + +template <> +struct BT::any_cast_base +{ + using type = Animal; +}; + +template <> +struct BT::any_cast_base +{ + using type = Cat; +}; + +template <> +struct BT::any_cast_base +{ + using type = Animal; +}; + +#endif // ANIMALHIERARCHY_H_