Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
captain-yoshi wants to merge20 commits intoBehaviorTree:master
base:master
Choose a base branch
Loading
fromcaptain-yoshi:allow-upcasting

Conversation

@captain-yoshi
Copy link

Unit tests with desired behavior from#943.

Any Tests Failures

/home/captain-yoshi/ws/ros/mimik_ws/src/BehaviorTree.CPP/tests/gtest_any.cpp:271: FailureExpected: auto res =a.cast<std::shared_ptr<Greeter>>() doesn't throw an exception.  Actual: it throws./home/captain-yoshi/ws/ros/mimik_ws/src/BehaviorTree.CPP/tests/gtest_any.cpp:273: FailureValue of: a.castPtr<std::shared_ptr<Greeter>>()  Actual: falseExpected: true

Blackboard Test Failure

C++ exception with description"The creation of the tree failed because the port [hello_greeter] was initially created with type [std::shared_ptr<HelloGreeter>] and, later type [std::shared_ptr<Greeter>] was used somewhere else." thrownin thetest body.

@captain-yoshicaptain-yoshiforce-pushed theallow-upcasting branch 3 times, most recently from8feb137 to38f9b42CompareApril 2, 2025 18:23
@captain-yoshi
Copy link
Author

There maybe a better way to allow type casting of a polymorphic class.

​This pull request introduces support for upcasting and downcastingstd::shared_ptr instances (must be polymorphic and has their base registered) within theAny class, facilitating polymorphic type handling. By leveraging type traits, theAny class can now perform safe and efficient conversions between shared pointers of related types.

  • Introducedany_cast_base<T> trait to register base types for polymorphic casting.
  • Addedis_shared_ptr andIsPolymorphicSharedPtr traits to detect eligible types.
  • ModifiedAny 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 can be stored and retrieved as shared_ptr when registered.

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 casttemplate<>structBT::any_cast_base<Greeter>{using type = Greeter;};// Register cast base type for HelloGreetertemplate<>structBT::any_cast_base<HelloGreeter>{using type = Greeter;};

Optionally we could add a macro to simplify this process (not in this PR):

#defineBT_REGISTER_BASE_TYPE(Derived, Base)    \template<>                                   \structBT::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.

@captain-yoshicaptain-yoshi changed the title[WIP] Allow upcastingAllow upcastingApr 2, 2025
@captain-yoshi
Copy link
Author

ThecastPtr function is a little bit tricky when downcasting...

I would prefer solution 2 or 3.

Solution 1

Returns aT::element_type* castPtr() when T is a shared pointer with a polymorphic class and a registered base. For everything else T* is returned.

ProsCons
No complexity2 different return types depending on the type.
Cannot retrieve the shared pointer, only access to the raw pointer.

Implemented in3021ff0

Solution 2

Clean API:T* castPtr() for all types.

ProsCons
Returns T*Need to store a std_shared<void> member to return a pointer to a Derived class.
Lifetime of pointer not garanteed*, another call to castPtr may change this cached value.

*If getLockedPortContent is used, there is no problem.

Implemented ind11512a

Solution 3

Seperate castPtr function when T is a shared pointer with a polymorphic class and a registered base, e.g.castShared.

ProsCons
No complexityUser has 2 different functions depending on what is stored in the _any.

Not implemented

@facontidavide
Copy link
Collaborator

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, givenCat <- Animal andDog <- Animal, I would be allowed to connect a Cat to a Dog...

Copy link

CopilotAI left a 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
ifconstexpr(std::is_same_v<Base, Derived>)
{
returnreinterpret_cast<T*>(base_ptr);

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.

Suggested change
returnreinterpret_cast<T*>(base_ptr);
returnstatic_cast<T*>(base_ptr);

Copilot uses AI. Check for mistakes.
- 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 errorswhen used with forward-declared types.
Introduced _cached_derived_ptr to temporarily store downcastedshared_ptr results which are polymorphic and base-registered.
@captain-yoshi
Copy link
Author

For instance, given Cat <- Animal and Dog <- Animal, I would be allowed to connect a Cat to a Dog...

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.
@captain-yoshicaptain-yoshiforce-pushed theallow-upcasting branch 2 times, most recently frome14e74f tofb30816CompareApril 18, 2025 23:11
…tionSimplifies 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 typeis included in the base_chain of the current port during blackboard typeconsistency checks.
@captain-yoshi

This comment was marked as resolved.

@captain-yoshi
Copy link
Author

I have now replaced the Greeter hierarchy with the Animal one (easier too understand).

I added some new tests.Cat <- Animal andDog <- 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_portname="collision_object"type="std::shared_ptr&lt;Sphinx&lt;std::allocator&lt;void&gt;&gt;&gt;"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...

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.
@facontidavide
Copy link
Collaborator

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.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

Copilot code reviewCopilotCopilot left review comments

Assignees

@facontidavidefacontidavide

Labels

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@captain-yoshi@facontidavide

[8]ページ先頭

©2009-2025 Movatter.jp