Skip to content

Commit 3021ff0

Browse files
committed
Enable polymorphic shared_ptr downcasting in Any using type traits
- 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.
1 parent 14589e5 commit 3021ff0

File tree

1 file changed

+152
-2
lines changed

1 file changed

+152
-2
lines changed

Diff for: include/behaviortree_cpp/utils/safe_any.hpp

+152-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,25 @@
2828

2929
namespace BT
3030
{
31-
3231
static std::type_index UndefinedAnyType = typeid(nullptr);
3332

33+
template <typename T>
34+
struct any_cast_base
35+
{
36+
using type = void; // Default: no base known, fallback to default any storage
37+
};
38+
39+
// Trait to detect std::shared_ptr types.
40+
template <typename T>
41+
struct is_shared_ptr : std::false_type
42+
{
43+
};
44+
45+
template <typename U>
46+
struct is_shared_ptr<std::shared_ptr<U>> : std::true_type
47+
{
48+
};
49+
3450
// Rational: since type erased numbers will always use at least 8 bytes
3551
// it is faster to cast everything to either double, uint64_t or int64_t.
3652
class Any
@@ -58,6 +74,32 @@ class Any
5874
typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_enum<T>::value &&
5975
!std::is_same<T, std::string>::value>::type*;
6076

77+
// Helper: IsPolymorphicSharedPtr<T> is true if T is a shared pointer,
78+
// its element_type exists, is polymorphic, and any_cast_base for that element
79+
// is specialized (i.e. not void).
80+
template <typename T, typename = void>
81+
struct IsPolymorphicSharedPtr : std::false_type
82+
{
83+
};
84+
85+
template <typename T>
86+
struct IsPolymorphicSharedPtr<T, std::void_t<typename T::element_type>>
87+
: std::integral_constant<
88+
bool,
89+
is_shared_ptr<T>::value && std::is_polymorphic_v<typename T::element_type> &&
90+
!std::is_same_v<typename any_cast_base<typename T::element_type>::type,
91+
void>>
92+
{
93+
};
94+
95+
template <typename T>
96+
using EnablePolymorphicSharedPtr =
97+
std::enable_if_t<IsPolymorphicSharedPtr<T>::value, int*>;
98+
99+
template <typename T>
100+
using EnableNonPolymorphicSharedPtr =
101+
std::enable_if_t<!IsPolymorphicSharedPtr<T>::value, int*>;
102+
61103
template <typename T>
62104
nonstd::expected<T, std::string> stringToNumber() const;
63105

@@ -107,6 +149,26 @@ class Any
107149
Any(const std::type_index& type) : _original_type(type)
108150
{}
109151

152+
// default for shared pointers
153+
template <typename T>
154+
explicit Any(const std::shared_ptr<T>& value)
155+
: _original_type(typeid(std::shared_ptr<T>))
156+
{
157+
using Base = typename any_cast_base<T>::type;
158+
159+
// store as base class if specialized
160+
if constexpr(!std::is_same_v<Base, void>)
161+
{
162+
static_assert(std::is_polymorphic_v<Base>, "Any Base trait specialization must be "
163+
"polymorphic");
164+
_any = std::static_pointer_cast<Base>(value);
165+
}
166+
else
167+
{
168+
_any = value;
169+
}
170+
}
171+
110172
// default for other custom types
111173
template <typename T>
112174
explicit Any(const T& value, EnableNonIntegral<T> = 0)
@@ -158,7 +220,7 @@ class Any
158220
// Method to access the value by pointer.
159221
// It will return nullptr, if the user try to cast it to a
160222
// wrong type or if Any was empty.
161-
template <typename T>
223+
template <typename T, typename = EnableNonPolymorphicSharedPtr<T>>
162224
[[nodiscard]] T* castPtr()
163225
{
164226
static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
@@ -192,6 +254,56 @@ class Any
192254
return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
193255
}
194256

257+
// Specialized version of castPtr() for shared_ptr<T> where T is a polymorphic type
258+
// with a registered base class via any_cast_base.
259+
//
260+
// Returns a raw pointer to T::element_type (i.e., Derived*), or nullptr on failure.
261+
//
262+
// Note: This function intentionally does not return a std::shared_ptr<T>* because doing so
263+
// would expose the internal ownership mechanism, which:
264+
// - Breaks encapsulation and may lead to accidental misuse (e.g., double-deletion, ref count tampering)
265+
// - Offers no real benefit, as the purpose of this function is to provide access
266+
// to the managed object, not the smart pointer itself.
267+
//
268+
// By returning a raw pointer to the object, we preserve ownership semantics and safely
269+
// allow read-only access without affecting the reference count.
270+
template <typename T, typename = EnablePolymorphicSharedPtr<T>>
271+
[[nodiscard]] typename T::element_type* castPtr()
272+
{
273+
using Derived = typename T::element_type;
274+
using Base = typename any_cast_base<Derived>::type;
275+
276+
try
277+
{
278+
// Attempt to retrieve the stored shared_ptr<Base> from the Any container
279+
auto base_ptr = linb::any_cast<std::shared_ptr<Base>>(&_any);
280+
if(!base_ptr)
281+
return nullptr;
282+
283+
// Case 1: If Base and Derived are the same, no casting is needed
284+
if constexpr(std::is_same_v<Base, Derived>)
285+
{
286+
return base_ptr ? base_ptr->get() : nullptr;
287+
}
288+
289+
// Case 2: If the original stored type was shared_ptr<Derived>, we can safely static_cast
290+
if(_original_type == typeid(std::shared_ptr<Derived>))
291+
{
292+
return std::static_pointer_cast<Derived>(*base_ptr).get();
293+
}
294+
295+
// Case 3: Otherwise, attempt a dynamic cast from Base to Derived
296+
auto derived_ptr = std::dynamic_pointer_cast<Derived>(*base_ptr);
297+
return derived_ptr ? derived_ptr.get() : nullptr;
298+
}
299+
catch(...)
300+
{
301+
return nullptr;
302+
}
303+
304+
return nullptr;
305+
}
306+
195307
// This is the original type
196308
[[nodiscard]] const std::type_index& type() const noexcept
197309
{
@@ -513,6 +625,44 @@ inline nonstd::expected<T, std::string> Any::tryCast() const
513625
throw std::runtime_error("Any::cast failed because it is empty");
514626
}
515627

628+
// special case: T is a shared_ptr to a registered polymorphic type.
629+
// The stored value is a shared_ptr<Base>, but the user is requesting shared_ptr<Derived>.
630+
// Perform safe downcasting (static or dynamic) from Base to Derived if applicable.
631+
if constexpr(is_shared_ptr<T>::value)
632+
{
633+
using Derived = typename T::element_type;
634+
using Base = typename any_cast_base<Derived>::type;
635+
636+
if constexpr(std::is_polymorphic_v<Derived> && !std::is_same_v<Base, void>)
637+
{
638+
// Attempt to retrieve the stored shared_ptr<Base> from the Any container
639+
auto base_ptr = linb::any_cast<std::shared_ptr<Base>>(_any);
640+
if(!base_ptr)
641+
{
642+
throw std::runtime_error("Any::cast cannot cast to shared_ptr<Base> class");
643+
}
644+
645+
// Case 1: If Base and Derived are the same, no casting is needed
646+
if constexpr(std::is_same_v<T, std::shared_ptr<Base>>)
647+
{
648+
return base_ptr;
649+
}
650+
651+
// Case 2: If the original stored type was shared_ptr<Derived>, we can safely static_cast
652+
if(_original_type == typeid(std::shared_ptr<Derived>))
653+
{
654+
return std::static_pointer_cast<Derived>(base_ptr);
655+
}
656+
657+
// Case 3: Otherwise, attempt a dynamic cast from Base to Derived
658+
auto derived_ptr = std::dynamic_pointer_cast<Derived>(base_ptr);
659+
if(!derived_ptr)
660+
throw std::runtime_error("Any::cast Dynamic cast failed, types are not related");
661+
662+
return derived_ptr;
663+
}
664+
}
665+
516666
if(castedType() == typeid(T))
517667
{
518668
return linb::any_cast<T>(_any);

0 commit comments

Comments
 (0)