@@ -120,23 +120,23 @@ class AnyBaseAll {};
120120
121121// / Small opaque type-erased type with configurable small buffer optimization
122122// /
123- // / @tparam sb_size Size of the internal buffer for small buffer optimization
124- // / @tparam copyable If true, stored types must be copyable and AnyBase is
123+ // / @tparam SbSize Size of the internal buffer for small buffer optimization
124+ // / @tparam Copyable If true, stored types must be copyable and AnyBase is
125125// / copyable. If false, move-only types are allowed and AnyBase is
126126// / move-only (copy constructor and copy assignment are deleted).
127127// /
128128// / @note
129- // / Type requirements when copyable is true:
129+ // / Type requirements when Copyable is true:
130130// / - All stored types must be copy constructible and copy assignable.
131- // / - Types stored locally (`sizeof(T) <= sb_size `) must also be move
131+ // / - Types stored locally (`sizeof(T) <= SbSize `) must also be move
132132// / constructible
133133// / and move assignable because local moves use move operations when not
134134// / trivially movable (trivial moves fall back to buffer copies).
135- // / - Types stored on the heap (`sizeof(T) > sb_size `) are moved by stealing the
135+ // / - Types stored on the heap (`sizeof(T) > SbSize `) are moved by stealing the
136136// / pointer, so no move operations are required in that case.
137137// /
138138// / @note
139- // / Type requirements when copyable is false:
139+ // / Type requirements when Copyable is false:
140140// / - All stored types must be move constructible.
141141// / - Types stored locally must also be move assignable.
142142// / - Heap-allocated types only need move constructible (pointer steal).
@@ -148,25 +148,29 @@ class AnyBaseAll {};
148148// / copies when trivial.
149149// / - Heap storage: values are allocated on the heap; moves transfer ownership
150150// / of the pointer; copies allocate and copy-construct the pointee.
151- template <std::size_t sb_size , bool copyable = true >
151+ template <std::size_t SbSize , bool Copyable = true , typename Base = void >
152152class AnyBase : public AnyBaseAll {
153- static_assert (sizeof (void *) <= sb_size , " Size is too small for a pointer" );
153+ static_assert (sizeof (void *) <= SbSize , " Size is too small for a pointer" );
154154
155- // / Type trait: T is storable when copyable requires copy+move, else
156- // / move-only.
155+ // / Type trait: T is storable when Copyable requires copy+move, else
156+ // / move-only. When @c Base is not @c void, the type must additionally be
157+ // / convertible to @c Base* (i.e. publicly and unambiguously derived).
157158 // / @tparam U Type to check
158159 // / @return True if the type is storable, false otherwise
159160 template <typename U>
160161 static constexpr bool isStorable () {
161162 if constexpr (std::is_base_of_v<AnyBaseAll, U>) {
162163 return false ;
163- } else if constexpr (copyable) {
164+ } else if constexpr (!std::is_void_v<Base> &&
165+ !std::is_convertible_v<U*, Base*>) {
166+ return false ;
167+ } else if constexpr (Copyable) {
164168 return std::is_copy_assignable_v<U> && std::is_copy_constructible_v<U> &&
165- (sizeof (U) > sb_size || (std::is_move_assignable_v<U> &&
166- std::is_move_constructible_v<U>));
169+ (sizeof (U) > SbSize || (std::is_move_assignable_v<U> &&
170+ std::is_move_constructible_v<U>));
167171 } else {
168172 return std::is_move_constructible_v<U> &&
169- (sizeof (U) > sb_size || std::is_move_assignable_v<U>);
173+ (sizeof (U) > SbSize || std::is_move_assignable_v<U>);
170174 }
171175 }
172176
@@ -304,12 +308,12 @@ class AnyBase : public AnyBaseAll {
304308
305309 ~AnyBase () { destroy (); }
306310
307- // / Copy constructor (only when copyable is true)
311+ // / Copy constructor (only when Copyable is true)
308312 // / @param other The AnyBase to copy from
309313 // / @note Not noexcept: copying a heap-allocated value allocates (may throw
310314 // / std::bad_alloc) and the stored type's copy constructor may throw.
311315 AnyBase (const AnyBase& other)
312- requires copyable
316+ requires Copyable
313317 {
314318 if (m_handler == nullptr && other.m_handler == nullptr ) {
315319 // both are empty, noop
@@ -323,18 +327,18 @@ class AnyBase : public AnyBaseAll {
323327 copyConstruct (other);
324328 }
325329
326- // / Copy constructor deleted when copyable is false (move-only variant)
330+ // / Copy constructor deleted when Copyable is false (move-only variant)
327331 AnyBase (const AnyBase&)
328- requires (!copyable )
332+ requires (!Copyable )
329333 = delete ;
330334
331- // / Copy assignment operator (only when copyable is true)
335+ // / Copy assignment operator (only when Copyable is true)
332336 // / @param other The AnyBase to copy from
333337 // / @return Reference to this object
334338 // / @note Not noexcept: copying a heap-allocated value allocates (may throw
335339 // / std::bad_alloc) and the stored type's copy operations may throw.
336340 AnyBase& operator =(const AnyBase& other)
337- requires copyable
341+ requires Copyable
338342 {
339343 _ACTS_ANY_VERBOSE (" Copy assign (this="
340344 << this << " ) at: " << static_cast <void *>(m_data.data ()));
@@ -374,9 +378,9 @@ class AnyBase : public AnyBaseAll {
374378 return *this ;
375379 }
376380
377- // / Copy assignment deleted when copyable is false (move-only variant)
381+ // / Copy assignment deleted when Copyable is false (move-only variant)
378382 AnyBase& operator =(const AnyBase&)
379- requires (!copyable )
383+ requires (!Copyable )
380384 = delete ;
381385
382386 // / Move constructor
@@ -438,6 +442,77 @@ class AnyBase : public AnyBaseAll {
438442 return m_handler != nullptr ? m_handler->typeInfo : nullptr ;
439443 }
440444
445+ // The base accessors below are member templates on a dummy @c B defaulting to
446+ // @c Base. This keeps their return types (@c B& / @c B*) from being formed
447+ // when @c Base is @c void: the member template is only instantiated on use,
448+ // and the @c requires clause removes it entirely for the untyped variant. A
449+ // plain non-template member would try to form @c void& at class instantiation
450+ // and fail to compile.
451+
452+ // / Get a pointer to the stored value as @c Base*, regardless of its concrete
453+ // / type. Only available when @c Base is not @c void.
454+ // / @return Pointer to the stored value upcast to @c Base*, or nullptr if empty
455+ template <typename B = Base>
456+ requires (!std::is_void_v<B>)
457+ B* asBase () {
458+ if (m_handler == nullptr ) {
459+ return nullptr ;
460+ }
461+ return m_handler->upcast (dataPtr ());
462+ }
463+
464+ // / Get a const pointer to the stored value as @c Base*. Only available when
465+ // / @c Base is not @c void.
466+ // / @return Const pointer to the stored value upcast to @c Base*, or nullptr if empty
467+ template <typename B = Base>
468+ requires (!std::is_void_v<B>)
469+ const B* asBase () const {
470+ if (m_handler == nullptr ) {
471+ return nullptr ;
472+ }
473+ return m_handler->upcastConst (dataPtr ());
474+ }
475+
476+ // / Dereference to the stored value as @c Base&. Only available when @c Base
477+ // / is not @c void.
478+ // / @return Reference to the stored value upcast to @c Base&
479+ template <typename B = Base>
480+ requires (!std::is_void_v<B>)
481+ B& operator *() {
482+ assert (m_handler != nullptr && " operator* on empty AnyBase" );
483+ return *m_handler->upcast (dataPtr ());
484+ }
485+
486+ // / Dereference to the stored value as @c const @c Base&. Only available when
487+ // / @c Base is not @c void.
488+ // / @return Const reference to the stored value upcast to @c Base&
489+ template <typename B = Base>
490+ requires (!std::is_void_v<B>)
491+ const B& operator *() const {
492+ assert (m_handler != nullptr && " operator* on empty AnyBase" );
493+ return *m_handler->upcastConst (dataPtr ());
494+ }
495+
496+ // / Member access on the stored value as @c Base*. Only available when
497+ // / @c Base is not @c void.
498+ // / @return Pointer to the stored value upcast to @c Base*
499+ template <typename B = Base>
500+ requires (!std::is_void_v<B>)
501+ B* operator ->() {
502+ assert (m_handler != nullptr && " operator-> on empty AnyBase" );
503+ return m_handler->upcast (dataPtr ());
504+ }
505+
506+ // / Member access on the stored value as @c const @c Base*. Only available
507+ // / when @c Base is not @c void.
508+ // / @return Const pointer to the stored value upcast to @c Base*
509+ template <typename B = Base>
510+ requires (!std::is_void_v<B>)
511+ const B* operator ->() const {
512+ assert (m_handler != nullptr && " operator-> on empty AnyBase" );
513+ return m_handler->upcastConst (dataPtr ());
514+ }
515+
441516 private:
442517 void * dataPtr () {
443518 if (m_handler->heapAllocated ) {
@@ -458,6 +533,13 @@ class AnyBase : public AnyBaseAll {
458533 }
459534
460535 struct Handler {
536+ // Maps the stored payload to Base*. Collapses to void*(*)(void*) and stays
537+ // nullptr (never read) when Base == void.
538+ Base* (*upcast)(void *) = nullptr ;
539+ // Const-qualified counterpart used by the const accessors so they don't
540+ // have to cast away const on the data pointer. Same as above when
541+ // Base == void.
542+ const Base* (*upcastConst)(const void *) = nullptr ;
461543 void (*destroy)(void * ptr) = nullptr ;
462544 void (*moveConstruct)(void * from, void * to) = nullptr ;
463545 void (*move)(void * from, void * to) = nullptr ;
@@ -499,6 +581,15 @@ class AnyBase : public AnyBaseAll {
499581 h.copy = ©Impl<T>;
500582 }
501583
584+ if constexpr (!std::is_void_v<Base>) {
585+ h.upcast = [](void * p) -> Base* {
586+ return static_cast <Base*>(static_cast <T*>(p));
587+ };
588+ h.upcastConst = [](const void * p) -> const Base* {
589+ return static_cast <const Base*>(static_cast <const T*>(p));
590+ };
591+ }
592+
502593 h.typeHash = typeHash<T>();
503594 h.typeInfo = &typeid (T);
504595
@@ -518,7 +609,7 @@ class AnyBase : public AnyBaseAll {
518609
519610 template <typename T>
520611 static constexpr bool heapAllocated () {
521- return sizeof (T) > sb_size ;
612+ return sizeof (T) > SbSize ;
522613 }
523614
524615 template <typename T, typename ... Args>
@@ -725,7 +816,7 @@ class AnyBase : public AnyBaseAll {
725816#endif
726817 );
727818
728- alignas (kMaxAlignment ) std::array<std::byte, sb_size > m_data{};
819+ alignas (kMaxAlignment ) std::array<std::byte, SbSize > m_data{};
729820 const Handler* m_handler{nullptr };
730821};
731822
@@ -740,6 +831,19 @@ using Any = AnyBase<sizeof(void*), true>;
740831// / Use when storing types that are not copyable.
741832using AnyMoveOnly = AnyBase<sizeof (void *), false >;
742833
834+ // / @brief Typed type-erased value over a common polymorphic base
835+ // / @details Stores any value of a type @c T convertible to @c Base* (i.e.
836+ // / publicly and unambiguously derived from @c Base) and hands it back
837+ // / as @c Base& / @c Base* through @ref AnyBase::asBase,
838+ // / @ref AnyBase::operator* and @ref AnyBase::operator-> without naming
839+ // / @c T. The concrete type can still be recovered with
840+ // / @ref AnyBase::as / @ref AnyBase::asPtr. Copyable; values up to
841+ // / @c SbSize bytes are stored inline, larger ones on the heap.
842+ // / @tparam Base Common base class exposed by the accessors
843+ // / @tparam SbSize Size of the small-buffer-optimization storage
844+ template <typename Base, std::size_t SbSize = sizeof (void *)>
845+ using AnyOf = AnyBase<SbSize, true , Base>;
846+
743847// / @}
744848
745849#undef _ACTS_ANY_VERBOSE
0 commit comments