Skip to content

Commit a422dd8

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 a422dd8

File tree

1 file changed

+152
-1
lines changed

1 file changed

+152
-1
lines changed

include/behaviortree_cpp/utils/safe_any.hpp

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ namespace BT
3131

3232
static std::type_index UndefinedAnyType = typeid(nullptr);
3333

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

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

@@ -107,6 +150,26 @@ class Any
107150
Any(const std::type_index& type) : _original_type(type)
108151
{}
109152

153+
// default for shared pointers
154+
template <typename T>
155+
explicit Any(const std::shared_ptr<T>& value)
156+
: _original_type(typeid(std::shared_ptr<T>))
157+
{
158+
using Base = typename any_cast_base<T>::type;
159+
160+
// store as base class if specialized
161+
if constexpr(!std::is_same_v<Base, void>)
162+
{
163+
static_assert(std::is_polymorphic_v<Base>, "Any Base trait specialization must be "
164+
"polymorphic");
165+
_any = std::static_pointer_cast<Base>(value);
166+
}
167+
else
168+
{
169+
_any = value;
170+
}
171+
}
172+
110173
// default for other custom types
111174
template <typename T>
112175
explicit Any(const T& value, EnableNonIntegral<T> = 0)
@@ -158,7 +221,7 @@ class Any
158221
// Method to access the value by pointer.
159222
// It will return nullptr, if the user try to cast it to a
160223
// wrong type or if Any was empty.
161-
template <typename T>
224+
template <typename T, typename = EnableNonPolymorphicSharedPtr<T>>
162225
[[nodiscard]] T* castPtr()
163226
{
164227
static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
@@ -192,6 +255,56 @@ class Any
192255
return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
193256
}
194257

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

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

0 commit comments

Comments
 (0)