Skip to content

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

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c105c75
Enable polymorphic shared_ptr downcasting in Any using type traits
captain-yoshi Apr 2, 2025
14815b5
Enable blackboard matching for types sharing a common base class
captain-yoshi Apr 2, 2025
6183340
Add test fixture for Greeter type hierarchy and type trait registration
captain-yoshi Apr 2, 2025
d644f67
Add unit test for Any/Blackboard type casting
captain-yoshi Apr 2, 2025
f403fb6
Avoid std::is_polymorphic instantiation on incomplete types
captain-yoshi Apr 3, 2025
5829a40
Refactor castPtr() to unify shared_ptr casting logic
captain-yoshi Apr 4, 2025
4a2e42c
Support recursive root base resolution for polymorphic shared_ptr types
captain-yoshi Apr 17, 2025
6f0c367
Adjust any_cast_base<FancyHelloGreeter> to map to HelloGreeter
captain-yoshi Apr 17, 2025
d313bec
Add compile-time base class chain introspection using any_cast_base
captain-yoshi Apr 18, 2025
f90f822
Extend TypeInfo to store and expose base class chain
captain-yoshi Apr 18, 2025
423e970
Add unit test for upcasting base type chain in PortInfo
captain-yoshi Apr 18, 2025
8ac0c1c
Use original typeid for shared_ptr types in TypeInfo::Create()
captain-yoshi Apr 18, 2025
12c1377
Add PortInfo constructors that accept TypeInfo for cleaner initializa…
captain-yoshi Apr 18, 2025
42b1d80
Allow port type mismatch if convertible via base class chain
captain-yoshi Apr 18, 2025
5090086
Improve test coverage for polymorphic port casting
captain-yoshi Apr 18, 2025
eda2683
Make test platform-independent by relaxing exact exception message check
captain-yoshi Apr 18, 2025
cad9a05
Refactor upcasting tests to use clearer Animal hierarchy example
captain-yoshi Apr 19, 2025
5f94fea
Restrict polymorphic upcast to INPUT ports only during type resolution
captain-yoshi Apr 19, 2025
1ce5185
Revert to direct type_index comparison in Blackboard::set()
captain-yoshi Apr 19, 2025
67276dd
Add caching mechanism for derived castPtr() in Any
captain-yoshi Apr 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor castPtr() to unify shared_ptr casting logic
Introduced _cached_derived_ptr to temporarily store downcasted
shared_ptr results which are polymorphic and base-registered.
  • Loading branch information
captain-yoshi committed Apr 16, 2025
commit 5829a40a266d6fda7046096081faed991db1125f
116 changes: 45 additions & 71 deletions include/behaviortree_cpp/utils/safe_any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,32 +101,6 @@ class Any
typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_enum<T>::value &&
!std::is_same<T, std::string>::value>::type*;

// Helper: IsPolymorphicSharedPtr<T> is true if T is a shared pointer,
// its element_type exists, is polymorphic, and any_cast_base for that element
// is specialized (i.e. not void).
template <typename T, typename = void>
struct IsPolymorphicSharedPtr : std::false_type
{
};

template <typename T>
struct IsPolymorphicSharedPtr<
T,
std::enable_if_t<
is_shared_ptr<T>::value && is_polymorphic_safe_v<typename T::element_type> &&
!std::is_same_v<typename any_cast_base<typename T::element_type>::type, void>>>
: std::true_type
{
};

template <typename T>
using EnablePolymorphicSharedPtr =
std::enable_if_t<IsPolymorphicSharedPtr<T>::value, int*>;

template <typename T>
using EnableNonPolymorphicSharedPtr =
std::enable_if_t<!IsPolymorphicSharedPtr<T>::value, int*>;

template <typename T>
nonstd::expected<T, std::string> stringToNumber() const;

Expand Down Expand Up @@ -247,7 +221,10 @@ 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.
template <typename T, typename = EnableNonPolymorphicSharedPtr<T>>
//
// 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<Derived> where Derived is polymorphic and base-registered.
template <typename T>
[[nodiscard]] T* castPtr()
{
static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
Expand Down Expand Up @@ -278,57 +255,53 @@ class Any
"tea"
"d");

return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
}

// Specialized version of castPtr() for shared_ptr<T> where T is a polymorphic type
// with a registered base class via any_cast_base.
//
// Returns a raw pointer to T::element_type (i.e., Derived*), or nullptr on failure.
//
// Note: This function intentionally does not return a std::shared_ptr<T>* because doing so
// would expose the internal ownership mechanism, which:
// - Breaks encapsulation and may lead to accidental misuse (e.g., double-deletion, ref count tampering)
// - Offers no real benefit, as the purpose of this function is to provide access
// to the managed object, not the smart pointer itself.
//
// By returning a raw pointer to the object, we preserve ownership semantics and safely
// allow read-only access without affecting the reference count.
template <typename T, typename = EnablePolymorphicSharedPtr<T>>
[[nodiscard]] typename T::element_type* castPtr()
{
using Derived = typename T::element_type;
using Base = typename any_cast_base<Derived>::type;

try
// Special case: applies only when requesting shared_ptr<Derived> and Derived is polymorphic
// with a registered base via any_cast_base.
if constexpr(is_shared_ptr<T>::value)
{
// Attempt to retrieve the stored shared_ptr<Base> from the Any container
auto base_ptr = linb::any_cast<std::shared_ptr<Base>>(&_any);
if(!base_ptr)
return nullptr;
using Derived = typename T::element_type;
using Base = typename any_cast_base<Derived>::type;

// Case 1: If Base and Derived are the same, no casting is needed
if constexpr(std::is_same_v<Base, Derived>)
if constexpr(is_polymorphic_safe_v<Derived> && !std::is_same_v<Base, void>)
{
return base_ptr ? base_ptr->get() : nullptr;
}
try
{
// Attempt to retrieve the stored shared_ptr<Base> from the Any container
auto base_ptr = linb::any_cast<std::shared_ptr<Base>>(&_any);
if(!base_ptr)
return nullptr;

// 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);
Copy link
Preview

Copilot AI Apr 16, 2025

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
return reinterpret_cast<T*>(base_ptr);
return static_cast<T*>(base_ptr);

Copilot uses AI. Check for mistakes.

}

// Case 2: Originally stored as shared_ptr<Derived>
if(_original_type == typeid(std::shared_ptr<Derived>))
{
_cached_derived_ptr = std::static_pointer_cast<Derived>(*base_ptr);
return reinterpret_cast<T*>(&_cached_derived_ptr);
}

// Case 3: Fallback to dynamic cast
auto derived_ptr = std::dynamic_pointer_cast<Derived>(*base_ptr);
if(derived_ptr)
{
_cached_derived_ptr = derived_ptr;
return reinterpret_cast<T*>(&_cached_derived_ptr);
}
}
catch(...)
{
return nullptr;
}

// Case 2: If the original stored type was shared_ptr<Derived>, we can safely static_cast
if(_original_type == typeid(std::shared_ptr<Derived>))
{
return std::static_pointer_cast<Derived>(*base_ptr).get();
return nullptr;
}

// Case 3: Otherwise, attempt a dynamic cast from Base to Derived
auto derived_ptr = std::dynamic_pointer_cast<Derived>(*base_ptr);
return derived_ptr ? derived_ptr.get() : nullptr;
}
catch(...)
{
return nullptr;
}

return nullptr;
return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
}

// This is the original type
Expand All @@ -351,6 +324,7 @@ class Any
private:
linb::any _any;
std::type_index _original_type;
mutable std::shared_ptr<void> _cached_derived_ptr = nullptr;

//----------------------------

Expand Down