aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-12-04 12:40:18 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-12-04 12:54:42 +0300
commitaefea967d5f49fd714d0cd3d6083014d2c610a29 (patch)
tree432f26000f05650140662c492e10da079bb7ccae /library/cpp
parentdfc382ee174bfd899e5ab5919df4020ba50f16bd (diff)
downloadydb-aefea967d5f49fd714d0cd3d6083014d2c610a29.tar.gz
Intermediate changes
commit_hash:cbf5ab65e5ba1b2d286f974a6b6b463ed152b381
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/yt/memory/type_erasure.h418
-rw-r--r--library/cpp/yt/memory/type_erasure_detail.h710
-rw-r--r--library/cpp/yt/memory/unittests/type_erasure_ut.cpp333
-rw-r--r--library/cpp/yt/memory/unittests/ya.make1
4 files changed, 1462 insertions, 0 deletions
diff --git a/library/cpp/yt/memory/type_erasure.h b/library/cpp/yt/memory/type_erasure.h
new file mode 100644
index 0000000000..92c4eb34c5
--- /dev/null
+++ b/library/cpp/yt/memory/type_erasure.h
@@ -0,0 +1,418 @@
+#pragma once
+
+#include "type_erasure_detail.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, class TCpo>
+struct TTypeErasableTraits;
+
+template <class T, auto Cpo, class TRet, bool NoExcept, class TCvThis, class... TArgs>
+struct TTypeErasableTraits<T, TOverloadedCpo<Cpo, TRet(TCvThis, TArgs...) noexcept(NoExcept)>>
+{
+ using TReplaced = TFromThis<T, TCvThis>;
+
+ static constexpr bool Value = NoExcept
+ ? requires (TReplaced t, TArgs... args) {
+ { Cpo(std::forward<TReplaced>(t), std::forward<TArgs>(args)...) } noexcept -> std::same_as<TRet>;
+ }
+ : requires (TReplaced t, TArgs... args) {
+ { Cpo(std::forward<TReplaced>(t), std::forward<TArgs>(args)...) } -> std::same_as<TRet>;
+ };
+};
+
+template <class T, class... TCpos>
+concept CAnyRefErasable = (TTypeErasableTraits<std::remove_cvref_t<T>, TCpos>::Value && ...);
+
+template <class T, bool EnableCopy, class... TCpos>
+concept CAnyObjectErasable =
+ CAnyRefErasable<T, TCpos...> &&
+ std::movable<std::remove_cvref_t<T>> &&
+ (!EnableCopy || std::copyable<std::remove_cvref_t<T>>);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TBadAnyCast
+ : public std::exception
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+// AnyRef.
+
+template <bool UseStaticVTable, class... TCpos>
+class TAnyRef
+ : private TAnyFragment<TAnyRef<UseStaticVTable, TCpos...>, TCpos>...
+{
+public:
+ using TStorage = TNonOwningStorage;
+
+ TAnyRef() = default;
+
+ template <class TConcrete>
+ requires
+ CAnyRefErasable<std::remove_cvref_t<TConcrete>, TCpos...> &&
+ (!std::same_as<std::remove_cvref_t<TConcrete>, TAnyRef>)
+ TAnyRef(TConcrete& concRef) noexcept
+ {
+ using TDecayed = std::remove_cvref_t<TConcrete>;
+
+ Storage_.Set(const_cast<void*>(reinterpret_cast<const void*>(std::addressof(concRef))));
+ Holder_ = THolder::template Create<TDecayed>();
+ }
+
+ // Copy/Move are trivial copy. Dtor is also trivial.
+
+ const auto& GetStorage() const &
+ {
+ return Storage_;
+ }
+
+ auto& GetStorage() &
+ {
+ return Storage_;
+ }
+
+ auto&& GetStorage() &&
+ {
+ return std::move(Storage_);
+ }
+
+ const auto& GetVTable() const
+ {
+ return Holder_.GetVTable();
+ }
+
+ template <class T>
+ requires CAnyRefErasable<T, TCpos...>
+ T& AnyCast() &
+ {
+ const auto& vtable = GetVTable();
+ if (!vtable.IsValid() || !vtable.template IsCurrentlyStored<T>()) {
+ throw TBadAnyCast{};
+ }
+
+ return Storage_.template As<T>();
+ }
+
+ template <class T>
+ requires CAnyRefErasable<T, TCpos...>
+ const T& AnyCast() const &
+ {
+ const auto& vtable = Holder_.GetVTable();
+ if (!vtable.IsValid() || !vtable.template IsCurrentlyStored<T>()) {
+ throw TBadAnyCast{};
+ }
+
+ return Storage_.template As<T>();
+ }
+
+private:
+ using TVTable = TVTable<TStorage, TCpos...>;
+ using THolder = TVTableHolder<TStorage, TVTable, UseStaticVTable>;
+
+ TStorage Storage_ = {};
+ THolder Holder_ = {};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... TCpos>
+struct TAnyRefPublicHelper
+{
+ using T = TAnyRef<(sizeof...(TCpos) >= 3), TCpos...>;
+};
+
+template <>
+struct TAnyRefPublicHelper<>
+{
+ using T = TAnyRef<false, TTagInvokeTag<NoopCpo>>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TIsAnyRef
+ : public std::false_type
+{ };
+
+template <bool UseStaticVTable, class... TCpos>
+struct TIsAnyRef<TAnyRef<UseStaticVTable, TCpos...>>
+ : public std::true_type
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <bool EnableCopy, CStorage TStorage, class... TCpos>
+struct TRebindVTable
+{
+ using TVTable = TVTable<
+ TStorage,
+ TTagInvokeTag<Deleter>,
+ TTagInvokeTag<Mover<TStorage>>,
+ TTagInvokeTag<Copier<TStorage>>,
+ TCpos...>;
+};
+
+template <CStorage TStorage, class... TCpos>
+struct TRebindVTable<false, TStorage, TCpos...>
+{
+ using TVTable = TVTable<
+ TStorage,
+ TTagInvokeTag<Deleter>,
+ TTagInvokeTag<Mover<TStorage>>,
+ TCpos...>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <bool UseStaticVTable, bool EnableCopy, size_t SmallObjectSize, size_t SmallObjectAlign, class... TCpos>
+class TAnyObject
+ : private TAnyFragment<
+ TAnyObject<
+ UseStaticVTable,
+ EnableCopy,
+ SmallObjectSize,
+ SmallObjectAlign,
+ TCpos...>,
+ TCpos>...
+{
+public:
+ using TStorage = TOwningStorage<SmallObjectSize, SmallObjectAlign>;
+
+ TAnyObject() = default;
+
+ template <class TConcrete>
+ requires
+ (!std::same_as<std::remove_cvref_t<TConcrete>, TAnyObject>) &&
+ std::constructible_from<std::remove_cvref_t<TConcrete>, TConcrete> &&
+ CAnyObjectErasable<std::remove_cvref_t<TConcrete>, EnableCopy, TCpos...> &&
+ (!TIsAnyRef<std::remove_cvref_t<TConcrete>>::value)
+ TAnyObject(TConcrete&& concrete)
+ {
+ Set<TConcrete>(std::forward<TConcrete>(concrete));
+ }
+
+ template <class TConcrete, class... TArgs>
+ requires
+ (!std::same_as<std::remove_cvref_t<TConcrete>, TAnyObject>) &&
+ std::constructible_from<std::remove_cvref_t<TConcrete>, TArgs...> &&
+ (!std::is_const_v<TConcrete>) &&
+ CAnyObjectErasable<std::remove_cvref_t<TConcrete>, EnableCopy, TCpos...> &&
+ (!TIsAnyRef<std::remove_cvref_t<TConcrete>>::value)
+ TAnyObject(std::in_place_type_t<TConcrete>, TArgs&&... args)
+ {
+ Set<TConcrete>(std::forward<TArgs>(args)...);
+ }
+
+ TAnyObject(TAnyObject&& other)
+ : Holder_(other.Holder_)
+ {
+ if (IsValid()) {
+ auto* mover = GetVTable().template GetFunctor<Mover<TStorage>>();
+ mover(std::move(other).GetStorage(), GetStorage());
+
+ other.Holder_.Reset();
+ }
+ }
+
+ TAnyObject& operator=(TAnyObject&& other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+
+ Reset();
+
+ Holder_ = other.Holder_;
+
+ if (IsValid()) {
+ auto* mover = GetVTable().template GetFunctor<Mover<TStorage>>();
+ mover(std::move(other).GetStorage(), GetStorage());
+
+ other.Holder_.Reset();
+ }
+
+ return *this;
+ }
+
+ TAnyObject(const TAnyObject& other) requires EnableCopy
+ : Holder_(other.Holder_)
+ {
+ if (IsValid()) {
+ auto* copier = GetVTable().template GetFunctor<Copier<TStorage>>();
+ copier(other.GetStorage(), GetStorage());
+ }
+ }
+
+ TAnyObject& operator=(const TAnyObject& other) requires EnableCopy
+ {
+ if (this == &other) {
+ return *this;
+ }
+
+ Reset();
+
+ Holder_ = other.Holder_;
+
+ if (IsValid()) {
+ auto* copier = GetVTable().template GetFunctor<Copier<TStorage>>();
+ copier(other.GetStorage(), GetStorage());
+ }
+
+ return *this;
+ }
+
+ constexpr explicit operator bool() const
+ {
+ return IsValid();
+ }
+
+ ~TAnyObject()
+ {
+ Reset();
+ }
+
+ Y_FORCE_INLINE const auto& GetStorage() const &
+ {
+ return Storage_;
+ }
+
+ Y_FORCE_INLINE auto& GetStorage() &
+ {
+ return Storage_;
+ }
+
+ Y_FORCE_INLINE auto&& GetStorage() &&
+ {
+ return std::move(Storage_);
+ }
+
+ Y_FORCE_INLINE const auto& GetVTable() const
+ {
+ return Holder_.GetVTable();
+ }
+
+ template <class T>
+ Y_FORCE_INLINE T& AnyCast() &
+ {
+ using TDecayed = std::remove_cvref_t<T>;
+ using TWrapped = TOwningWrapper<TDecayed, TStorage, EnableCopy, TCpos...>;
+
+ const auto& vtable = GetVTable();
+ if (!vtable.IsValid() || !vtable.template IsCurrentlyStored<TWrapped>()) {
+ throw TBadAnyCast{};
+ }
+
+ return Storage_.template As<TWrapped>().Unwrap();
+ }
+
+ template <class T>
+ Y_FORCE_INLINE const T& AnyCast() const &
+ {
+ using TDecayed = std::remove_cvref_t<T>;
+ using TWrapped = TOwningWrapper<TDecayed, TStorage, EnableCopy, TCpos...>;
+
+ const auto& vtable = GetVTable();
+ if (!vtable.IsValid() || !vtable.template IsCurrentlyStored<TWrapped>()) {
+ throw TBadAnyCast{};
+ }
+
+ return Storage_.template As<TWrapped>().Unwrap();
+ }
+
+ Y_FORCE_INLINE bool IsValid() const
+ {
+ return Holder_.IsValid();
+ }
+
+private:
+ using TVTable = typename TRebindVTable<EnableCopy, TStorage, TCpos...>::TVTable;
+ using THolder = TVTableHolder<TStorage, TVTable, UseStaticVTable>;
+ using TTraits = std::allocator_traits<std::allocator<std::byte>>;
+
+ TStorage Storage_ = {};
+ THolder Holder_ = {};
+
+ static inline std::allocator<std::byte> Allocator = {};
+
+ Y_FORCE_INLINE void Reset() noexcept
+ {
+ const auto& vtable = Holder_.GetVTable();
+ if (Holder_.IsValid()) {
+ auto* deleter = vtable.template GetFunctor<Deleter>();
+ deleter(Storage_);
+
+ Holder_.Reset();
+ }
+ }
+
+ template <class TConcrete, class... TArgs>
+ Y_FORCE_INLINE void Set(TArgs&&... args)
+ {
+ using TDecayed = std::remove_cvref_t<TConcrete>;
+ using TWrapped = TOwningWrapper<TDecayed, TStorage, EnableCopy, TCpos...>;
+
+ Reset();
+
+ Holder_ = THolder::template Create<TWrapped>();
+
+ if constexpr (TStorage::template IsStatic<TWrapped>) {
+ Storage_.Set();
+ } else {
+ Storage_.Set(TTraits::allocate(Allocator, sizeof(TWrapped)));
+ }
+
+ TTraits::template construct<TWrapped>(
+ Allocator,
+ &Storage_.template As<TWrapped>(),
+ std::forward<TArgs>(args)...);
+ }
+};
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+// A simple placeholder for overloaded cpo usage.
+using NDetail::TErasedThis;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// A wrapper class which carries all the necessary information for type erasure.
+template <auto Cpo, class TSignature>
+using TOverload = NDetail::TOverloadedCpo<Cpo, TSignature>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// A non-owning reference.
+template <class... TCpos>
+using TAnyRef = typename NDetail::TAnyRefPublicHelper<TCpos...>::T;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... TCpos>
+using TAnyObject = NDetail::TAnyObject<
+ (sizeof...(TCpos) >= 3),
+ /*EnableCopy*/ true,
+ /*SmallObjectSize*/ sizeof(void*) * 2,
+ /*SmallObjectAlign*/ alignof(void*) * 2,
+ TCpos...>;
+
+template <class... TCpos>
+using TAnyUnique = NDetail::TAnyObject<
+ (sizeof...(TCpos) >= 3),
+ /*EnableCopy*/ false,
+ /*SmallObjectSize*/ sizeof(void*) * 2,
+ /*SmallObjectAlign*/ alignof(void*) * 2,
+ TCpos...>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/memory/type_erasure_detail.h b/library/cpp/yt/memory/type_erasure_detail.h
new file mode 100644
index 0000000000..5e158d3a81
--- /dev/null
+++ b/library/cpp/yt/memory/type_erasure_detail.h
@@ -0,0 +1,710 @@
+#pragma once
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <library/cpp/yt/misc/tag_invoke_cpo.h>
+
+#include <util/system/compiler.h>
+
+#include <concepts>
+#include <memory>
+#include <numeric>
+
+namespace NYT::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): Next section of code is about most general type erasure storage.
+// There are two kinds: One which simply holds a pointer to data stored elsewhere
+// and the one which holds the data on its own. The latter can store it inline
+// on the byte array or on heap depending on the object size (small object optimization).
+// Classes below feature bare-bone implementations of said storages which do not
+// control when and which object are being placed inside the storage for they do not
+// have access to the virtual table (ctors/dtors) of the type. Said operations are handled
+// by type erasure containers themselves.
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): This class is used to check
+// whether some class has a templated method which
+// accepts any type parameter.
+struct TConceptWitness
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Semantic requirement:
+// 1) Pointer provided by |Ptr| call
+// must be pointing to the beginning of some object.
+// 2) IsStatic must be a constexpr variable.
+// NB(arkady-e1ppa): value of bool template in Ptr
+// is whether currently stored object is static or not.
+template <class T>
+concept CPointerProvider = requires (T* t, const T* ct) {
+ { t->template Ptr<true>() } -> std::same_as<void*>;
+ { ct->template Ptr<true>() } -> std::same_as<void*>;
+ { t->template Ptr<false>() } -> std::same_as<void*>;
+ { ct->template Ptr<false>() } -> std::same_as<void*>;
+ { T::template IsStatic<TConceptWitness> } -> std::same_as<const bool&>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// CRTP base to implement cast methods for each
+// storage.
+template <class TDerived>
+struct TStorageCasterBase
+{
+ template <bool IsStatic>
+ Y_FORCE_INLINE void* GetPtr() const noexcept
+ {
+ return static_cast<const TDerived*>(this)->template Ptr<IsStatic>();
+ }
+
+ template <class TDecayedConcrete>
+ Y_FORCE_INLINE TDecayedConcrete& As() & noexcept
+ {
+ return this->template ToRef<TDecayedConcrete, TDecayedConcrete*>();
+ }
+
+ template <class TDecayedConcrete>
+ Y_FORCE_INLINE const TDecayedConcrete& As() const & noexcept
+ {
+ return this->template ToRef<TDecayedConcrete, const TDecayedConcrete*>();
+ }
+
+ template <class TDecayedConcrete>
+ Y_FORCE_INLINE TDecayedConcrete&& As() && noexcept
+ {
+ return std::move(this->template ToRef<TDecayedConcrete, TDecayedConcrete*>());
+ }
+
+ template <class TDecayedConcrete, class TPtr>
+ Y_FORCE_INLINE decltype(auto) ToRef() const noexcept
+ {
+ static_assert(std::derived_from<TDerived, TStorageCasterBase>, "Must inherit from TStorageCasterBase");
+ static_assert(CPointerProvider<TDerived>, "Class must define Ptr method. See CPointerProvider");
+ static_assert(
+ std::same_as<
+ TDecayedConcrete,
+ std::remove_cvref_t<TDecayedConcrete>>,
+ "Submitted type must not contain const, reference or volatile qualifiers");
+
+ if constexpr (TDerived::template IsStatic<TDecayedConcrete>) {
+ return *std::launder(reinterpret_cast<TPtr>(this->template GetPtr</*IsStatic*/ true>()));
+ } else {
+ return *static_cast<TPtr>(this->template GetPtr</*IsStatic*/ false>());
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+concept CStorage = requires (const T& cref, T& ref, void* ptr) {
+ { cref.template As<TConceptWitness>() } -> std::same_as<const TConceptWitness&>;
+ { ref.template As<TConceptWitness>() } -> std::same_as<TConceptWitness&>;
+ { std::move(ref).template As<TConceptWitness>() } -> std::same_as<TConceptWitness&&>;
+ { ref.Set(ptr) } -> std::same_as<void>;
+
+ // Semantic: Set() should reset the instance making it empty.
+ { ref.Set() } -> std::same_as<void>;
+} && std::default_initializable<T>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TNonOwningStorage
+ : public TStorageCasterBase<TNonOwningStorage>
+{
+public:
+ template <bool /*IsStatic*/>
+ Y_FORCE_INLINE void* Ptr() const noexcept
+ {
+ return const_cast<void*>(Data_);
+ }
+
+ template <class T>
+ static constexpr bool IsStatic = false;
+
+ Y_FORCE_INLINE void Set(void* ptr) noexcept
+ {
+ Data_ = ptr;
+ }
+
+ Y_FORCE_INLINE void Set() noexcept
+ {
+ Data_ = nullptr;
+ }
+
+private:
+ void* Data_ = nullptr;
+};
+
+static_assert(CPointerProvider<TNonOwningStorage>);
+static_assert(CStorage<TNonOwningStorage>);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <size_t InlineSize, size_t InlineAlign>
+class TOwningStorage
+ : public TStorageCasterBase<TOwningStorage<InlineSize, InlineAlign>>
+{
+private:
+ static constexpr size_t RealSize = sizeof(void*) > InlineSize
+ ? sizeof(void*)
+ : InlineSize;
+
+ static constexpr size_t RealAlign = alignof(void*) > InlineAlign
+ ? alignof(void*)
+ : InlineAlign;
+
+public:
+ template <class T>
+ static constexpr bool IsStatic =
+ (sizeof(T) <= RealSize) &&
+ (alignof(T) <= RealAlign);
+
+ Y_FORCE_INLINE void Set(void* ptr) noexcept
+ {
+ std::construct_at<void*>(reinterpret_cast<void**>(Data_), ptr);
+ }
+
+ Y_FORCE_INLINE void Set() noexcept
+ {
+ std::construct_at(Data_);
+ }
+
+ template <bool IsStatic>
+ Y_FORCE_INLINE void* Ptr() const noexcept
+ {
+ auto* mutableThis = const_cast<TOwningStorage*>(this);
+
+ if constexpr (IsStatic) {
+ return &mutableThis->Data_;
+ } else {
+ return *std::launder(reinterpret_cast<void**>(&mutableThis->Data_));
+ }
+ }
+
+private:
+ alignas(RealAlign) std::byte Data_[RealSize] = {};
+};
+
+static_assert(CPointerProvider<TOwningStorage<1, 1>>);
+static_assert(CStorage<TOwningStorage<1, 1>>);
+static_assert(CPointerProvider<TOwningStorage<64, 64>>);
+static_assert(CStorage<TOwningStorage<64, 64>>);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): This part covers details of overloaded CPOs parcing and placeholder types.
+// Relevant to public interface parts are exposed in type_erasure.h
+////////////////////////////////////////////////////////////////////////////////
+
+// Type erasable "function" = (function name <=> cpo) + (fixed signature).
+template <auto Cpo, class TSignature>
+struct TOverloadedCpo;
+
+template <auto Cpo, class TRet, bool NoExcept, class... TArgs>
+struct TOverloadedCpo<Cpo, TRet(TArgs...) noexcept(NoExcept)>
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Each implementation of erasable method has
+// its own "this" in signature. Since in the erased methods
+// we do not have any specific type, we use placeholder.
+struct TErasedThis
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, class U>
+struct TFromThisImpl
+{
+ using TT = U;
+};
+
+template <class T>
+struct TFromThisImpl<T, const TErasedThis&>
+{
+ using TT = const T&;
+};
+
+template <class T>
+struct TFromThisImpl<T, TErasedThis&>
+{
+ using TT = T&;
+};
+
+template <class T>
+struct TFromThisImpl<T, TErasedThis&&>
+{
+ using TT = T&&;
+};
+
+template <class T>
+struct TFromThisImpl<T, TErasedThis>
+{
+ using TT = T;
+};
+
+template <class T, class U>
+using TFromThis = typename TFromThisImpl<T, U>::TT;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <CStorage TStorage, class TCpo>
+class TVTableEntry;
+
+template <CStorage TStorage, auto Cpo, class TRet, bool NoExcept, class TCvThis, class... TArgs>
+class TVTableEntry<TStorage, TOverloadedCpo<Cpo, TRet(TCvThis, TArgs...) noexcept(NoExcept)>>
+{
+private:
+ using TReplaced = TFromThis<TStorage, TCvThis>;
+ using TSignature = TRet(TReplaced, TArgs...) noexcept(NoExcept);
+ using TFunction = TRet(*)(TReplaced, TArgs...) noexcept(NoExcept);
+
+public:
+ TVTableEntry() = default;
+
+ Y_FORCE_INLINE TFunction Get() const noexcept
+ {
+ return Function_;
+ }
+
+ template <class TConcrete>
+ Y_FORCE_INLINE static TVTableEntry Create() noexcept
+ {
+ TVTableEntry entry = {};
+
+ entry.Function_ = &TVTableEntry::StaticInvoke<TConcrete>;
+
+ return entry;
+ }
+
+ Y_FORCE_INLINE void Reset() noexcept
+ {
+ Function_ = nullptr;
+ }
+
+ Y_FORCE_INLINE bool IsValid() const noexcept
+ {
+ return Function_ != nullptr;
+ }
+
+ // NB(arkady-e1ppa): This method may or may not work correctly
+ // for dynamically-linked libraries.
+ template <class T>
+ Y_FORCE_INLINE bool IsCurrentlyStored() const noexcept
+ {
+ return Function_ == &TVTableEntry::StaticInvoke<T>;
+ }
+
+private:
+ TFunction Function_ = nullptr;
+
+ template <class TConcrete>
+ static TRet StaticInvoke(TReplaced storage, TArgs... args) noexcept(NoExcept)
+ {
+ return Cpo(std::forward<TReplaced>(storage).template As<TConcrete>(), std::forward<TArgs>(args)...);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Add vtable holder for static/local versions.
+template <CStorage TStorage, class... TCpos>
+class TVTable;
+
+// NB(arkady-e1ppa): We do not support empty vtables since there is no way
+// to check validity nor type of the object for such vtables.
+template <CStorage TStorage, class TCpo, class... TCpos>
+class TVTable<TStorage, TCpo, TCpos...>
+ : private TVTableEntry<TStorage, TCpo>
+ , private TVTableEntry<TStorage, TCpos>...
+{
+public:
+ TVTable() = default;
+
+ template <class TConcrete>
+ Y_FORCE_INLINE static TVTable Create() noexcept
+ {
+ return TVTable{TCtorTag<TConcrete>{}};
+ }
+
+ template <auto C>
+ Y_FORCE_INLINE auto GetFunctor() const noexcept
+ {
+ return TVTableEntry<TStorage, TTagInvokeTag<C>>::Get();
+ }
+
+ Y_FORCE_INLINE void Reset() noexcept
+ {
+ TVTableEntry<TStorage, TCpo>::Reset();
+ }
+
+ Y_FORCE_INLINE bool IsValid() const noexcept
+ {
+ return TVTableEntry<TStorage, TCpo>::IsValid();
+ }
+
+ template <class T>
+ Y_FORCE_INLINE bool IsCurrentlyStored() const noexcept
+ {
+ return TVTableEntry<TStorage, TCpo>::template IsCurrentlyStored<T>();
+ }
+
+private:
+ template <class T>
+ struct TCtorTag
+ { };
+
+ template <class T>
+ explicit TVTable(TCtorTag<T>) noexcept
+ : TVTableEntry<TStorage, TCpo>{TVTableEntry<TStorage, TCpo>::template Create<T>()}
+ , TVTableEntry<TStorage, TCpos>{TVTableEntry<TStorage, TCpos>::template Create<T>()}...
+ { }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, class TStorage>
+concept CVTableFor =
+ CStorage<TStorage> &&
+ requires (const T& t) {
+ [] <class... TCpos> (TVTable<TStorage, TCpos...>) { } (t);
+ };
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TStorage, CVTableFor<TStorage> TTable, bool IsStatic>
+class TVTableHolder
+{
+public:
+ TVTableHolder() = default;
+
+ bool IsValid() const
+ {
+ return Table_ != nullptr && Table_->IsValid();
+ }
+
+ template <class TConcrete>
+ static TVTableHolder Create()
+ {
+ static TTable staticTable = TTable::template Create<TConcrete>();
+
+ TVTableHolder holder = {};
+ holder.Table_ = std::addressof(staticTable);
+ return holder;
+ }
+
+ const TTable& GetVTable() const
+ {
+ if (!IsValid()) {
+ return EmptyTable;
+ }
+
+ return *Table_;
+ }
+
+ void Reset()
+ {
+ Table_ = nullptr;
+ }
+
+private:
+ TTable* Table_ = nullptr;
+
+ static inline TTable EmptyTable = {};
+};
+
+template <class TStorage, CVTableFor<TStorage> TTable>
+class TVTableHolder<TStorage, TTable, false>
+{
+public:
+ TVTableHolder() = default;
+
+ bool IsValid() const
+ {
+ return Table_.IsValid();
+ }
+
+ template <class TConcrete>
+ static TVTableHolder Create()
+ {
+ TVTableHolder holder = {};
+ holder.Table_ = TTable::template Create<TConcrete>();
+ return holder;
+ }
+
+ const TTable& GetVTable() const
+ {
+ return Table_;
+ }
+
+ void Reset()
+ {
+ Table_.Reset();
+ }
+
+private:
+ TTable Table_ = {};
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): Below are Cpos which replicate function of default language features
+// such as copy/move ctors, dtor. More can be added later if needed.
+// But they are likely to be an overkill.
+// General formula is that we have a cpo for default feature and then
+// a wrapper of an object which serves the purpose of an adaptor between
+// native c++ expression and CPO expression of the same behavior.
+////////////////////////////////////////////////////////////////////////////////
+
+struct TDeleter
+ : public TTagInvokeCpoBase<TDeleter>
+{ };
+
+inline constexpr TOverloadedCpo<TDeleter{}, void(TErasedThis&) noexcept> Deleter = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): We do not use custom allocators with wacky policies
+// and therefore don't adopt reallocating move ctor.
+template <CStorage TStorage>
+struct TMover
+ : public TTagInvokeCpoBase<TMover<TStorage>>
+{ };
+
+template <CStorage TStorage>
+inline constexpr TOverloadedCpo<TMover<TStorage>{}, void(TErasedThis&&, TStorage&)> Mover = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <CStorage TStorage>
+struct TCopier
+ : public TTagInvokeCpoBase<TCopier<TStorage>>
+{ };
+
+template <CStorage TStorage>
+inline constexpr TOverloadedCpo<TCopier<TStorage>{}, void(const TErasedThis&, TStorage&)> Copier = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TNoopCpo
+{
+ template <class... TArgs>
+ constexpr void operator() (TArgs&&... /*args*/) const
+ {
+ YT_ABORT();
+ }
+};
+
+inline constexpr TOverloadedCpo<TNoopCpo{}, void(const TErasedThis&)> NoopCpo = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class W, class T>
+concept CWrapperOf = requires (W& ref, const W& cref) {
+ { ref.Unwrap() } -> std::same_as<T&>;
+ { std::move(ref).Unwrap() } -> std::same_as<T&&>;
+ { cref.Unwrap() } -> std::same_as<const T&>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Wraps value, adding TagInvoke overload for dtor and move/copy ctors.
+template <class TDerived, class TConcrete, CStorage TStorage, bool EnableCopy>
+class TOwningWrapperBase
+{
+public:
+ // NB(arkady-e1ppa): Wrapper is stored not only the object itself.
+ static constexpr bool IsStatic = TStorage::template IsStatic<TDerived>;
+
+ using TTraits = std::allocator_traits<std::allocator<std::byte>>;
+ static inline std::allocator<std::byte> Allocator = {};
+
+ template <class... TArgs>
+ requires std::constructible_from<TConcrete, TArgs...>
+ explicit TOwningWrapperBase(TArgs&&... args)
+ noexcept(std::is_nothrow_constructible_v<TConcrete, TArgs...>)
+ : Concrete_(std::forward<TArgs>(args)...)
+ { }
+
+ Y_FORCE_INLINE void Delete() & noexcept
+ {
+ TTraits::template destroy<TDerived>(Allocator, AsFinal());
+
+ if constexpr (!IsStatic) {
+ TTraits::deallocate(Allocator, reinterpret_cast<std::byte*>(AsFinal()), sizeof(TDerived));
+ }
+ }
+
+ Y_FORCE_INLINE void Move(TStorage& to) &&
+ requires std::movable<TConcrete>
+ {
+ if constexpr (IsStatic) {
+ to.Set();
+
+ TTraits::template construct<TDerived>(
+ Allocator,
+ &to.template As<TDerived>(),
+ std::move(*AsFinal()));
+ } else {
+ to.Set(static_cast<void*>(AsFinal()));
+ }
+ }
+
+ Y_FORCE_INLINE void Copy(TStorage& to) const &
+ requires (EnableCopy && std::copyable<TConcrete>)
+ {
+ if constexpr (IsStatic) {
+ to.Set();
+ } else {
+ to.Set(TTraits::allocate(Allocator, sizeof(TDerived)));
+ }
+
+ TTraits::template construct<TDerived>(
+ Allocator,
+ &to.template As<TDerived>(),
+ *AsFinal());
+ }
+
+ friend Y_FORCE_INLINE void TagInvoke(TDeleter, TDerived& this_) noexcept
+ {
+ static_assert(std::derived_from<TDerived, TOwningWrapperBase>, "Must derived from TOwningWrapperBase");
+ this_.Delete();
+ }
+
+ friend Y_FORCE_INLINE void TagInvoke(TMover<TStorage>, TDerived&& this_, TStorage& to)
+ noexcept(std::is_nothrow_move_constructible_v<TConcrete>)
+ {
+ std::move(this_).Move(to);
+ }
+
+ friend Y_FORCE_INLINE void TagInvoke(TCopier<TStorage>, const TDerived& this_, TStorage& to)
+ noexcept(std::is_nothrow_copy_constructible_v<TConcrete>)
+ {
+ this_.Copy(to);
+ }
+
+ // CWrapperOf<TConcrete>
+ Y_FORCE_INLINE TConcrete& Unwrap() &
+ {
+ return Concrete_;
+ }
+
+ Y_FORCE_INLINE const TConcrete& Unwrap() const &
+ {
+ return Concrete_;
+ }
+
+ Y_FORCE_INLINE TConcrete&& Unwrap() &&
+ {
+ return std::move(Concrete_);
+ }
+
+private:
+ TConcrete Concrete_;
+
+ Y_FORCE_INLINE TDerived* AsFinal() const
+ {
+ return static_cast<TDerived*>(const_cast<TOwningWrapperBase*>(this));
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TDerived, class TCpo>
+struct TUnwrappingTagInvokeBase;
+
+// NB(arkady-e1ppa): Technically, we could accept TErasedThis as any argument, but
+// there is barely any profit from doing so yet code becomes much more cumbersome
+// both here and around TAny(Object|Ref).
+template <class TDerived, auto Cpo, class TRet, bool NoExcept, class TCvThis, class... TArgs>
+struct TUnwrappingTagInvokeBase<TDerived, TOverloadedCpo<Cpo, TRet(TCvThis, TArgs...) noexcept(NoExcept)>>
+{
+ using TReplaced = TFromThis<TDerived, TCvThis>;
+
+ friend Y_FORCE_INLINE TRet TagInvoke(TTagInvokeTag<Cpo>, TReplaced wrapper, TArgs... args) noexcept(NoExcept)
+ {
+ return Cpo(std::forward<TReplaced>(wrapper).Unwrap(), std::forward<TArgs>(args)...);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TConcrete, CStorage TStorage, bool EnableCopy, class... TCpos>
+class TOwningWrapper final
+ : public TOwningWrapperBase<
+ TOwningWrapper<
+ TConcrete,
+ TStorage,
+ EnableCopy,
+ TCpos...>,
+ TConcrete,
+ TStorage,
+ EnableCopy>
+ , public TUnwrappingTagInvokeBase<
+ TOwningWrapper<
+ TConcrete,
+ TStorage,
+ EnableCopy,
+ TCpos...>,
+ TCpos>...
+{
+ using TBase = TOwningWrapperBase<
+ TOwningWrapper<
+ TConcrete,
+ TStorage,
+ EnableCopy,
+ TCpos...>,
+ TConcrete,
+ TStorage,
+ EnableCopy>;
+
+ using TBase::TBase;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Every "Any-like" object must be able to provide access to storage
+// and a vtable which matches the storage provided.
+template <class T>
+concept CSomeAnyObject = requires (T& ref, const T& cref) {
+ typename T::TStorage;
+ { ref.GetStorage() } -> std::same_as<typename T::TStorage&>;
+ { std::move(ref).GetStorage() } -> std::same_as<typename T::TStorage&&>;
+ { cref.GetStorage() } -> std::same_as<const typename T::TStorage&>;
+
+ { cref.GetVTable() } -> CVTableFor<typename T::TStorage>;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TDerived, class TCpo>
+struct TAnyFragment;
+
+template <class TDerived, auto Cpo, class TRet, bool NoExcept, class TCvThis, class... TArgs>
+struct TAnyFragment<TDerived, TOverloadedCpo<Cpo, TRet(TCvThis, TArgs...) noexcept(NoExcept)>>
+{
+ using TReplaced = TFromThis<TDerived, TCvThis>;
+ using TVTableTag = TOverloadedCpo<Cpo, TRet(TCvThis, TArgs...) noexcept(NoExcept)>;
+
+ friend Y_FORCE_INLINE TRet TagInvoke(TTagInvokeTag<Cpo>, TReplaced any, TArgs... args) noexcept(NoExcept)
+ {
+ static_assert(CSomeAnyObject<TDerived>);
+
+ auto&& vtable = any.GetVTable();
+
+ YT_VERIFY(vtable.IsValid());
+
+ auto* functor = vtable.template GetFunctor<TVTableTag{}>();
+
+ return functor(std::forward<TReplaced>(any).GetStorage(), std::forward<TArgs>(args)...);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDetail
diff --git a/library/cpp/yt/memory/unittests/type_erasure_ut.cpp b/library/cpp/yt/memory/unittests/type_erasure_ut.cpp
new file mode 100644
index 0000000000..e6e917d975
--- /dev/null
+++ b/library/cpp/yt/memory/unittests/type_erasure_ut.cpp
@@ -0,0 +1,333 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/memory/type_erasure.h>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTestCpo1
+ : public TTagInvokeCpoBase<TTestCpo1>
+{ };
+
+inline constexpr TTestCpo1 TestCpo = {};
+
+struct TCustomized
+{
+ int Value = 42;
+
+ friend int TagInvoke(TTagInvokeTag<TestCpo>, const TCustomized& this_)
+ {
+ return this_.Value + 1;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// 2 Cpos won't trigger static vtable
+static_assert(
+ sizeof(
+ TAnyRef<
+ TOverload<
+ TestCpo,
+ void(TErasedThis&)>,
+ TOverload<
+ TestCpo,
+ void(TErasedThis&&)>>)
+ == 8 + 2 * 8);
+
+// 3 Cpos trigger static vtable
+static_assert(
+ sizeof(
+ TAnyRef<
+ TOverload<
+ TestCpo,
+ void(TErasedThis&)>,
+ TOverload<
+ TestCpo,
+ void(TErasedThis&&)>,
+ TOverload<
+ TestCpo,
+ void(TErasedThis, int)>>)
+ == 8 + 8);
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TAnyRefTest, JustWorks)
+{
+ using TAnyRef = TAnyRef<TOverload<TestCpo, int(const TErasedThis&)>>;
+ TCustomized concrete{.Value = 42};
+
+ TAnyRef any{concrete};
+ EXPECT_EQ(TestCpo(any), 43);
+
+ auto copy = any;
+ EXPECT_EQ(TestCpo(copy), 43);
+
+ auto movedOut = std::move(any);
+ EXPECT_EQ(TestCpo(movedOut), 43);
+}
+
+TEST(TAnyRefTest, EmptyRef)
+{
+ TCustomized concrete{.Value = 11};
+
+ TAnyRef<> any{concrete};
+
+ static_assert(!std::invocable<TTagInvokeTag<TestCpo>, decltype(any)>);
+ const auto& conc = any.AnyCast<TCustomized>();
+ EXPECT_EQ(conc.Value, 11);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TNoCopy
+{
+ TNoCopy() = default;
+
+ TNoCopy(const TNoCopy&) = delete;
+
+ TNoCopy(TNoCopy&&)
+ { }
+
+ TNoCopy& operator=(TNoCopy&&)
+ {
+ return *this;
+ }
+
+ int Val = 123;
+};
+
+static_assert(std::movable<TNoCopy>);
+
+struct TCustomized2
+{
+ int Value = 1;
+ static inline int DtorCount = 0;
+
+ TCustomized2() = default;
+
+ TCustomized2(const TCustomized2&)
+ { }
+
+ TCustomized2& operator=(const TCustomized2&)
+ {
+ return *this;
+ }
+
+ TCustomized2(TCustomized2&& other)
+ : Value(other.Value)
+ {
+ other.Value = -1;
+ }
+
+ TCustomized2& operator=(TCustomized2&& other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+
+ Value = std::exchange(other.Value, -1);
+ return *this;
+ }
+
+ ~TCustomized2()
+ {
+ ++DtorCount;
+ }
+
+ friend const TNoCopy& TagInvoke(TTagInvokeTag<TestCpo>, TCustomized2&)
+ {
+ static TNoCopy noCp;
+ noCp.Val = 11;
+ return noCp;
+ }
+
+ friend int TagInvoke(TTagInvokeTag<TestCpo>, TCustomized2&& this_)
+ {
+ auto v = std::move(this_);
+
+ return 1212;
+ }
+
+ friend int TagInvoke(TTagInvokeTag<TestCpo>, TCustomized2&, int)
+ {
+ return 42;
+ }
+};
+
+static_assert(std::copyable<TCustomized2>);
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TAnyRefTest, CvRefCorrectness)
+{
+ using TAnyRef = TAnyRef<
+ TOverload<TestCpo, const TNoCopy&(TErasedThis&)>,
+ TOverload<TestCpo, int(TErasedThis&&)>>;
+
+ TCustomized2 cust = {};
+
+ cust.DtorCount = 0;
+ EXPECT_EQ(cust.Value, 1);
+
+ {
+ TAnyRef ref{cust};
+
+ EXPECT_EQ(TestCpo(ref).Val, 11);
+ }
+
+ EXPECT_EQ(cust.DtorCount, 0);
+ EXPECT_EQ(cust.Value, 1);
+
+ {
+ TAnyRef ref(cust);
+
+ EXPECT_EQ(TestCpo(std::move(ref)), 1212);
+ }
+
+ EXPECT_EQ(cust.DtorCount, 1);
+ EXPECT_EQ(cust.Value, -1);
+
+ cust.Value = 1;
+ TAnyRef any{cust};
+ {
+ TAnyRef copy{any};
+ TAnyRef movedOut{std::move(any)};
+ EXPECT_EQ(TestCpo(copy).Val, 11);
+ EXPECT_EQ(TestCpo(movedOut).Val, 11);
+ }
+
+ EXPECT_EQ(cust.DtorCount, 1);
+ EXPECT_EQ(cust.Value, 1);
+}
+
+TEST(TAnyRefTest, StaticVTableForAnyRef)
+{
+ using TAnyRef = TAnyRef<
+ TOverload<TestCpo, const TNoCopy&(TErasedThis&)>,
+ TOverload<TestCpo, int(TErasedThis&&)>,
+ TOverload<TestCpo, int(TErasedThis&, int)>
+ >;
+
+ TCustomized2 cst = {};
+ cst.Value = 1111;
+ cst.DtorCount = 0;
+
+ TAnyRef any{cst};
+ {
+ TAnyRef copy{any};
+ TAnyRef movedOut{std::move(any)};
+ EXPECT_EQ(TestCpo(copy).Val, 11);
+ EXPECT_EQ(TestCpo(movedOut).Val, 11);
+ }
+ EXPECT_EQ(cst.Value, 1111);
+ EXPECT_EQ(cst.DtorCount, 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TAnyObjectTest, JustWorks)
+{
+ TCustomized concrete{.Value = 42};
+
+ TAnyObject<TOverload<TestCpo, int(const TErasedThis&)>> any{concrete};
+ EXPECT_EQ(TestCpo(any), 43);
+
+ auto copy = any;
+ EXPECT_EQ(TestCpo(copy), 43);
+
+ auto movedOut = std::move(any);
+ EXPECT_EQ(TestCpo(movedOut), 43);
+}
+
+TEST(TAnyObjectTest, EmptyAny)
+{
+ TCustomized concrete{.Value = 11};
+
+ TAnyObject<> any{concrete};
+
+ static_assert(!std::invocable<TTagInvokeTag<TestCpo>, decltype(any)>);
+ const auto& conc = any.AnyCast<TCustomized>();
+ EXPECT_EQ(conc.Value, 11);
+}
+
+TEST(TAnyObjectTest, CvRefCorrectness)
+{
+ using TAnyObject = TAnyObject<
+ TOverload<TestCpo, const TNoCopy&(TErasedThis&)>,
+ TOverload<TestCpo, int(TErasedThis&&)>>;
+
+ TCustomized2 cust = {};
+
+ cust.DtorCount = 0;
+ EXPECT_EQ(cust.Value, 1);
+
+ {
+ TAnyObject any{cust};
+
+ EXPECT_EQ(TestCpo(any).Val, 11);
+ }
+
+ // Any object itself.
+ EXPECT_EQ(cust.DtorCount, 1);
+ EXPECT_EQ(cust.Value, 1);
+
+ {
+ TAnyObject any(cust);
+
+ EXPECT_EQ(TestCpo(std::move(any)), 1212);
+ }
+
+ // Second any object + moved out object.
+ EXPECT_EQ(cust.DtorCount, 3);
+ EXPECT_EQ(cust.Value, 1);
+
+ TAnyObject any{cust};
+ {
+ TAnyObject copy{any};
+ TAnyObject movedOut{std::move(any)};
+ EXPECT_EQ(TestCpo(copy).Val, 11);
+ EXPECT_EQ(TestCpo(movedOut).Val, 11);
+ }
+
+ EXPECT_EQ(cust.DtorCount, 5);
+ EXPECT_THROW(any.AnyCast<TCustomized2>(), NDetail::TBadAnyCast);
+}
+
+TEST(TAnyObjectTest, StaticVTableForAnyRef)
+{
+ using TAnyObject = TAnyObject<
+ TOverload<TestCpo, const TNoCopy&(TErasedThis&)>,
+ TOverload<TestCpo, int(TErasedThis&&)>,
+ TOverload<TestCpo, int(TErasedThis&, int)>
+ >;
+
+ TCustomized2 cst = {};
+ cst.Value = 1111;
+ cst.DtorCount = 0;
+
+ TAnyObject any{cst};
+ {
+ TAnyObject copy{any};
+ TAnyObject movedOut{std::move(any)};
+ EXPECT_EQ(TestCpo(copy).Val, 11);
+ EXPECT_EQ(TestCpo(movedOut).Val, 11);
+ }
+ EXPECT_EQ(cst.Value, 1111);
+ EXPECT_EQ(cst.DtorCount, 2);
+ EXPECT_FALSE(any.IsValid());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TAnyObjectTest, AnyMoveOnly)
+{
+ TAnyUnique<> any{std::in_place_type<TNoCopy>};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/memory/unittests/ya.make b/library/cpp/yt/memory/unittests/ya.make
index 1a61a130ba..708586d651 100644
--- a/library/cpp/yt/memory/unittests/ya.make
+++ b/library/cpp/yt/memory/unittests/ya.make
@@ -15,6 +15,7 @@ SRCS(
weak_ptr_ut.cpp
ref_ut.cpp
range_protobuf_repeated_field_ut.cpp
+ type_erasure_ut.cpp
)
IF (NOT OS_WINDOWS)