Skip to content
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

Allow upcasting #944

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

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: Failure
Expected: 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: Failure
Value of: a.castPtr<std::shared_ptr<Greeter>>()
  Actual: false
Expected: 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." thrown in the test body.

- 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.
@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 downcasting std::shared_ptr instances (must be polymorphic and has their base registered) within the Any class, facilitating polymorphic type handling. By leveraging type traits, the Any class can now perform safe and efficient conversions between shared pointers of related types.

  • 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 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 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.

@captain-yoshi captain-yoshi changed the title [WIP] Allow upcasting Allow upcasting Apr 2, 2025
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.
@captain-yoshi
Copy link
Author

The castPtr function is a little bit tricky when downcasting...

I would prefer solution 2 or 3.

Solution 1

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

Pros Cons
No complexity 2 different return types depending on the type.
Cannot retrieve the shared pointer, only access to the raw pointer.

Implemented in 3021ff0

Solution 2

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

Pros Cons
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 in d11512a

Solution 3

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

Pros Cons
No complexity User has 2 different functions depending on what is stored in the _any.

Not implemented

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant