@@ -31,6 +31,23 @@ namespace BT
3131
3232static 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.
3653class 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