-
Notifications
You must be signed in to change notification settings - Fork 714
Allow upcasting #944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Allow upcasting #944
Conversation
8feb137
to
38f9b42
Compare
There maybe a better way to allow type casting of a polymorphic class. This pull request introduces support for upcasting and downcasting
So users who wants this behavior needs to register their classes to it's base class. I don't think we can get away without this process... // Register cast base type for self to allow direct cast, otherwise defaults to a dynamic cast
template <>
struct BT::any_cast_base<Greeter>
{
using type = Greeter;
};
// Register cast base type for HelloGreeter
template <>
struct BT::any_cast_base<HelloGreeter>
{
using type = Greeter;
}; Optionally we could add a macro to simplify this process (not in this PR): #define BT_REGISTER_BASE_TYPE(Derived, Base) \
template <> \
struct BT::any_cast_base<Derived> \
{ \
using type = Base; \
};
BT_REGISTER_BASE_TYPE(Greeter, Greeter)
BT_REGISTER_BASE_TYPE(HelloGreeter, Greeter) It's a little bit more permissive, then what I intended initially. You can now downcast to something that is invalid (must still be a valid class of the stored base class), e.g. the blackboard will not see a mismatch, but it will fail when trying to retrieve it in a BT::Node. Regarding the previous point, should we try to downcast early, in the blackboard to stop the tree from running early if it is invalid ? Instead of running the tree and let it fail down the line ? The Sonarcube check fails, but it does not seem related to this PR. |
The I would prefer solution 2 or 3. Solution 1Returns a
Implemented in 3021ff0 Solution 2Clean API:
Implemented in d11512a Solution 3Seperate castPtr function when T is a shared pointer with a polymorphic class and a registered base, e.g.
Not implemented |
There is a conflict that need to be resolved. Also, correct me if i am wrong, but isn't this change allowing two ports with derived classes to connect? For instance, given Cat <- Animal and Dog <- Animal, I would be allowed to connect a Cat to a Dog... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
// Case 1: If Base and Derived are the same, no casting is needed | ||
if constexpr(std::is_same_v<Base, Derived>) | ||
{ | ||
return reinterpret_cast<T*>(base_ptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] When Base and Derived are identical, consider using static_cast instead of reinterpret_cast to improve type safety and clarify conversion intent.
return reinterpret_cast<T*>(base_ptr); | |
return static_cast<T*>(base_ptr); |
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.
- Introduced `any_cast_base<T>` trait to register base types for polymorphic casting. - Added `is_shared_ptr` and `IsPolymorphicSharedPtr` traits to detect eligible types. - Modified `Any` to store shared_ptr as its registered base class if available. - Enhanced castPtr() and tryCast() to support safe downcasting from stored base class to derived class using static or dynamic pointer casting. - Ensures shared_ptr<Derived> can be stored and retrieved as shared_ptr<Base> when registered.
Added is_polymorphic_safe to defer evaluation of std::is_polymorphic<T> until T is known to be a complete type. This prevents compilation errors when used with forward-declared types.
Introduced _cached_derived_ptr to temporarily store downcasted shared_ptr results which are polymorphic and base-registered.
At the moment yes. If we want to make it fail when creating a Tree node from xml, we would need to store the type id of the base class and the original class, because at this stage, I think the any values are still empty. But trying to have a SphinxCat into a registered Cat port would also fail because only the the SphinxCat and Animal typeid would be stored when registering SphinxCat. We could fix this by storing a vector (or limit with a max depth) of typeid's from current class to root base class to enable this case. I would also prefer to make it fail early when trying to connect incompatible types. What wold be the way to go ? Only allow a depth of 2 base classes, allow a max range of depth or allow any depth ? Or something else entirely... |
- Introduced root_base_t<T> to recursively resolve the ultimate base type using the any_cast_base trait. - Applied root_base_t in TypeInfo::Create, Any constructor, castPtr, and tryCast to normalize stored/casted types across polymorphic hierarchies. - Ensured static_assert enforces polymorphic base safety on resolved RootBase.
- Reflects the intended design that any_cast_base does not need to map directly to the root base, but can resolve recursively via intermediate types. - Enables fine-grained control over polymorphic casting resolution.
- Introduced type_list<Ts...> to represent type chains at compile time. - Implemented compute_base_chain_impl<T> to recursively collect base types based on any_cast_base<T> specializations. - Added get_base_chain_type_index<T>() to convert the chain into std::vector<std::type_index> for runtime use. - Supports safe termination when base is void or self-referential.
- Modified TypeInfo::Create<T>() to compute and store the full base type chain using compute_base_chain and to_type_index_vector. - Added a new constructor to TypeInfo to accept a base_chain vector. - Introduced baseChain() accessor and isConvertibleFrom() helper. - Enables runtime introspection and safe type conversions across the base chain.
e14e74f
to
fb30816
Compare
…tion Simplifies PortInfo creation by directly accepting TypeInfo objects, allowing reuse of TypeInfo::Create<T>() without redundant field extraction.
Adds support for polymorphic upcasting by checking if the previous type is included in the base_chain of the current port during blackboard type consistency checks.
fb30816
to
5090086
Compare
This comment was marked as resolved.
This comment was marked as resolved.
I have now replaced the Greeter hierarchy with the Animal one (easier too understand). I added some new tests. Cat <- Animal and Dog <- Animal now throws when constructing the TreeNode. I think the last thing missing is the serialization of the TreeNodeModel in XML ? I think we would need to store the chain base, e.g. : <output_port name="collision_object"
type="std::shared_ptr<Sphinx<std::allocator<void> > >"
base_chain="Sphinx;Cat;Animal" /> Do we need something for deserializing the base_chain ? Is this even needed, I got the impression that we always need to register the node type in the BehaviorTreeFactory... |
Upcasting is not allowed for OUTPUT/INOUT port directions.
3e750bd
to
1ce5185
Compare
Improves Any::castPtr() by caching results of std::dynamic_pointer_cast to avoid repeated casts. Adds _cached_type to track the last casted type and reuse the cached pointer. Also simplifies the case where the requested type matches the root base.
thanks for working on this. the proposed solution seems to work but it adds some (necessary) complexity and I need to take my time (something that right now I don't have in abundance, to think about alternatives... if they exist. Please be patient, i will look at this in the next couple of weeks. |
Unit tests with desired behavior from #943.
Any Tests Failures
Blackboard Test Failure