@@ -31,6 +31,23 @@ namespace BT
31
31
32
32
static std::type_index UndefinedAnyType = typeid (nullptr );
33
33
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
+
34
51
// Rational: since type erased numbers will always use at least 8 bytes
35
52
// it is faster to cast everything to either double, uint64_t or int64_t.
36
53
class Any
@@ -58,6 +75,32 @@ class Any
58
75
typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_enum<T>::value &&
59
76
!std::is_same<T, std::string>::value>::type*;
60
77
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
+
61
104
template <typename T>
62
105
nonstd::expected<T, std::string> stringToNumber () const ;
63
106
@@ -107,6 +150,26 @@ class Any
107
150
Any (const std::type_index& type) : _original_type(type)
108
151
{}
109
152
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
+
110
173
// default for other custom types
111
174
template <typename T>
112
175
explicit Any (const T& value, EnableNonIntegral<T> = 0 )
@@ -158,7 +221,7 @@ class Any
158
221
// Method to access the value by pointer.
159
222
// It will return nullptr, if the user try to cast it to a
160
223
// wrong type or if Any was empty.
161
- template <typename T>
224
+ template <typename T, typename = EnableNonPolymorphicSharedPtr<T> >
162
225
[[nodiscard]] T* castPtr ()
163
226
{
164
227
static_assert (!std::is_same_v<T, float >, " The value has been casted internally to "
@@ -192,6 +255,56 @@ class Any
192
255
return _any.empty () ? nullptr : linb::any_cast<T>(&_any);
193
256
}
194
257
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
+
195
308
// This is the original type
196
309
[[nodiscard]] const std::type_index& type () const noexcept
197
310
{
@@ -513,6 +626,44 @@ inline nonstd::expected<T, std::string> Any::tryCast() const
513
626
throw std::runtime_error (" Any::cast failed because it is empty" );
514
627
}
515
628
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
+
516
667
if (castedType () == typeid (T))
517
668
{
518
669
return linb::any_cast<T>(_any);
0 commit comments