summaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/compact_containers
diff options
context:
space:
mode:
authorbabenko <[email protected]>2025-01-12 08:43:05 +0300
committerbabenko <[email protected]>2025-01-12 09:02:29 +0300
commit6065cf56d7ca8909c36e1f5c38daac25b2e584da (patch)
tree0f0842364b043541a144ceff0b9ca45cb46fc329 /library/cpp/yt/compact_containers
parentf05fb708c73e4f5a484e9546c92a9ae8c5e799e8 (diff)
YT-18571: library/cpp/yt/small_containers -> library/cpp/yt/compact_containers
commit_hash:fc31d2770ebeffeb513c4535bd146c731b7f78fb
Diffstat (limited to 'library/cpp/yt/compact_containers')
-rw-r--r--library/cpp/yt/compact_containers/compact_flat_map-inl.h253
-rw-r--r--library/cpp/yt/compact_containers/compact_flat_map.h134
-rw-r--r--library/cpp/yt/compact_containers/compact_heap-inl.h152
-rw-r--r--library/cpp/yt/compact_containers/compact_heap.h75
-rw-r--r--library/cpp/yt/compact_containers/compact_queue-inl.h71
-rw-r--r--library/cpp/yt/compact_containers/compact_queue.h37
-rw-r--r--library/cpp/yt/compact_containers/compact_set-inl.h334
-rw-r--r--library/cpp/yt/compact_containers/compact_set.h92
-rw-r--r--library/cpp/yt/compact_containers/compact_vector-inl.h1026
-rw-r--r--library/cpp/yt/compact_containers/compact_vector.h230
-rw-r--r--library/cpp/yt/compact_containers/unittests/compact_flat_map_ut.cpp285
-rw-r--r--library/cpp/yt/compact_containers/unittests/compact_heap_ut.cpp108
-rw-r--r--library/cpp/yt/compact_containers/unittests/compact_queue_ut.cpp114
-rw-r--r--library/cpp/yt/compact_containers/unittests/compact_set_ut.cpp211
-rw-r--r--library/cpp/yt/compact_containers/unittests/compact_vector_ut.cpp1097
-rw-r--r--library/cpp/yt/compact_containers/unittests/ya.make18
-rw-r--r--library/cpp/yt/compact_containers/ya.make23
17 files changed, 4260 insertions, 0 deletions
diff --git a/library/cpp/yt/compact_containers/compact_flat_map-inl.h b/library/cpp/yt/compact_containers/compact_flat_map-inl.h
new file mode 100644
index 00000000000..e781569d9da
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_flat_map-inl.h
@@ -0,0 +1,253 @@
+#ifndef COMPACT_FLAT_MAP_INL_H_
+#error "Direct inclusion of this file is not allowed, include compact_flat_map.h"
+// For the sake of sane code completion.
+#include "compact_flat_map.h"
+#endif
+
+namespace NYT {
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <class TInputIterator>
+TCompactFlatMap<TKey, TValue, N, TKeyCompare>::TCompactFlatMap(TInputIterator begin, TInputIterator end)
+{
+ insert(begin, end);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+TCompactFlatMap<TKey, TValue, N, TKeyCompare>::TCompactFlatMap(std::initializer_list<value_type> values)
+ : TCompactFlatMap<TKey, TValue, N, TKeyCompare>(values.begin(), values.end())
+{ }
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+bool TCompactFlatMap<TKey, TValue, N, TKeyCompare>::operator==(const TCompactFlatMap& rhs) const
+{
+ return Storage_ == rhs.Storage_;
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+bool TCompactFlatMap<TKey, TValue, N, TKeyCompare>::operator!=(const TCompactFlatMap& rhs) const
+{
+ return !(*this == rhs);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::begin()
+{
+ return Storage_.begin();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::begin() const
+{
+ return Storage_.begin();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::cbegin() const
+{
+ return Storage_.begin();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::end()
+{
+ return Storage_.end();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::end() const
+{
+ return Storage_.end();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::cend() const
+{
+ return Storage_.end();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::reserve(size_type n)
+{
+ Storage_.reserve(n);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::size_type TCompactFlatMap<TKey, TValue, N, TKeyCompare>::size() const
+{
+ return Storage_.size();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+int TCompactFlatMap<TKey, TValue, N, TKeyCompare>::ssize() const
+{
+ return static_cast<int>(Storage_.size());
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+bool TCompactFlatMap<TKey, TValue, N, TKeyCompare>::empty() const
+{
+ return Storage_.empty();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::clear()
+{
+ Storage_.clear();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::shrink_to_small()
+{
+ Storage_.shrink_to_small();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::find(const TOtherKey& k)
+{
+ auto [rangeBegin, rangeEnd] = equal_range(k);
+ return rangeBegin == rangeEnd ? end() : rangeBegin;
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::find(const TOtherKey& k) const
+{
+ auto [rangeBegin, rangeEnd] = equal_range(k);
+ return rangeBegin == rangeEnd ? end() : rangeBegin;
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+bool TCompactFlatMap<TKey, TValue, N, TKeyCompare>::contains(const TOtherKey& k) const
+{
+ return find(k) != end();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+auto TCompactFlatMap<TKey, TValue, N, TKeyCompare>::insert(const value_type& value) -> std::pair<iterator, bool>
+{
+ return do_insert(value);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+auto TCompactFlatMap<TKey, TValue, N, TKeyCompare>::insert(value_type&& value) -> std::pair<iterator, bool>
+{
+ return do_insert(std::move(value));
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <class TArg>
+auto TCompactFlatMap<TKey, TValue, N, TKeyCompare>::do_insert(TArg&& value) -> std::pair<iterator, bool>
+{
+ auto [rangeBegin, rangeEnd] = equal_range(value.first);
+ if (rangeBegin != rangeEnd) {
+ return {rangeBegin, false};
+ } else {
+ auto it = Storage_.insert(rangeBegin, std::forward<TArg>(value));
+ return {it, true};
+ }
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <class TInputIterator>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::insert(TInputIterator begin, TInputIterator end)
+{
+ for (auto it = begin; it != end; ++it) {
+ insert(*it);
+ }
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <class... TArgs>
+auto TCompactFlatMap<TKey, TValue, N, TKeyCompare>::emplace(TArgs&&... args) -> std::pair<iterator, bool>
+{
+ return insert(value_type(std::forward<TArgs>(args)...));
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+TValue& TCompactFlatMap<TKey, TValue, N, TKeyCompare>::operator[](const TKey& k)
+{
+ auto [it, inserted] = insert({k, TValue()});
+ return it->second;
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::erase(const TKey& k)
+{
+ auto [rangeBegin, rangeEnd] = equal_range(k);
+ erase(rangeBegin, rangeEnd);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::erase(iterator pos)
+{
+ Storage_.erase(pos);
+
+ // Try to keep the storage inline. This is why erase doesn't return an iterator.
+ Storage_.shrink_to_small();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+void TCompactFlatMap<TKey, TValue, N, TKeyCompare>::erase(iterator b, iterator e)
+{
+ Storage_.erase(b, e);
+
+ // Try to keep the storage inline. This is why erase doesn't return an iterator.
+ Storage_.shrink_to_small();
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+std::pair<typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator, typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator>
+TCompactFlatMap<TKey, TValue, N, TKeyCompare>::equal_range(const TOtherKey& k)
+{
+ auto result = std::ranges::equal_range(Storage_, k, {}, &value_type::first);
+ YT_ASSERT(result.size() <= 1);
+ return result;
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+std::pair<typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator, typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator>
+TCompactFlatMap<TKey, TValue, N, TKeyCompare>::equal_range(const TOtherKey& k) const
+{
+ auto result = std::ranges::equal_range(Storage_, k, {}, &value_type::first);
+ YT_ASSERT(result.size() <= 1);
+ return result;
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::lower_bound(const TOtherKey& k) const
+{
+ return std::ranges::lower_bound(Storage_, k, {}, &value_type::first);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::lower_bound(const TOtherKey& k)
+{
+ return std::ranges::lower_bound(Storage_, k, {}, &value_type::first);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::const_iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::upper_bound(const TOtherKey& k) const
+{
+ return std::ranges::upper_bound(Storage_, k, {}, &value_type::first);
+}
+
+template <class TKey, class TValue, size_t N, class TKeyCompare>
+template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+typename TCompactFlatMap<TKey, TValue, N, TKeyCompare>::iterator TCompactFlatMap<TKey, TValue, N, TKeyCompare>::upper_bound(const TOtherKey& k)
+{
+ return std::ranges::upper_bound(Storage_, k, {}, &value_type::first);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/compact_flat_map.h b/library/cpp/yt/compact_containers/compact_flat_map.h
new file mode 100644
index 00000000000..d4e93668430
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_flat_map.h
@@ -0,0 +1,134 @@
+#pragma once
+
+#include "compact_vector.h"
+
+namespace NYT {
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <typename T>
+concept CHasIsTransparentFlag = requires {
+ typename T::is_transparent;
+};
+
+template <typename T, typename U, typename TCompare>
+concept CComparisonAllowed = std::same_as<T, U> || CHasIsTransparentFlag<TCompare>;
+
+} // namespace NDetail
+
+///////////////////////////////////////////////////////////////////////////////
+
+//! A flat map implementation over TCompactTValueector that tries to keep data inline.
+/*!
+ * Similarly to SmallSet, this is implemented via binary search over a sorted
+ * vector. Unlike SmallSet, however, this one never falls back to std::map (or
+ * set) for larger sizes. This means that the flat map is only useful
+ * - at small sizes, when there's absolutely no chance of it getting big, or
+ * - when it's filled once and is then only read from.
+ *
+ * In return, the flat map provides
+ * - a smaller size overhead and
+ * - a guarantee that if data fits into inline storage, it goes there.
+ *
+ * Because of the latter, one should be very careful with iterators: virtually
+ * any call to insert or erase may potentially invalidate all iterators.
+ */
+template <class TKey, class TValue, size_t N, class TKeyCompare = std::ranges::less>
+class TCompactFlatMap
+{
+public:
+ // NB: can't make this pair<const TKey, TValue> as TCompactTValueector requires its type
+ // parameter to be copy-assignable.
+ using value_type = std::pair<TKey, TValue>;
+ using key_type = TKey;
+ using mapped_type = TValue;
+ using key_compare = TKeyCompare;
+
+private:
+ using TStorage = TCompactVector<value_type, N>;
+
+public:
+ using iterator = typename TStorage::iterator;
+ using const_iterator = typename TStorage::const_iterator;
+ using size_type = size_t;
+
+ TCompactFlatMap() = default;
+
+ template <class TInputIterator>
+ TCompactFlatMap(TInputIterator begin, TInputIterator end);
+
+ TCompactFlatMap(std::initializer_list<value_type> values);
+
+ bool operator==(const TCompactFlatMap& rhs) const;
+ bool operator!=(const TCompactFlatMap& rhs) const;
+
+ iterator begin();
+ const_iterator begin() const;
+ const_iterator cbegin() const;
+
+ iterator end();
+ const_iterator end() const;
+ const_iterator cend() const;
+
+ void reserve(size_type n);
+
+ size_type size() const;
+ int ssize() const;
+
+ [[nodiscard]] bool empty() const;
+ void clear();
+
+ void shrink_to_small();
+
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ iterator find(const TOtherKey& k);
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ const_iterator find(const TOtherKey& k) const;
+
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ iterator lower_bound(const TOtherKey& k);
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ const_iterator lower_bound(const TOtherKey& k) const;
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ iterator upper_bound(const TOtherKey& k);
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ const_iterator upper_bound(const TOtherKey& k) const;
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ std::pair<iterator, iterator> equal_range(const TOtherKey& k);
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ std::pair<const_iterator, const_iterator> equal_range(const TOtherKey& k) const;
+
+ template <NDetail::CComparisonAllowed<TKey, TKeyCompare> TOtherKey>
+ bool contains(const TOtherKey& k) const;
+
+ std::pair<iterator, bool> insert(const value_type& value);
+ std::pair<iterator, bool> insert(value_type&& value);
+
+ template <class TInputIterator>
+ void insert(TInputIterator begin, TInputIterator end);
+
+ template <class... TArgs>
+ std::pair<iterator, bool> emplace(TArgs&&... args);
+
+ TValue& operator[](const TKey& k);
+
+ void erase(const TKey& k);
+ void erase(iterator pos);
+ void erase(iterator b, iterator e);
+
+private:
+ TStorage Storage_;
+
+ template <class TArg>
+ std::pair<iterator, bool> do_insert(TArg&& value);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define COMPACT_FLAT_MAP_INL_H_
+#include "compact_flat_map-inl.h"
+#undef COMPACT_FLAT_MAP_INL_H_
diff --git a/library/cpp/yt/compact_containers/compact_heap-inl.h b/library/cpp/yt/compact_containers/compact_heap-inl.h
new file mode 100644
index 00000000000..2c6b3507ba5
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_heap-inl.h
@@ -0,0 +1,152 @@
+#ifndef COMPACT_HEAP_INL_H_
+#error "Direct inclusion of this file is not allowed, include compact_heap.h"
+// For the sake of sane code completion.
+#include "compact_heap.h"
+#endif
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <algorithm>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N, class C>
+TCompactHeap<T, N, C>::TCompactHeap(C comparator) noexcept
+ : Comparator_(TReverseComparator(std::move(comparator)))
+{ }
+
+template <class T, size_t N, class C>
+void TCompactHeap<T, N, C>::push(T value)
+{
+ bool wasInline = IsInline();
+ Heap_.push_back(std::move(value));
+ if (Y_UNLIKELY(!IsInline())) {
+ if (wasInline) {
+ std::make_heap(Heap_.begin(), Heap_.end(), Comparator_);
+ } else {
+ std::push_heap(Heap_.begin(), Heap_.end(), Comparator_);
+ }
+ }
+}
+
+template <class T, size_t N, class C>
+void TCompactHeap<T, N, C>::pop()
+{
+ YT_ASSERT(!empty());
+
+ if (Y_LIKELY(IsInline())) {
+ auto minIt = std::max_element(Heap_.begin(), Heap_.end(), Comparator_);
+ std::swap(*minIt, Heap_.back());
+ Heap_.pop_back();
+ } else {
+ std::pop_heap(Heap_.begin(), Heap_.end(), Comparator_);
+ Heap_.pop_back();
+ }
+}
+
+template <class T, size_t N, class C>
+auto TCompactHeap<T, N, C>::get_min() const -> const_reference
+{
+ YT_ASSERT(!empty());
+
+ if (Y_LIKELY(IsInline())) {
+ return *std::max_element(Heap_.begin(), Heap_.end(), Comparator_);
+ } else {
+ return Heap_.front();
+ }
+}
+
+template <class T, size_t N, class C>
+auto TCompactHeap<T, N, C>::extract_min() -> value_type
+{
+ YT_ASSERT(!empty());
+
+ if (Y_LIKELY(IsInline())) {
+ auto minIt = std::max_element(Heap_.begin(), Heap_.end(), Comparator_);
+ std::swap(*minIt, Heap_.back());
+ auto value = Heap_.back();
+ Heap_.pop_back();
+
+ return value;
+ } else {
+ std::pop_heap(Heap_.begin(), Heap_.end(), Comparator_);
+ auto value = std::move(Heap_.back());
+ Heap_.pop_back();
+
+ return value;
+ }
+}
+
+template <class T, size_t N, class C>
+auto TCompactHeap<T, N, C>::begin() const -> const_iterator
+{
+ return Heap_.begin();
+}
+
+template <class T, size_t N, class C>
+auto TCompactHeap<T, N, C>::end() const -> const_iterator
+{
+ return Heap_.end();
+}
+
+template <class T, size_t N, class C>
+void TCompactHeap<T, N, C>::swap(TCompactHeap<T, N, C>& other)
+{
+ Heap_.swap(other.Heap_);
+ std::swap(Comparator_, other.Comparator_);
+}
+
+template <class T, size_t N, class C>
+size_t TCompactHeap<T, N, C>::size() const
+{
+ return Heap_.size();
+}
+
+template <class T, size_t N, class C>
+size_t TCompactHeap<T, N, C>::capacity() const
+{
+ return Heap_.capacity();
+}
+
+template <class T, size_t N, class C>
+size_t TCompactHeap<T, N, C>::max_size() const
+{
+ return Heap_.max_size();
+}
+
+template <class T, size_t N, class C>
+bool TCompactHeap<T, N, C>::empty() const
+{
+ return Heap_.empty();
+}
+
+template <class T, size_t N, class C>
+void TCompactHeap<T, N, C>::shrink_to_small()
+{
+ Heap_.shrink_to_small();
+}
+
+template <class T, size_t N, class C>
+bool TCompactHeap<T, N, C>::IsInline() const
+{
+ return Heap_.capacity() == N;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N, class C>
+TCompactHeap<T, N, C>::TReverseComparator::TReverseComparator(C underlying)
+ : Underlying_(std::move(underlying))
+{ }
+
+template <class T, size_t N, class C>
+bool TCompactHeap<T, N, C>::TReverseComparator::operator()(const T& lhs, const T& rhs) const
+{
+ return Underlying_(rhs, lhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/compact_heap.h b/library/cpp/yt/compact_containers/compact_heap.h
new file mode 100644
index 00000000000..951b36962d7
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_heap.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "compact_vector.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A heap structure optimized for storing elements inline
+//! and with little memory overhead. See TCompactVector.
+/*!
+ * When inline, uses linear search for selecting minimum
+ * instead of storing heap.
+ */
+template <class T, size_t N, class C = std::less<T>>
+class TCompactHeap
+{
+public:
+ static_assert(N <= 8, "TCompactHeap is not optimal for large N");
+
+ explicit TCompactHeap(C comparator = C()) noexcept;
+
+ using value_type = T;
+
+ using const_reference = const T&;
+
+ using const_iterator = const T*;
+
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+
+ void push(T value);
+ void pop();
+
+ const_reference get_min() const;
+ value_type extract_min();
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ void swap(TCompactHeap<T, N, C>& other);
+
+ size_t size() const;
+ size_t capacity() const;
+ size_t max_size() const;
+
+ bool empty() const;
+
+ void shrink_to_small();
+
+private:
+ TCompactVector<T, N> Heap_;
+
+ class TReverseComparator
+ {
+ public:
+ explicit TReverseComparator(C underlying);
+
+ bool operator()(const T& lhs, const T& rhs) const;
+
+ private:
+ C Underlying_;
+ };
+ TReverseComparator Comparator_;
+
+ bool IsInline() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define COMPACT_HEAP_INL_H_
+#include "compact_heap-inl.h"
+#undef COMPACT_HEAP_INL_H_
diff --git a/library/cpp/yt/compact_containers/compact_queue-inl.h b/library/cpp/yt/compact_containers/compact_queue-inl.h
new file mode 100644
index 00000000000..6e085ab260a
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_queue-inl.h
@@ -0,0 +1,71 @@
+#include "compact_queue.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N>
+void TCompactQueue<T, N>::Push(T value)
+{
+ if (Size_ == Queue_.size()) {
+ auto oldSize = Queue_.size();
+ Queue_.resize(2 * oldSize);
+
+ if (FrontIndex_ + Size_ > oldSize) {
+ std::move(
+ Queue_.begin(),
+ Queue_.begin() + FrontIndex_,
+ Queue_.begin() + Size_);
+ }
+ }
+
+ auto index = FrontIndex_ + Size_;
+ if (index >= Queue_.size()) {
+ index -= Queue_.size();
+ }
+ Queue_[index] = std::move(value);
+ ++Size_;
+}
+
+template <class T, size_t N>
+T TCompactQueue<T, N>::Pop()
+{
+ YT_VERIFY(!Empty());
+
+ auto value = std::move(Queue_[FrontIndex_]);
+ ++FrontIndex_;
+ if (FrontIndex_ >= Queue_.size()) {
+ FrontIndex_ -= Queue_.size();
+ }
+ --Size_;
+
+ return value;
+}
+
+template <class T, size_t N>
+const T& TCompactQueue<T, N>::Front() const
+{
+ return Queue_[FrontIndex_];
+}
+
+template <class T, size_t N>
+size_t TCompactQueue<T, N>::Size() const
+{
+ return Size_;
+}
+
+template <class T, size_t N>
+size_t TCompactQueue<T, N>::Capacity() const
+{
+ return Queue_.capacity();
+}
+
+template <class T, size_t N>
+bool TCompactQueue<T, N>::Empty() const
+{
+ return Size_ == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/compact_queue.h b/library/cpp/yt/compact_containers/compact_queue.h
new file mode 100644
index 00000000000..1852d297065
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_queue.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "compact_vector.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A queue optimized for storing elements inline
+//! and with little memory overhead. See TCompactVector.
+template <class T, size_t N>
+class TCompactQueue
+{
+public:
+ void Push(T value);
+ T Pop();
+
+ const T& Front() const;
+
+ size_t Size() const;
+ size_t Capacity() const;
+
+ bool Empty() const;
+
+private:
+ TCompactVector<T, N> Queue_ = TCompactVector<T, N>(N);
+ size_t FrontIndex_ = 0;
+ size_t Size_ = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define COMPACT_QUEUE_INL_H_
+#include "compact_queue-inl.h"
+#undef COMPACT_QUEUE_INL_H_
diff --git a/library/cpp/yt/compact_containers/compact_set-inl.h b/library/cpp/yt/compact_containers/compact_set-inl.h
new file mode 100644
index 00000000000..20736f50833
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_set-inl.h
@@ -0,0 +1,334 @@
+#ifndef COMPACT_SET_INL_H_
+#error "Direct inclusion of this file is not allowed, include compact_set.h"
+// For the sake of sane code completion.
+#include "compact_set.h"
+#endif
+
+#include <library/cpp/yt/assert/assert.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T, size_t N, typename C, typename A>
+class TCompactSet<T, N, C, A>::const_iterator
+{
+private:
+ friend class TCompactSet<T, N, C, A>;
+
+ union
+ {
+ TVectorConstIterator VIter;
+ TSetConstIterator SIter;
+ };
+
+ bool Small;
+
+ const_iterator(TVectorConstIterator it)
+ : VIter(it)
+ , Small(true)
+ { }
+
+ const_iterator(TSetConstIterator it)
+ : SIter(it)
+ , Small(false)
+ { }
+
+ template <typename TOther>
+ void ConstructFrom(TOther&& rhs)
+ {
+ YT_ASSERT(Small == rhs.Small);
+
+ if (Small) {
+ new (&VIter)TVectorConstIterator(std::forward<TOther>(rhs).VIter);
+ } else {
+ new (&SIter)TSetConstIterator(std::forward<TOther>(rhs).SIter);
+ }
+ }
+
+ template <typename TOther>
+ const_iterator& AssignFrom(TOther&& rhs)
+ {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ if (Small && rhs.Small) {
+ VIter = std::forward<TOther>(rhs).VIter;
+ } else if (!Small && !rhs.Small) {
+ SIter = std::forward<TOther>(rhs).SIter;
+ } else {
+ if (Small) {
+ VIter.~TVectorConstIterator();
+ } else {
+ SIter.~TSetConstIterator();
+ }
+
+ if (rhs.Small) {
+ new (&VIter)TVectorConstIterator(std::forward<TOther>(rhs).VIter);
+ } else {
+ new (&SIter)TSetConstIterator(std::forward<TOther>(rhs).SIter);
+ }
+ }
+
+ Small = rhs.Small;
+
+ return *this;
+ }
+
+public:
+ static_assert(std::is_same_v<
+ typename std::iterator_traits<TVectorConstIterator>::difference_type,
+ typename std::iterator_traits<TSetConstIterator>::difference_type>);
+ static_assert(std::is_same_v<
+ typename std::iterator_traits<TVectorConstIterator>::value_type,
+ typename std::iterator_traits<TSetConstIterator>::value_type>);
+ static_assert(std::is_same_v<
+ typename std::iterator_traits<TVectorConstIterator>::pointer,
+ typename std::iterator_traits<TSetConstIterator>::pointer>);
+ static_assert(std::is_same_v<
+ typename std::iterator_traits<TVectorConstIterator>::reference,
+ typename std::iterator_traits<TSetConstIterator>::reference>);
+
+ using difference_type = typename std::iterator_traits<TVectorConstIterator>::difference_type;
+ using value_type = typename std::iterator_traits<TVectorConstIterator>::value_type;
+ using pointer = typename std::iterator_traits<TVectorConstIterator>::pointer;
+ using reference = typename std::iterator_traits<TVectorConstIterator>::reference;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ const_iterator(const const_iterator& rhs)
+ : Small(rhs.Small)
+ {
+ ConstructFrom(rhs);
+ }
+
+ const_iterator(const_iterator&& rhs)
+ : Small(rhs.Small)
+ {
+ ConstructFrom(std::move(rhs));
+ }
+
+ ~const_iterator()
+ {
+ if (Small) {
+ VIter.~TVectorConstIterator();
+ } else {
+ SIter.~TSetConstIterator();
+ }
+ }
+
+ const_iterator& operator=(const const_iterator& rhs)
+ {
+ return AssignFrom(rhs);
+ }
+
+ const_iterator& operator=(const_iterator&& rhs)
+ {
+ return AssignFrom(std::move(rhs));
+ }
+
+ const_iterator& operator++()
+ {
+ if (Small) {
+ ++VIter;
+ } else {
+ ++SIter;
+ }
+
+ return *this;
+ }
+
+ const_iterator operator++(int)
+ {
+ auto result = *this;
+
+ if (Small) {
+ ++VIter;
+ } else {
+ ++SIter;
+ }
+
+ return result;
+ }
+
+ const_iterator& operator--()
+ {
+ if (Small) {
+ --VIter;
+ } else {
+ --SIter;
+ }
+
+ return *this;
+ }
+
+ const_iterator operator--(int)
+ {
+ auto result = *this;
+
+ if (Small) {
+ --VIter;
+ } else {
+ --SIter;
+ }
+
+ return result;
+ }
+
+ bool operator==(const const_iterator& rhs) const
+ {
+ if (Small != rhs.Small) {
+ return false;
+ }
+
+ return Small ? (VIter == rhs.VIter) : (SIter == rhs.SIter);
+ }
+
+ bool operator!=(const const_iterator& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ const T& operator*() const
+ {
+ return Small ? *VIter : *SIter;
+ }
+
+ const T* operator->() const
+ {
+ return &operator*();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T, size_t N, typename C, typename A>
+TCompactSet<T, N, C, A>::TCompactSet(const A& allocator)
+ : Set_(allocator)
+{ }
+
+template <typename T, size_t N, typename C, typename A>
+bool TCompactSet<T, N, C, A>::empty() const
+{
+ return Vector_.empty() && Set_.empty();
+}
+
+template <typename T, size_t N, typename C, typename A>
+typename TCompactSet<T, N, C, A>::size_type TCompactSet<T, N, C, A>::size() const
+{
+ return IsSmall() ? Vector_.size() : Set_.size();
+}
+
+template <typename T, size_t N, typename C, typename A>
+const T& TCompactSet<T, N, C, A>::front() const
+{
+ return IsSmall() ? Vector_.front() : *Set_.begin();
+}
+
+template <typename T, size_t N, typename C, typename A>
+typename TCompactSet<T, N, C, A>::size_type TCompactSet<T, N, C, A>::count(const T& v) const
+{
+ if (IsSmall()) {
+ return std::binary_search(Vector_.begin(), Vector_.end(), v, C()) ? 1 : 0;
+ } else {
+ return Set_.count(v);
+ }
+}
+
+template <typename T, size_t N, typename C, typename A>
+bool TCompactSet<T, N, C, A>::contains(const T& v) const
+{
+ return count(v) == 1;
+}
+
+template <typename T, size_t N, typename C, typename A>
+std::pair<typename TCompactSet<T, N, C, A>::const_iterator, bool> TCompactSet<T, N, C, A>::insert(const T& v)
+{
+ if (!IsSmall()) {
+ auto [it, inserted] = Set_.insert(v);
+ return {const_iterator(std::move(it)), inserted};
+ }
+
+ auto it = std::lower_bound(Vector_.begin(), Vector_.end(), v, C());
+ if (it != Vector_.end() && !C()(v, *it)) {
+ return {const_iterator(std::move(it)), false}; // Don't reinsert if it already exists.
+ }
+
+ if (Vector_.size() < N) {
+ auto newIt = Vector_.insert(it, v);
+ return {const_iterator(std::move(newIt)), true};
+ }
+
+ Set_.insert(Vector_.begin(), Vector_.end());
+ Vector_.clear();
+
+ auto [newIt, inserted] = Set_.insert(v);
+ YT_ASSERT(inserted);
+ return {const_iterator(std::move(newIt)), true};
+}
+
+template <typename T, size_t N, typename C, typename A>
+template <typename TIter>
+void TCompactSet<T, N, C, A>::insert(TIter i, TIter e)
+{
+ for (; i != e; ++i) {
+ insert(*i);
+ }
+}
+
+template <typename T, size_t N, typename C, typename A>
+bool TCompactSet<T, N, C, A>::erase(const T& v)
+{
+ if (!IsSmall()) {
+ return Set_.erase(v);
+ }
+
+ auto [rangeBegin, rangeEnd] = std::equal_range(Vector_.begin(), Vector_.end(), v, C());
+ if (rangeBegin != rangeEnd) {
+ Vector_.erase(rangeBegin, rangeEnd);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+template <typename T, size_t N, typename C, typename A>
+void TCompactSet<T, N, C, A>::clear()
+{
+ Vector_.clear();
+ Set_.clear();
+}
+
+template <typename T, size_t N, typename C, typename A>
+typename TCompactSet<T, N, C, A>::const_iterator TCompactSet<T, N, C, A>::begin() const
+{
+ return IsSmall() ? const_iterator(Vector_.begin()) : const_iterator(Set_.begin());
+}
+
+template <typename T, size_t N, typename C, typename A>
+typename TCompactSet<T, N, C, A>::const_iterator TCompactSet<T, N, C, A>::cbegin() const
+{
+ return begin();
+}
+
+template <typename T, size_t N, typename C, typename A>
+typename TCompactSet<T, N, C, A>::const_iterator TCompactSet<T, N, C, A>::end() const
+{
+ return IsSmall() ? const_iterator(Vector_.end()) : const_iterator(Set_.end());
+}
+
+template <typename T, size_t N, typename C, typename A>
+typename TCompactSet<T, N, C, A>::const_iterator TCompactSet<T, N, C, A>::cend() const
+{
+ return end();
+}
+
+template <typename T, size_t N, typename C, typename A>
+bool TCompactSet<T, N, C, A>::IsSmall() const
+{
+ return Set_.empty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/compact_set.h b/library/cpp/yt/compact_containers/compact_set.h
new file mode 100644
index 00000000000..354fad195cd
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_set.h
@@ -0,0 +1,92 @@
+// This is based on the following:
+//===- llvm/ADT/SmallSet.h - 'Normally small' sets --------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the CompactSet class.
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "compact_vector.h"
+
+#include <cstddef>
+#include <iterator>
+#include <set>
+#include <type_traits>
+
+namespace NYT {
+
+///////////////////////////////////////////////////////////////////////////////
+
+//! Maintains a set of unique values, optimizing for the case
+//! when the set is small (less than N).
+/*!
+ * In this case, the set can be maintained with no mallocs.
+ * If the set gets large, we expand to using an
+ * std::set to maintain reasonable lookup times.
+ *
+ * Note that any modification of the set may invalidate *all* iterators.
+ */
+template <typename T, size_t N, typename C = std::less<T>, typename A = std::allocator<T>>
+class TCompactSet
+{
+public:
+ class const_iterator;
+ using size_type = std::size_t;
+ using key_type = T;
+
+ TCompactSet() = default;
+ TCompactSet(const A& allocator);
+
+ [[nodiscard]] bool empty() const;
+
+ size_type size() const;
+
+ const T& front() const;
+
+ size_type count(const T& v) const;
+
+ bool contains(const T& v) const;
+
+ std::pair<const_iterator, bool> insert(const T& v);
+
+ template <typename TIter>
+ void insert(TIter i, TIter e);
+
+ bool erase(const T& v);
+
+ void clear();
+
+ const_iterator begin() const;
+ const_iterator cbegin() const;
+
+ const_iterator end() const;
+ const_iterator cend() const;
+
+private:
+ // Use a TCompactVector to hold the elements here (even though it will never
+ // reach its 'large' stage) to avoid calling the default ctors of elements
+ // we will never use.
+ TCompactVector<T, N> Vector_;
+ std::set<T, C, A> Set_;
+
+ using TSetConstIterator = typename std::set<T, C, A>::const_iterator;
+ using TVectorConstIterator = typename TCompactVector<T, N>::const_iterator;
+
+ bool IsSmall() const;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define COMPACT_SET_INL_H_
+#include "compact_set-inl.h"
+#undef COMPACT_SET_INL_H_
diff --git a/library/cpp/yt/compact_containers/compact_vector-inl.h b/library/cpp/yt/compact_containers/compact_vector-inl.h
new file mode 100644
index 00000000000..3bb9576d3ea
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_vector-inl.h
@@ -0,0 +1,1026 @@
+#ifndef COMPACT_VECTOR_INL_H_
+#error "Direct inclusion of this file is not allowed, include compact_vector.h"
+// For the sake of sane code completion.
+#include "compact_vector.h"
+#endif
+#undef COMPACT_VECTOR_INL_H_
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <library/cpp/yt/malloc/malloc.h>
+
+#include <library/cpp/yt/misc/hash.h>
+
+#include <util/system/compiler.h>
+
+#include <algorithm>
+#include <bit>
+
+#include <string.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+static_assert(sizeof(uintptr_t) == 8);
+static_assert(std::endian::native == std::endian::little);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TCompactVectorOnHeapStorage
+{
+ T* End;
+ T* Capacity;
+ T Elements[0];
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TVector, class TPtr>
+class TCompactVectorReallocationPtrAdjuster
+{
+public:
+ TCompactVectorReallocationPtrAdjuster(TVector* vector, TPtr& ptr)
+ : Vector_(vector)
+ , Ptr_(ptr)
+ , Index_(ptr >= Vector_->begin() && ptr <= Vector_->end()
+ ? std::distance(Vector_->begin(), const_cast<typename TVector::iterator>(ptr))
+ : -1)
+ { }
+
+ ~TCompactVectorReallocationPtrAdjuster()
+ {
+ if (Index_ >= 0) {
+ Ptr_ = Vector_->begin() + Index_;
+ }
+ }
+
+private:
+ TVector* const Vector_;
+ TPtr& Ptr_;
+ const ptrdiff_t Index_;
+};
+
+template <class TVector>
+class TCompactVectorReallocationPtrAdjuster<TVector, std::nullptr_t>
+{
+public:
+ TCompactVectorReallocationPtrAdjuster(TVector* /*vector*/, std::nullptr_t /*ptr*/)
+ { }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N>
+TCompactVector<T, N>::TCompactVector() noexcept
+{
+ InlineMeta_.SizePlusOne = 1;
+}
+
+template <class T, size_t N>
+TCompactVector<T, N>::TCompactVector(const TCompactVector& other)
+ : TCompactVector()
+{
+ assign(other.begin(), other.end());
+}
+
+template <class T, size_t N>
+template <size_t OtherN>
+TCompactVector<T, N>::TCompactVector(const TCompactVector<T, OtherN>& other)
+ : TCompactVector()
+{
+ assign(other.begin(), other.end());
+}
+
+template <class T, size_t N>
+TCompactVector<T, N>::TCompactVector(TCompactVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
+ : TCompactVector()
+{
+ swap(other);
+}
+
+template <class T, size_t N>
+template <size_t OtherN>
+TCompactVector<T, N>::TCompactVector(TCompactVector<T, OtherN>&& other)
+ : TCompactVector()
+{
+ swap(other);
+}
+
+template <class T, size_t N>
+TCompactVector<T, N>::TCompactVector(size_type count)
+ : TCompactVector()
+{
+ assign(count, T());
+}
+
+template <class T, size_t N>
+TCompactVector<T, N>::TCompactVector(size_type count, const T& value)
+ : TCompactVector()
+{
+ assign(count, value);
+}
+
+template <class T, size_t N>
+template <class TIterator>
+TCompactVector<T, N>::TCompactVector(TIterator first, TIterator last)
+ : TCompactVector()
+{
+ assign(first, last);
+}
+
+template <class T, size_t N>
+TCompactVector<T, N>::TCompactVector(std::initializer_list<T> list)
+ : TCompactVector()
+{
+ assign(list.begin(), list.end());
+}
+
+template <class T, size_t N>
+TCompactVector<T, N>::~TCompactVector()
+{
+ if (Y_LIKELY(IsInline())) {
+ Destroy(&InlineElements_[0], &InlineElements_[InlineMeta_.SizePlusOne - 1]);
+ } else {
+ auto* storage = OnHeapMeta_.Storage;
+ Destroy(storage->Elements, storage->End);
+ ::free(storage);
+ }
+}
+
+template <class T, size_t N>
+bool TCompactVector<T, N>::empty() const
+{
+ if (Y_LIKELY(IsInline())) {
+ return InlineMeta_.SizePlusOne == 1;
+ } else {
+ const auto* storage = OnHeapMeta_.Storage;
+ return storage->Elements == storage->End;
+ }
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::begin() -> iterator
+{
+ return Y_LIKELY(IsInline()) ? &InlineElements_[0] : OnHeapMeta_.Storage->Elements;
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::begin() const -> const_iterator
+{
+ return const_cast<TCompactVector*>(this)->begin();
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::end() -> iterator
+{
+ return Y_LIKELY(IsInline()) ? &InlineElements_[InlineMeta_.SizePlusOne - 1] : OnHeapMeta_.Storage->End;
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::end() const -> const_iterator
+{
+ return const_cast<TCompactVector*>(this)->end();
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::rbegin() -> reverse_iterator
+{
+ return static_cast<reverse_iterator>(end());
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::rbegin() const -> const_reverse_iterator
+{
+ return static_cast<const_reverse_iterator>(end());
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::rend() -> reverse_iterator
+{
+ return static_cast<reverse_iterator>(begin());
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::rend() const -> const_reverse_iterator
+{
+ return static_cast<const_reverse_iterator>(begin());
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::size() const -> size_type
+{
+ if (Y_LIKELY(IsInline())) {
+ return InlineMeta_.SizePlusOne - 1;
+ } else {
+ const auto* storage = OnHeapMeta_.Storage;
+ return storage->End - storage->Elements;
+ }
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::capacity() const -> size_type
+{
+ if (Y_LIKELY(IsInline())) {
+ return N;
+ } else {
+ const auto* storage = OnHeapMeta_.Storage;
+ return storage->Capacity - storage->Elements;
+ }
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::max_size() const -> size_type
+{
+ return static_cast<size_type>(-1) / sizeof(T);
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::data() -> pointer
+{
+ return static_cast<pointer>(begin());
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::data() const -> const_pointer
+{
+ return static_cast<const_pointer>(begin());
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::operator[](size_type index) -> reference
+{
+ YT_ASSERT(index < size());
+ return begin()[index];
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::operator[](size_type index) const -> const_reference
+{
+ return const_cast<TCompactVector*>(this)->operator[](index);
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::front() -> reference
+{
+ YT_ASSERT(!empty());
+ return begin()[0];
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::front() const -> const_reference
+{
+ return const_cast<TCompactVector*>(this)->front();
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::back() -> reference
+{
+ YT_ASSERT(!empty());
+ return end()[-1];
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::back() const -> const_reference
+{
+ return const_cast<TCompactVector*>(this)->back();
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::push_back(const T& value)
+{
+ PushBackImpl(
+ &value,
+ [&] (T* dst, const T* value) {
+ ::new(dst) T(*value);
+ });
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::push_back(T&& value)
+{
+ PushBackImpl(
+ &value,
+ [&] (T* dst, T* value) {
+ ::new(dst) T(std::move(*value));
+ });
+}
+
+template <class T, size_t N>
+template <class... TArgs>
+auto TCompactVector<T, N>::emplace(const_iterator pos, TArgs&&... args) -> iterator
+{
+ return InsertOneImpl(
+ pos,
+ nullptr,
+ [&] (auto* dst, std::nullptr_t) {
+ ::new(dst) T(std::forward<TArgs>(args)...);
+ },
+ [&] (auto* dst, std::nullptr_t) {
+ *dst = T(std::forward<TArgs>(args)...);
+ });
+}
+
+template <class T, size_t N>
+template <class... TArgs>
+auto TCompactVector<T, N>::emplace_back(TArgs&&... args) -> reference
+{
+ return PushBackImpl(
+ nullptr,
+ [&] (T* dst, std::nullptr_t) {
+ ::new(dst) T(std::forward<TArgs>(args)...);
+ });
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::pop_back()
+{
+ YT_ASSERT(!empty());
+
+ if (Y_LIKELY(IsInline())) {
+ InlineElements_[InlineMeta_.SizePlusOne - 2].T::~T();
+ --InlineMeta_.SizePlusOne;
+ } else {
+ auto* storage = OnHeapMeta_.Storage;
+ storage->End[-1].T::~T();
+ --storage->End;
+ }
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::erase(const_iterator pos) -> iterator
+{
+ YT_ASSERT(pos >= begin());
+ YT_ASSERT(pos < end());
+
+ auto* mutablePos = const_cast<iterator>(pos);
+ Move(mutablePos + 1, end(), mutablePos);
+ pop_back();
+
+ return mutablePos;
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::erase(const_iterator first, const_iterator last) -> iterator
+{
+ YT_ASSERT(first >= begin());
+ YT_ASSERT(last <= end());
+
+ auto* mutableFirst = const_cast<iterator>(first);
+ auto* mutableLast = const_cast<iterator>(last);
+ auto count = std::distance(mutableFirst, mutableLast);
+
+ if (Y_LIKELY(IsInline())) {
+ auto* end = &InlineElements_[0] + InlineMeta_.SizePlusOne - 1;
+ Move(mutableLast, end, mutableFirst);
+ Destroy(end - count, end);
+ InlineMeta_.SizePlusOne -= count;
+ } else {
+ auto* storage = OnHeapMeta_.Storage;
+ auto* end = storage->End;
+ Move(mutableLast, storage->End, mutableFirst);
+ Destroy(end - count, end);
+ storage->End -= count;
+ }
+
+ return mutableFirst;
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::clear()
+{
+ if (Y_LIKELY(IsInline())) {
+ Destroy(&InlineElements_[0], &InlineElements_[InlineMeta_.SizePlusOne - 1]);
+ InlineMeta_.SizePlusOne = 1;
+ } else {
+ auto* storage = OnHeapMeta_.Storage;
+ Destroy(storage->Elements, storage->End);
+ storage->End = storage->Elements;
+ }
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::resize(size_type newSize)
+{
+ ResizeImpl(
+ newSize,
+ [] (auto* dst) {
+ ::new(dst) T();
+ });
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::resize(size_type newSize, const T& value)
+{
+ ResizeImpl(
+ newSize,
+ [&] (auto* dst) {
+ ::new(dst) T(value);
+ });
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::reserve(size_t newCapacity)
+{
+ if (Y_UNLIKELY(newCapacity > N)) {
+ EnsureOnHeapCapacity(newCapacity, /*incremental*/ false);
+ }
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::swap(TCompactVector& other)
+{
+ if (this == &other) {
+ return;
+ }
+
+ if (!IsInline() && !other.IsInline()) {
+ std::swap(OnHeapMeta_.Storage, other.OnHeapMeta_.Storage);
+ return;
+ }
+
+ auto* lhs = this;
+ auto* rhs = &other;
+ if (lhs->size() < rhs->size()) {
+ std::swap(lhs, rhs);
+ }
+
+ size_t rhsSize = rhs->size();
+ size_t lhsSize = lhs->size();
+ if (lhsSize > rhs->capacity()) {
+ rhs->EnsureOnHeapCapacity(lhs->size(), /*incremental*/ false);
+ }
+
+ for (size_t index = 0; index < rhsSize; ++index) {
+ std::swap((*lhs)[index], (*rhs)[index]);
+ }
+
+ UninitializedMove(lhs->begin() + rhsSize, lhs->end(), rhs->end());
+ Destroy(lhs->begin() + rhsSize, lhs->end());
+
+ rhs->SetSize(lhsSize);
+ lhs->SetSize(rhsSize);
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::assign(size_type count, const T& value)
+{
+ clear();
+
+ if (Y_UNLIKELY(count > capacity())) {
+ EnsureOnHeapCapacity(count, /*incremental*/ false);
+ }
+
+ auto* dst = begin();
+ std::uninitialized_fill(dst, dst + count, value);
+
+ SetSize(count);
+}
+
+template <class T, size_t N>
+template <class TIterator>
+void TCompactVector<T, N>::assign(TIterator first, TIterator last)
+{
+ clear();
+
+ auto count = std::distance(first, last);
+ if (Y_UNLIKELY(count > static_cast<ptrdiff_t>(capacity()))) {
+ EnsureOnHeapCapacity(count, /*incremental*/ false);
+ }
+
+ std::uninitialized_copy(first, last, begin());
+
+ SetSize(count);
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::assign(std::initializer_list<T> list)
+{
+ assign(list.begin(), list.end());
+}
+
+template <class T, size_t N>
+template <size_t OtherN>
+void TCompactVector<T, N>::assign(const TCompactVector<T, OtherN>& other)
+{
+ if constexpr(N == OtherN) {
+ if (this == &other) {
+ return;
+ }
+ }
+
+ auto otherSize = other.size();
+ auto otherBegin = other.begin();
+
+ if (capacity() >= otherSize) {
+ const auto* src = other.begin();
+ auto* dst = begin();
+
+ auto thisSize = size();
+ auto copySize = std::min(thisSize, otherSize);
+ Copy(src, src + copySize, dst);
+ src += copySize;
+ dst += copySize;
+
+ auto uninitializedCopySize = otherSize - copySize;
+ UninitializedCopy(src, src + uninitializedCopySize, dst);
+ // NB: src += uninitializedCopySize is not needed.
+ dst += uninitializedCopySize;
+
+ if (thisSize > otherSize) {
+ Destroy(dst, end());
+ }
+
+ SetSize(otherSize);
+ return;
+ }
+
+ clear();
+
+ EnsureOnHeapCapacity(otherSize, /*incremental*/ false);
+
+ YT_ASSERT(!IsInline());
+ auto* storage = OnHeapMeta_.Storage;
+ UninitializedCopy(otherBegin, otherBegin + otherSize, storage->Elements);
+ storage->End = storage->Elements + otherSize;
+}
+
+template <class T, size_t N>
+template <size_t OtherN>
+void TCompactVector<T, N>::assign(TCompactVector<T, OtherN>&& other)
+{
+ if constexpr(N == OtherN) {
+ if (this == &other) {
+ return;
+ }
+ }
+
+ clear();
+
+ if (!other.IsInline()) {
+ if (Y_UNLIKELY(!IsInline())) {
+ ::free(OnHeapMeta_.Storage);
+ }
+ OnHeapMeta_.Storage = other.OnHeapMeta_.Storage;
+ other.InlineMeta_.SizePlusOne = 1;
+ return;
+ }
+
+ auto otherSize = other.size();
+ if (Y_UNLIKELY(otherSize > capacity())) {
+ EnsureOnHeapCapacity(otherSize, /*incremental*/ false);
+ }
+
+ auto* otherBegin = other.begin();
+ UninitializedMove(otherBegin, otherBegin + otherSize, begin());
+ SetSize(otherSize);
+
+ other.clear();
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::operator=(const TCompactVector& other) -> TCompactVector&
+{
+ assign(other);
+ return *this;
+}
+
+template <class T, size_t N>
+template <size_t OtherN>
+auto TCompactVector<T, N>::operator=(const TCompactVector<T, OtherN>& other) -> TCompactVector&
+{
+ assign(other);
+ return *this;
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::operator=(TCompactVector&& other) -> TCompactVector&
+{
+ assign(std::move(other));
+ return *this;
+}
+
+template <class T, size_t N>
+template <size_t OtherN>
+auto TCompactVector<T, N>::operator=(TCompactVector<T, OtherN>&& other) -> TCompactVector&
+{
+ assign(std::move(other));
+ return *this;
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::operator=(std::initializer_list<T> list) -> TCompactVector&
+{
+ assign(list);
+ return *this;
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::insert(const_iterator pos, const T& value) -> iterator
+{
+ return InsertOneImpl(
+ pos,
+ &value,
+ [&] (auto* dst, const auto* value) {
+ ::new(dst) T(*value);
+ },
+ [&] (auto* dst, const auto* value) {
+ *dst = *value;
+ });
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::insert(const_iterator pos, T&& value) -> iterator
+{
+ return InsertOneImpl(
+ pos,
+ &value,
+ [&] (auto* dst, auto* value) {
+ ::new(dst) T(std::move(*value));
+ },
+ [&] (auto* dst, auto* value) {
+ *dst = std::move(*value);
+ });
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::insert(const_iterator pos, size_type count, const T& value) -> iterator
+{
+ return InsertManyImpl(
+ pos,
+ count,
+ [&] (auto* dstFirst, auto* dstLast) {
+ for (auto* dst = dstFirst; dst != dstLast; ++dst) {
+ ::new(dst) T(value);
+ }
+ },
+ [&] (auto* dstFirst, auto* dstLast) {
+ for (auto* dst = dstFirst; dst != dstLast; ++dst) {
+ *dst = value;
+ }
+ });
+}
+
+template <class T, size_t N>
+template <class TIterator>
+auto TCompactVector<T, N>::insert(const_iterator pos, TIterator first, TIterator last) -> iterator
+{
+ auto current = first;
+ return InsertManyImpl(
+ pos,
+ std::distance(first, last),
+ [&] (auto* dstFirst, auto* dstLast) {
+ for (auto* dst = dstFirst; dst != dstLast; ++dst) {
+ ::new(dst) T(*current++);
+ }
+ },
+ [&] (auto* dstFirst, auto* dstLast) {
+ for (auto* dst = dstFirst; dst != dstLast; ++dst) {
+ *dst = *current++;
+ }
+ });
+}
+
+template <class T, size_t N>
+auto TCompactVector<T, N>::insert(const_iterator pos, std::initializer_list<T> list) -> iterator
+{
+ return insert(pos, list.begin(), list.end());
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::shrink_to_small()
+{
+ if (Y_LIKELY(IsInline())) {
+ return;
+ }
+
+ auto size = this->size();
+ if (size > N) {
+ return;
+ }
+
+ auto* storage = OnHeapMeta_.Storage;
+ UninitializedMove(storage->Elements, storage->End, &InlineElements_[0]);
+ Destroy(storage->Elements, storage->End);
+ ::free(storage);
+
+ InlineMeta_.SizePlusOne = size + 1;
+}
+
+template <class T, size_t N>
+bool TCompactVector<T, N>::IsInline() const
+{
+ return InlineMeta_.SizePlusOne != 0;
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::SetSize(size_t newSize)
+{
+ if (Y_LIKELY(IsInline())) {
+ InlineMeta_.SizePlusOne = newSize + 1;
+ } else {
+ auto* storage = OnHeapMeta_.Storage;
+ storage->End = storage->Elements + newSize;
+ }
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::EnsureOnHeapCapacity(size_t newCapacity, bool incremental)
+{
+ newCapacity = std::max(newCapacity, N + 1);
+ if (incremental) {
+ newCapacity = std::max(newCapacity, capacity() * 2);
+ }
+
+ auto byteSize = sizeof(TOnHeapStorage) + newCapacity * sizeof(T);
+ byteSize = nallocx(byteSize, 0);
+
+ newCapacity = (byteSize - sizeof(TOnHeapStorage)) / sizeof(T);
+
+ auto* newStorage = static_cast<TOnHeapStorage*>(::malloc(byteSize));
+ YT_VERIFY((reinterpret_cast<uintptr_t>(newStorage) >> 56) == 0);
+
+ newStorage->Capacity = newStorage->Elements + newCapacity;
+
+ size_t size;
+ if (IsInline()) {
+ size = InlineMeta_.SizePlusOne - 1;
+ UninitializedMove(&InlineElements_[0], &InlineElements_[0] + size, newStorage->Elements);
+ Destroy(&InlineElements_[0], &InlineElements_[0] + size);
+ } else {
+ auto* storage = OnHeapMeta_.Storage;
+ size = storage->End - storage->Elements;
+ UninitializedMove(storage->Elements, storage->End, newStorage->Elements);
+ Destroy(storage->Elements, storage->End);
+ ::free(storage);
+ }
+
+ newStorage->End = newStorage->Elements + size;
+ OnHeapMeta_.Storage = newStorage;
+}
+
+template <class T, size_t N>
+template <class TPtr, class F>
+auto TCompactVector<T, N>::PushBackImpl(TPtr valuePtr, F&& func) -> reference
+{
+ auto sizePlusOne = InlineMeta_.SizePlusOne;
+ if (Y_LIKELY(sizePlusOne != 0 && sizePlusOne != N + 1)) {
+ auto* dst = &InlineElements_[sizePlusOne - 1];
+ func(dst, valuePtr);
+ ++InlineMeta_.SizePlusOne;
+ return *dst;
+ }
+
+ auto hasSpareOnHeapCapacity = [&] {
+ if (sizePlusOne != 0) {
+ return false;
+ }
+ auto* storage = OnHeapMeta_.Storage;
+ return storage->End < storage->Capacity;
+ };
+
+ if (Y_UNLIKELY(!hasSpareOnHeapCapacity())) {
+ TCompactVectorReallocationPtrAdjuster<TCompactVector, TPtr> valuePtrAdjuster(this, valuePtr);
+ EnsureOnHeapCapacity(0, /*incremental*/ true);
+ }
+
+ YT_ASSERT(!IsInline());
+ auto* storage = OnHeapMeta_.Storage;
+ auto* dst = storage->End++;
+ func(dst, valuePtr);
+
+ return *dst;
+}
+
+template <class T, size_t N>
+template <class F>
+void TCompactVector<T, N>::ResizeImpl(size_type newSize, F&& func)
+{
+ auto size = this->size();
+ if (newSize > size) {
+ if (Y_UNLIKELY(newSize > capacity())) {
+ EnsureOnHeapCapacity(newSize, /*incremental*/ false);
+ }
+
+ auto* first = end();
+ auto* last = first + newSize - size;
+ for (auto* current = first; current != last; ++current) {
+ func(current);
+ }
+ } else if (newSize < size) {
+ Destroy(begin() + newSize, end());
+ }
+
+ SetSize(newSize);
+}
+
+template <class T, size_t N>
+template <class TPtr, class UninitializedF, class InitializedF>
+auto TCompactVector<T, N>::InsertOneImpl(const_iterator pos, TPtr valuePtr, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc) -> iterator
+{
+ YT_ASSERT(pos >= begin());
+ YT_ASSERT(pos <= end());
+
+ auto* mutablePos = const_cast<iterator>(pos);
+
+ auto newSize = size() + 1;
+ if (Y_UNLIKELY(newSize > capacity())) {
+ TCompactVectorReallocationPtrAdjuster<TCompactVector, iterator> mutablePosAdjuster(this, mutablePos);
+ TCompactVectorReallocationPtrAdjuster<TCompactVector, TPtr> valuePtrAdjuster(this, valuePtr);
+ EnsureOnHeapCapacity(newSize, /*incremental*/ true);
+ }
+
+ auto* end = this->end();
+
+ if constexpr(!std::is_same_v<TPtr, std::nullptr_t>) {
+ if (valuePtr >= mutablePos && valuePtr < end) {
+ ++valuePtr;
+ }
+ }
+
+ auto moveCount = std::distance(mutablePos, end);
+ if (moveCount == 0) {
+ uninitializedFunc(end, valuePtr);
+ } else {
+ if constexpr(std::is_trivially_copyable_v<T>) {
+ ::memmove(mutablePos + 1, mutablePos, moveCount * sizeof(T));
+ } else {
+ ::new(end) T(std::move(end[-1]));
+ MoveBackward(mutablePos, end - 1, mutablePos + 1);
+ }
+ initializedFunc(mutablePos, valuePtr);
+ }
+
+ SetSize(newSize);
+
+ return mutablePos;
+}
+
+template <class T, size_t N>
+template <class UninitializedF, class InitializedF>
+auto TCompactVector<T, N>::InsertManyImpl(const_iterator pos, size_t insertCount, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc) -> iterator
+{
+ YT_ASSERT(pos >= begin());
+ YT_ASSERT(pos <= end());
+
+ auto* mutablePos = const_cast<iterator>(pos);
+ if (insertCount == 0) {
+ return mutablePos;
+ }
+
+ auto size = this->size();
+ auto newSize = size + insertCount;
+ if (Y_UNLIKELY(newSize > capacity())) {
+ auto index = std::distance(begin(), mutablePos);
+ EnsureOnHeapCapacity(newSize, /*incremental*/ true);
+ mutablePos = begin() + index;
+ }
+
+ auto* end = this->end();
+ auto moveCount = std::distance(mutablePos, end);
+ if constexpr(std::is_trivially_copyable_v<T>) {
+ ::memmove(mutablePos + insertCount, mutablePos, moveCount * sizeof(T));
+ initializedFunc(mutablePos, mutablePos + insertCount);
+ } else {
+ if (static_cast<ptrdiff_t>(insertCount) >= moveCount) {
+ UninitializedMove(mutablePos, end, mutablePos + insertCount);
+ initializedFunc(mutablePos, end);
+ uninitializedFunc(end, end + insertCount - moveCount);
+ } else {
+ auto overlapCount = moveCount - insertCount;
+ UninitializedMove(mutablePos + overlapCount, end, mutablePos + overlapCount + insertCount);
+ MoveBackward(mutablePos, mutablePos + overlapCount, mutablePos + insertCount);
+ initializedFunc(mutablePos, mutablePos + insertCount);
+ }
+ }
+
+ SetSize(newSize);
+
+ return mutablePos;
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::Destroy(T* first, T* last)
+{
+ if constexpr(!std::is_trivially_destructible_v<T>) {
+ for (auto* current = first; current != last; ++current) {
+ current->T::~T();
+ }
+ }
+}
+
+template <class T, size_t N>
+template <class T1, class T2>
+void TCompactVector<T, N>::Copy(const T1* srcFirst, const T1* srcLast, T2* dst)
+{
+ if constexpr(std::is_trivially_copyable_v<T1> && std::is_same_v<T1, T2>) {
+ ::memcpy(dst, srcFirst, (srcLast - srcFirst) * sizeof(T));
+ } else {
+ std::copy(srcFirst, srcLast, dst);
+ }
+}
+
+template <class T, size_t N>
+template <class T1, class T2>
+void TCompactVector<T, N>::UninitializedCopy(const T1* srcFirst, const T1* srcLast, T2* dst)
+{
+ if constexpr(std::is_trivially_copyable_v<T1> && std::is_same_v<T1, T2>) {
+ ::memcpy(dst, srcFirst, (srcLast - srcFirst) * sizeof(T));
+ } else {
+ std::uninitialized_copy(srcFirst, srcLast, dst);
+ }
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::Move(T* srcFirst, T* srcLast, T* dst)
+{
+ if constexpr(std::is_trivially_copyable_v<T>) {
+ ::memmove(dst, srcFirst, (srcLast - srcFirst) * sizeof(T));
+ } else {
+ std::move(srcFirst, srcLast, dst);
+ }
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::UninitializedMove(T* srcFirst, T* srcLast, T* dst)
+{
+ if constexpr(std::is_trivially_copyable_v<T>) {
+ ::memcpy(dst, srcFirst, (srcLast - srcFirst) * sizeof(T));
+ } else {
+ std::uninitialized_move(srcFirst, srcLast, dst);
+ }
+}
+
+template <class T, size_t N>
+void TCompactVector<T, N>::MoveBackward(T* srcFirst, T* srcLast, T* dst)
+{
+ auto* src = srcLast;
+ dst += std::distance(srcFirst, srcLast);
+ while (src > srcFirst) {
+ *--dst = std::move(*--src);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t LhsN, size_t RhsN>
+bool operator==(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs)
+{
+ if constexpr(LhsN == RhsN) {
+ if (&lhs == &rhs) {
+ return true;
+ }
+ }
+
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+
+ return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+template <class T, size_t LhsN, size_t RhsN>
+bool operator!=(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs)
+{
+ return !(lhs == rhs);
+}
+
+template <class T, size_t LhsN, size_t RhsN>
+bool operator<(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs)
+{
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N>
+void swap(TCompactVector<T, N>& lhs, TCompactVector<T, N>& rhs) // NOLINT
+{
+ lhs.swap(rhs);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+namespace std {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N>
+struct hash<NYT::TCompactVector<T, N>>
+{
+ size_t operator()(const NYT::TCompactVector<T, N>& container) const
+ {
+ size_t result = 0;
+ for (const auto& element : container) {
+ NYT::HashCombine(result, element);
+ }
+ return result;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace std
diff --git a/library/cpp/yt/compact_containers/compact_vector.h b/library/cpp/yt/compact_containers/compact_vector.h
new file mode 100644
index 00000000000..07fd9f89244
--- /dev/null
+++ b/library/cpp/yt/compact_containers/compact_vector.h
@@ -0,0 +1,230 @@
+#pragma once
+
+#include <util/system/defaults.h>
+
+#include <cstdint>
+#include <iterator>
+#include <limits>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TCompactVectorOnHeapStorage;
+
+//! A vector-like structure optimized for storing elements inline
+//! and with little memory overhead.
+/*!
+ * Stores up to #N (<= 254) elements inline.
+ *
+ * When capacity starts exceeding #N, moves all elements to heap;
+ * \see #TCompactVectorOnHeapStorage.
+ *
+ * When linked with YTAlloc, employs its API to adjust the on-heap capacity in accordance
+ * to the actual size of the allocated region.
+ *
+ * Assuming the entropy and the alignment constraints, yields a seemingly optimal memory overhead.
+ * E.g. TCompactVector<uint8_t, 7> takes 8 bytes and TCompactVector<uint32_t, 3> takes 16 bytes.
+ * \see #ByteSize.
+ *
+ * Assumes (and asserts) the following:
+ * 1) the platform is 64 bit;
+ * 2) the highest 8 bits of pointers returned by |malloc| are zeroes;
+ * 3) the platform is little-endian.
+ */
+template <class T, size_t N>
+class TCompactVector
+{
+public:
+ static_assert(N < std::numeric_limits<uint8_t>::max());
+
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+
+ using value_type = T;
+
+ using iterator = T*;
+ using const_iterator = const T*;
+
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+
+ using reference = T&;
+ using const_reference = const T&;
+
+ using pointer = T*;
+ using const_pointer = const T*;
+
+ TCompactVector() noexcept;
+ TCompactVector(const TCompactVector& other);
+ template <size_t OtherN>
+ TCompactVector(const TCompactVector<T, OtherN>& other);
+ TCompactVector(TCompactVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>);
+ template <size_t OtherN>
+ TCompactVector(TCompactVector<T, OtherN>&& other);
+ explicit TCompactVector(size_type count);
+ TCompactVector(size_type count, const T& value);
+ template <class TIterator>
+ TCompactVector(TIterator first, TIterator last);
+ TCompactVector(std::initializer_list<T> list);
+
+ ~TCompactVector();
+
+ [[nodiscard]] bool empty() const;
+
+ iterator begin();
+ const_iterator begin() const;
+ iterator end();
+ const_iterator end() const;
+
+ reverse_iterator rbegin();
+ const_reverse_iterator rbegin() const;
+ reverse_iterator rend();
+ const_reverse_iterator rend() const;
+
+ size_type size() const;
+ size_type capacity() const;
+ size_type max_size() const;
+
+ pointer data();
+ const_pointer data() const;
+
+ reference operator[](size_type index);
+ const_reference operator[](size_type index) const;
+
+ reference front();
+ const_reference front() const;
+ reference back();
+ const_reference back() const;
+
+ void push_back(const T& value);
+ void push_back(T&& value);
+
+ template <class... TArgs>
+ iterator emplace(const_iterator pos, TArgs&&... args);
+ template <class... TArgs>
+ reference emplace_back(TArgs&&... args);
+
+ void pop_back();
+
+ iterator erase(const_iterator pos);
+ iterator erase(const_iterator first, const_iterator last);
+
+ void clear();
+
+ void resize(size_type newSize);
+ void resize(size_type newSize, const T& value);
+
+ void reserve(size_type newCapacity);
+
+ void swap(TCompactVector& other);
+
+ void assign(size_type count, const T& value);
+ template <class TIterator>
+ void assign(TIterator first, TIterator last);
+ void assign(std::initializer_list<T> list);
+ template <size_t OtherN>
+ void assign(const TCompactVector<T, OtherN>& other);
+ template <size_t OtherN>
+ void assign(TCompactVector<T, OtherN>&& other);
+
+ TCompactVector& operator=(const TCompactVector& other);
+ template <size_t OtherN>
+ TCompactVector& operator=(const TCompactVector<T, OtherN>& other);
+ TCompactVector& operator=(TCompactVector&& other);
+ template <size_t OtherN>
+ TCompactVector& operator=(TCompactVector<T, OtherN>&& other);
+ TCompactVector& operator=(std::initializer_list<T> list);
+
+ iterator insert(const_iterator pos, const T& value);
+ iterator insert(const_iterator pos, T&& value);
+ iterator insert(const_iterator pos, size_type count, const T& value);
+ template <class TIterator>
+ iterator insert(const_iterator pos, TIterator first, TIterator last);
+ iterator insert(const_iterator pos, std::initializer_list<T> list);
+
+ void shrink_to_small();
+
+private:
+ template <class OtherT, size_t OtherN>
+ friend class TCompactVector;
+
+ using TOnHeapStorage = TCompactVectorOnHeapStorage<T>;
+
+ static constexpr size_t ByteSize =
+ (sizeof(T) * N + alignof(T) + sizeof(uintptr_t) - 1) &
+ ~(sizeof(uintptr_t) - 1);
+
+ struct TInlineMeta
+ {
+ char Padding[ByteSize - sizeof(uint8_t)];
+ // > 0 indicates inline storage
+ // == 0 indicates on-heap storage
+ uint8_t SizePlusOne;
+ } alias_hack;
+
+ // TODO(aleexfi): Use [[no_unique_address]] when clang will support it on windows.
+ template <class = void>
+ struct TOnHeapMeta
+ {
+ char Padding[ByteSize - sizeof(uintptr_t)];
+ TOnHeapStorage* Storage;
+ } alias_hack;
+
+ template <class _>
+ requires (ByteSize == sizeof(uintptr_t))
+ struct TOnHeapMeta<_>
+ {
+ TOnHeapStorage* Storage;
+ } alias_hack;
+
+ static_assert(sizeof(TOnHeapMeta<>) == ByteSize);
+
+ union
+ {
+ T InlineElements_[N];
+ TInlineMeta InlineMeta_;
+ TOnHeapMeta<> OnHeapMeta_;
+ };
+
+ bool IsInline() const;
+ void SetSize(size_t newSize);
+ void EnsureOnHeapCapacity(size_t newCapacity, bool incremental);
+ template <class TPtr, class F>
+ reference PushBackImpl(TPtr valuePtr, F&& func);
+ template <class F>
+ void ResizeImpl(size_t newSize, F&& func);
+ template <class TPtr, class UninitializedF, class InitializedF>
+ iterator InsertOneImpl(const_iterator pos, TPtr valuePtr, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc);
+ template <class UninitializedF, class InitializedF>
+ iterator InsertManyImpl(const_iterator pos, size_t insertCount, UninitializedF&& uninitializedFunc, InitializedF&& initializedFunc);
+
+ static void Destroy(T* first, T* last);
+ template <class T1, class T2>
+ static void Copy(const T1* srcFirst, const T1* srcLast, T2* dst);
+ template <class T1, class T2>
+ static void UninitializedCopy(const T1* srcFirst, const T1* srcLast, T2* dst);
+ static void Move(T* srcFirst, T* srcLast, T* dst);
+ static void MoveBackward(T* srcFirst, T* srcLast, T* dst);
+ static void UninitializedMove(T* srcFirst, T* srcLast, T* dst);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t LhsN, size_t RhsN>
+bool operator==(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs);
+
+template <class T, size_t LhsN, size_t RhsN>
+bool operator!=(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs);
+
+template <class T, size_t LhsN, size_t RhsN>
+bool operator<(const TCompactVector<T, LhsN>& lhs, const TCompactVector<T, RhsN>& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define COMPACT_VECTOR_INL_H_
+#include "compact_vector-inl.h"
+#undef COMPACT_VECTOR_INL_H_
diff --git a/library/cpp/yt/compact_containers/unittests/compact_flat_map_ut.cpp b/library/cpp/yt/compact_containers/unittests/compact_flat_map_ut.cpp
new file mode 100644
index 00000000000..9683d2a267f
--- /dev/null
+++ b/library/cpp/yt/compact_containers/unittests/compact_flat_map_ut.cpp
@@ -0,0 +1,285 @@
+#include <library/cpp/yt/compact_containers/compact_flat_map.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TMap = TCompactFlatMap<std::string, std::string, 2>;
+
+TMap CreateMap()
+{
+ std::vector<std::pair<std::string, std::string>> data = {{"I", "met"}, {"a", "traveller"}, {"from", "an"}, {"antique", "land"}};
+ return {data.begin(), data.end()};
+}
+
+TEST(TCompactFlatMapTest, DefaultEmpty)
+{
+ TMap m;
+ EXPECT_TRUE(m.empty());
+ EXPECT_EQ(m.begin(), m.end());
+}
+
+TEST(TCompactFlatMapTest, Reserve)
+{
+ // No real way to test reserve - just use it and wiggle about.
+ auto m1 = CreateMap();
+ TMap m2;
+ m2.reserve(m1.size());
+ m2.insert(m1.begin(), m1.end());
+ EXPECT_EQ(m1.size(), m2.size());
+}
+
+TEST(TCompactFlatMapTest, Size)
+{
+ auto m = CreateMap();
+
+ EXPECT_EQ(m.size(), 4u);
+ EXPECT_EQ(m.ssize(), 4);
+
+ m.insert({"Who", "said"});
+
+ EXPECT_EQ(m.size(), 5u);
+ EXPECT_EQ(m.ssize(), 5);
+
+ m.erase("antique");
+
+ EXPECT_EQ(m.size(), 4u);
+ EXPECT_EQ(m.ssize(), 4);
+}
+
+TEST(TCompactFlatMapTest, ClearAndEmpty)
+{
+ auto m = CreateMap();
+
+ EXPECT_FALSE(m.empty());
+ EXPECT_NE(m.begin(), m.end());
+
+ m.clear();
+
+ EXPECT_TRUE(m.empty());
+ EXPECT_EQ(m.begin(), m.end());
+
+ m.insert({"Who", "said"});
+
+ EXPECT_FALSE(m.empty());
+ EXPECT_NE(m.begin(), m.end());
+}
+
+TEST(TCompactFlatMapTest, FindMutable)
+{
+ auto m = CreateMap();
+ {
+ auto it = m.find("from");
+ EXPECT_NE(it, m.end());
+ EXPECT_EQ(it->second, "an");
+ it->second = "the";
+ }
+ {
+ auto it = m.find("from");
+ EXPECT_NE(it, m.end());
+ EXPECT_EQ(it->second, "the");
+ }
+ {
+ auto it = m.find("Who");
+ EXPECT_EQ(it, m.end());
+ }
+}
+
+TEST(TCompactFlatMapTest, FindConst)
+{
+ const auto& m = CreateMap();
+ {
+ auto it = m.find("from");
+ EXPECT_NE(it, m.end());
+ EXPECT_EQ(it->second, "an");
+ }
+ {
+ auto it = m.find("Who");
+ EXPECT_EQ(it, m.end());
+ }
+}
+
+TEST(TCompactFlatMapTest, Insert)
+{
+ auto m = CreateMap();
+
+ auto [it, inserted] = m.insert({"Who", "said"});
+ EXPECT_TRUE(inserted);
+ EXPECT_EQ(m.ssize(), 5);
+ EXPECT_NE(it, m.end());
+ EXPECT_EQ(it, m.find("Who"));
+ EXPECT_EQ(it->second, "said");
+
+ auto [it2, inserted2] = m.insert({"Who", "told"});
+ EXPECT_FALSE(inserted2);
+ EXPECT_EQ(m.ssize(), 5);
+ EXPECT_EQ(it2, it);
+ EXPECT_EQ(it->second, "said");
+
+ std::vector<std::pair<std::string, std::string>> data = {{"Two", "vast"}, {"and", "trunkless"}, {"legs", "of"}, {"stone", "Stand"}, {"in", "the"}, {"desert", "..."}};
+ m.insert(data.begin(), data.end());
+ EXPECT_EQ(m.ssize(), 11);
+ EXPECT_NE(m.find("and"), m.end());
+ EXPECT_EQ(m.find("and")->second, "trunkless");
+}
+
+TEST(TCompactFlatMapTest, Emplace)
+{
+ auto m = CreateMap();
+
+ auto [it, inserted] = m.emplace("Far", "place");
+ EXPECT_TRUE(inserted);
+ EXPECT_EQ(m.ssize(), 5);
+ EXPECT_NE(it, m.end());
+ EXPECT_EQ(it, m.find("Far"));
+ EXPECT_EQ(it->second, "place");
+
+ auto [it2, inserted2] = m.emplace("Far", "behind");
+ EXPECT_FALSE(inserted2);
+ EXPECT_EQ(m.ssize(), 5);
+ EXPECT_EQ(it2, it);
+ EXPECT_EQ(it->second, "place");
+}
+
+TEST(TCompactFlatMapTest, Subscript)
+{
+ auto m = CreateMap();
+
+ EXPECT_EQ(m["antique"], "land");
+ EXPECT_EQ(m.ssize(), 4);
+
+ EXPECT_EQ(m["Who"], "");
+ EXPECT_EQ(m.ssize(), 5);
+}
+
+TEST(TCompactFlatMapTest, Erase)
+{
+ auto m = CreateMap();
+
+ m.erase("antique");
+ EXPECT_EQ(m.ssize(), 3);
+
+ m.erase("Who");
+ EXPECT_EQ(m.ssize(), 3);
+
+ m.erase(m.begin(), m.end());
+ EXPECT_TRUE(m.empty());
+}
+
+TEST(TCompactFlatMapTest, GrowShrink)
+{
+ TMap m;
+ m.insert({"Two", "vast"});
+ m.insert({"and", "trunkless"});
+ m.insert({"legs", "of"});
+ m.insert({"stone", "Stand"});
+ m.insert({"in", "the"});
+ m.insert({"desert", "..."});
+
+ m.erase("legs");
+ m.erase("stone");
+ m.erase("in");
+ m.erase("desert");
+
+ EXPECT_EQ(m.ssize(), 2);
+
+ // Must not crash or trigger asan.
+}
+
+TEST(TCompactFlatMapTest, GrowShrinkGrow)
+{
+ TMap m;
+ m.insert({"Two", "vast"});
+ m.insert({"and", "trunkless"});
+ m.insert({"legs", "of"});
+ m.insert({"stone", "Stand"});
+ m.insert({"in", "the"});
+ m.insert({"desert", "..."});
+
+ m.erase("legs");
+ m.erase("stone");
+ m.erase("in");
+ m.erase("desert");
+
+ EXPECT_EQ(m.ssize(), 2);
+
+ m.insert({"I", "met"});
+ m.insert({"a", "traveller"});
+ m.insert({"from", "an"});
+ m.insert({"antique", "land"});
+
+ EXPECT_EQ(m.ssize(), 6);
+
+ // Must not crash or trigger asan.
+}
+
+TEST(TCompactFlatMapTest, LowerBound)
+{
+ TMap m;
+ EXPECT_EQ(m.lower_bound("a"), m.end());
+
+ m.emplace("b", "value");
+ EXPECT_EQ(m.lower_bound("a")->second, "value");
+ EXPECT_EQ(m.lower_bound("b")->second, "value");
+
+ m.emplace("c", "value2");
+
+ // Grows here.
+ m.emplace("d", "value3");
+ EXPECT_EQ(m.lower_bound("a")->second, "value");
+ EXPECT_EQ(m.lower_bound("e"), m.end());
+}
+
+TEST(TCompactFlatMapTest, UpperBound)
+{
+ using TKeyValue = std::pair<std::string, std::string>;
+ TMap m;
+ EXPECT_EQ(m.upper_bound("a"), m.end());
+
+ m.emplace("b", "value");
+ EXPECT_EQ(m.upper_bound("a")->second, "value");
+ EXPECT_EQ(m.upper_bound("b"), m.end());
+
+ m.emplace("c", "value2");
+
+ // Grows here.
+ m.emplace("d", "value3");
+
+ EXPECT_EQ(*m.upper_bound("a"), TKeyValue("b", "value"));
+ EXPECT_EQ(*m.upper_bound("b"), TKeyValue("c", "value2"));
+ EXPECT_EQ(m.upper_bound("d"), m.end());
+}
+
+TEST(TCompactFlatMapTest, EqualRange)
+{
+ TMap m;
+ EXPECT_EQ(m.equal_range("a"), std::pair(m.end(), m.end()));
+
+ m.emplace("b", "value-b");
+ EXPECT_EQ(m.equal_range("a"), std::pair(m.begin(), m.begin()));
+ EXPECT_EQ(m.equal_range("b"), std::pair(m.begin(), m.end()));
+ EXPECT_EQ(m.equal_range("c"), std::pair(m.end(), m.end()));
+
+ m.emplace("c", "value-c");
+ m.emplace("d", "value-d");
+
+ auto it = m.begin();
+ EXPECT_EQ(m.equal_range("a"), std::pair(it, it));
+ EXPECT_EQ(m.equal_range("b"), std::pair(it, std::next(it)));
+ ++it;
+ EXPECT_EQ(m.equal_range("c"), std::pair(it, std::next(it)));
+ ++it;
+ EXPECT_EQ(m.equal_range("d"), std::pair(it, std::next(it)));
+ EXPECT_EQ(m.equal_range("e"), std::pair(m.end(), m.end()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/unittests/compact_heap_ut.cpp b/library/cpp/yt/compact_containers/unittests/compact_heap_ut.cpp
new file mode 100644
index 00000000000..4cd66443563
--- /dev/null
+++ b/library/cpp/yt/compact_containers/unittests/compact_heap_ut.cpp
@@ -0,0 +1,108 @@
+#include <library/cpp/yt/compact_containers/compact_heap.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <random>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(CompactHeapTest, Simple)
+{
+ TCompactHeap<int, 2> heap;
+ heap.push(3);
+ heap.push(1);
+ heap.push(2);
+
+ EXPECT_EQ(3u, heap.size());
+ EXPECT_EQ(1, heap.extract_min());
+
+ EXPECT_EQ(2, heap.get_min());
+ EXPECT_EQ(2, heap.extract_min());
+
+ EXPECT_EQ(3, heap.extract_min());
+
+ EXPECT_TRUE(heap.empty());
+}
+
+TEST(CompactHeapTest, SimpleComparator)
+{
+ TCompactHeap<int, 2, std::greater<int>> heap;
+ heap.push(3);
+ heap.push(1);
+ heap.push(2);
+
+ EXPECT_EQ(3u, heap.size());
+ EXPECT_EQ(3, heap.extract_min());
+ EXPECT_EQ(2, heap.get_min());
+ EXPECT_EQ(2, heap.extract_min());
+ EXPECT_EQ(1, heap.extract_min());
+ EXPECT_TRUE(heap.empty());
+}
+
+TEST(CompactHeapTest, Capacity)
+{
+ TCompactHeap<int, 2> heap;
+ EXPECT_EQ(2u, heap.capacity());
+ EXPECT_EQ(0u, heap.size());
+
+ for (int i = 0; i < 100; ++i) {
+ heap.push(i);
+ }
+ EXPECT_LE(100u, heap.capacity());
+ EXPECT_EQ(100u, heap.size());
+
+ for (int i = 0; i < 99; ++i) {
+ heap.pop();
+ }
+ EXPECT_LE(100u, heap.capacity());
+ EXPECT_EQ(1u, heap.size());
+
+ heap.shrink_to_small();
+
+ EXPECT_EQ(2u, heap.capacity());
+ EXPECT_EQ(1u, heap.size());
+}
+
+TEST(CompactHeapTest, Stress)
+{
+ TCompactHeap<int, 3, std::greater<int>> heap;
+ std::vector<int> values;
+
+ std::mt19937 rng(42);
+ for (int iteration = 0; iteration < 1000; ++iteration) {
+ EXPECT_EQ(values.size(), heap.size());
+ EXPECT_EQ(values.empty(), heap.empty());
+
+ {
+ std::vector<int> content(heap.begin(), heap.end());
+ std::sort(content.rbegin(), content.rend());
+ EXPECT_EQ(values, content);
+ }
+
+ if (!values.empty()) {
+ EXPECT_EQ(values[0], heap.get_min());
+ }
+
+ if (values.empty() || rng() % 2 == 0) {
+ int x = rng() % 100;
+ heap.push(x);
+ values.push_back(x);
+ std::sort(values.rbegin(), values.rend());
+ } else {
+ if (rng() % 2 == 0) {
+ EXPECT_EQ(values[0], heap.extract_min());
+ } else {
+ heap.pop();
+ }
+ values.erase(values.begin());
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/unittests/compact_queue_ut.cpp b/library/cpp/yt/compact_containers/unittests/compact_queue_ut.cpp
new file mode 100644
index 00000000000..463d4ec4970
--- /dev/null
+++ b/library/cpp/yt/compact_containers/unittests/compact_queue_ut.cpp
@@ -0,0 +1,114 @@
+#include <library/cpp/yt/compact_containers/compact_queue.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <queue>
+#include <random>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TCompactQueueTest, Simple)
+{
+ TCompactQueue<int, 4> queue;
+ queue.Push(1);
+ queue.Push(2);
+ queue.Push(3);
+
+ for (int i = 1; i <= 10; i++) {
+ EXPECT_EQ(queue.Size(), 3u);
+ EXPECT_EQ(queue.Front(), i);
+ EXPECT_EQ(queue.Pop(), i);
+ queue.Push(i + 3);
+ }
+
+ for (int i = 11; i <= 13; i++) {
+ EXPECT_EQ(queue.Front(), i);
+ queue.Pop();
+ }
+
+ EXPECT_TRUE(queue.Empty());
+}
+
+TEST(TCompactQueueTest, Reallocation1)
+{
+ TCompactQueue<int, 2> queue;
+ queue.Push(1);
+ queue.Push(2);
+ queue.Push(3);
+
+ for (int i = 1; i <= 10; i++) {
+ EXPECT_EQ(queue.Size(), 3u);
+ EXPECT_EQ(queue.Front(), i);
+ EXPECT_EQ(queue.Pop(), i);
+ queue.Push(i + 3);
+ }
+
+ for (int i = 11; i <= 13; i++) {
+ EXPECT_EQ(queue.Front(), i);
+ queue.Pop();
+ }
+
+ EXPECT_TRUE(queue.Empty());
+}
+
+TEST(TCompactQueueTest, Reallocation2)
+{
+ TCompactQueue<int, 3> queue;
+ queue.Push(1);
+ queue.Push(2);
+ queue.Push(3);
+ EXPECT_EQ(queue.Pop(), 1);
+ queue.Push(4);
+ queue.Push(5);
+
+ EXPECT_EQ(queue.Size(), 4u);
+
+ for (int i = 2; i <= 10; i++) {
+ EXPECT_EQ(queue.Size(), 4u);
+ EXPECT_EQ(queue.Front(), i);
+ EXPECT_EQ(queue.Pop(), i);
+ queue.Push(i + 4);
+ }
+
+ for (int i = 11; i <= 14; i++) {
+ EXPECT_EQ(queue.Front(), i);
+ queue.Pop();
+ }
+
+ EXPECT_TRUE(queue.Empty());
+}
+
+TEST(TCompactQueueTest, Stress)
+{
+ std::mt19937_64 rng(42);
+
+ for (int iteration = 0; iteration < 1000; ++iteration) {
+ TCompactQueue<int, 4> queue1;
+ std::queue<int> queue2;
+
+ for (int step = 0; step < 10'000; ++step) {
+ EXPECT_EQ(queue1.Size(), queue2.size());
+ EXPECT_EQ(queue1.Empty(), queue2.empty());
+ if (!queue1.Empty()) {
+ EXPECT_EQ(queue1.Front(), queue2.front());
+ }
+
+ if (queue2.empty() || rng() % 2 == 0) {
+ int value = rng() % 1'000'000;
+ queue1.Push(value);
+ queue2.push(value);
+ } else {
+ queue1.Pop();
+ queue2.pop();
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/unittests/compact_set_ut.cpp b/library/cpp/yt/compact_containers/unittests/compact_set_ut.cpp
new file mode 100644
index 00000000000..203a6f75f58
--- /dev/null
+++ b/library/cpp/yt/compact_containers/unittests/compact_set_ut.cpp
@@ -0,0 +1,211 @@
+//===- llvm/unittest/ADT/SmallSetTest.cpp ---------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// CompactSet unit tests.
+//
+//===----------------------------------------------------------------------===//
+
+#include <library/cpp/yt/compact_containers/compact_set.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <string>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TCompactSetTest, Insert)
+{
+ TCompactSet<int, 4> s1;
+
+ for (int i = 0; i < 4; i++)
+ s1.insert(i);
+
+ for (int i = 0; i < 4; i++)
+ s1.insert(i);
+
+ EXPECT_EQ(4u, s1.size());
+
+ for (int i = 0; i < 4; i++)
+ EXPECT_EQ(1u, s1.count(i));
+
+ EXPECT_EQ(0u, s1.count(4));
+}
+
+TEST(TCompactSetTest, Grow)
+{
+ TCompactSet<int, 4> s1;
+
+ for (int i = 0; i < 8; i++)
+ s1.insert(i);
+
+ EXPECT_EQ(8u, s1.size());
+
+ for (int i = 0; i < 8; i++)
+ EXPECT_EQ(1u, s1.count(i));
+
+ EXPECT_EQ(0u, s1.count(8));
+}
+
+TEST(TCompactSetTest, Erase)
+{
+ TCompactSet<int, 4> s1;
+
+ for (int i = 0; i < 8; i++)
+ s1.insert(i);
+
+ EXPECT_EQ(8u, s1.size());
+
+ // Remove elements one by one and check if all other elements are still there.
+ for (int i = 0; i < 8; i++) {
+ EXPECT_EQ(1u, s1.count(i));
+ EXPECT_TRUE(s1.erase(i));
+ EXPECT_EQ(0u, s1.count(i));
+ EXPECT_EQ(8u - i - 1, s1.size());
+ for (int j = i + 1; j < 8; j++)
+ EXPECT_EQ(1u, s1.count(j));
+ }
+
+ EXPECT_EQ(0u, s1.count(8));
+}
+
+TEST(TCompactSetTest, IteratorInt)
+{
+ TCompactSet<int, 4> s1;
+
+ // Test the 'small' case.
+ for (int i = 0; i < 3; i++)
+ s1.insert(i);
+
+ std::vector<int> V(s1.begin(), s1.end());
+ // Make sure the elements are in the expected order.
+ std::sort(V.begin(), V.end());
+ for (int i = 0; i < 3; i++)
+ EXPECT_EQ(i, V[i]);
+
+ // Test the 'big' case by adding a few more elements to switch to std::set
+ // internally.
+ for (int i = 3; i < 6; i++)
+ s1.insert(i);
+
+ V.assign(s1.begin(), s1.end());
+ // Make sure the elements are in the expected order.
+ std::sort(V.begin(), V.end());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i, V[i]);
+}
+
+TEST(TCompactSetTest, IteratorString)
+{
+ // Test CompactSetIterator for TCompactSet with a type with non-trivial
+ // ctors/dtors.
+ TCompactSet<std::string, 2> s1;
+
+ s1.insert("str 1");
+ s1.insert("str 2");
+ s1.insert("str 1");
+
+ std::vector<std::string> V(s1.begin(), s1.end());
+ std::sort(V.begin(), V.end());
+ EXPECT_EQ(2u, s1.size());
+ EXPECT_EQ("str 1", V[0]);
+ EXPECT_EQ("str 2", V[1]);
+
+ s1.insert("str 4");
+ s1.insert("str 0");
+ s1.insert("str 4");
+
+ V.assign(s1.begin(), s1.end());
+ // Make sure the elements are in the expected order.
+ std::sort(V.begin(), V.end());
+ EXPECT_EQ(4u, s1.size());
+ EXPECT_EQ("str 0", V[0]);
+ EXPECT_EQ("str 1", V[1]);
+ EXPECT_EQ("str 2", V[2]);
+ EXPECT_EQ("str 4", V[3]);
+}
+
+TEST(TCompactSetTest, IteratorIncMoveCopy)
+{
+ // Test CompactSetIterator for TCompactSet with a type with non-trivial
+ // ctors/dtors.
+ TCompactSet<std::string, 2> s1;
+
+ s1.insert("str 1");
+ s1.insert("str 2");
+
+ auto Iter = s1.begin();
+ EXPECT_EQ("str 1", *Iter);
+ ++Iter;
+ EXPECT_EQ("str 2", *Iter);
+
+ s1.insert("str 4");
+ s1.insert("str 0");
+ auto Iter2 = s1.begin();
+ Iter = std::move(Iter2);
+ EXPECT_EQ("str 0", *Iter);
+}
+
+// These test weren't taken from llvm.
+
+TEST(TCompactSetTest, Empty)
+{
+ TCompactSet<int, 10> v;
+ EXPECT_TRUE(v.empty());
+
+ auto data = {1, 2, 3, 4, 5};
+
+ v.insert(data.begin(), data.end()); // not crossing size threshold
+ v.erase(4);
+ v.erase(2);
+ v.erase(3);
+ v.erase(5);
+ v.erase(1);
+ EXPECT_TRUE(v.empty());
+
+ auto data2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ v.insert(data2.begin(), data2.end()); // crossing size threshold
+ v.erase(7);
+ v.erase(3);
+ v.erase(1);
+ v.erase(10);
+ v.erase(9);
+ v.erase(0);
+ v.erase(2);
+ v.erase(6);
+ v.erase(4);
+ v.erase(5);
+ v.erase(8);
+ EXPECT_TRUE(v.empty());
+}
+
+TEST(TCompactSetTest, ForEach)
+{
+ TCompactSet<int, 10> v;
+
+ auto data = {10, 9, 3, 4, 1, 5, 6, 8};
+
+ v.insert(data.begin(), data.end());
+
+ std::vector<int> buf(v.begin(), v.end());
+ std::vector<int> expected{1, 3, 4, 5, 6, 8, 9, 10};
+ EXPECT_EQ(expected, buf);
+
+ auto data2 = {7, 1, 2, 0};
+ v.insert(data2.begin(), data2.end());
+ buf.assign(v.begin(), v.end());
+ expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ EXPECT_EQ(expected, buf);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/unittests/compact_vector_ut.cpp b/library/cpp/yt/compact_containers/unittests/compact_vector_ut.cpp
new file mode 100644
index 00000000000..5ffc04efdc4
--- /dev/null
+++ b/library/cpp/yt/compact_containers/unittests/compact_vector_ut.cpp
@@ -0,0 +1,1097 @@
+// Adapted from the following:
+//===- llvm/unittest/ADT/CompactVectorTest.cpp ------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// TCompactVector unit tests.
+//
+//===----------------------------------------------------------------------===//
+
+#include <library/cpp/yt/compact_containers/compact_vector.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/string/cast.h>
+
+#include <algorithm>
+#include <list>
+
+#include <stdarg.h>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+/// A helper class that counts the total number of constructor and
+/// destructor calls.
+class Constructable {
+private:
+ static int numConstructorCalls;
+ static int numMoveConstructorCalls;
+ static int numCopyConstructorCalls;
+ static int numDestructorCalls;
+ static int numAssignmentCalls;
+ static int numMoveAssignmentCalls;
+ static int numCopyAssignmentCalls;
+
+ bool constructed;
+ int value;
+
+public:
+ Constructable() : constructed(true), value(0) {
+ ++numConstructorCalls;
+ }
+
+ Constructable(int val) : constructed(true), value(val) {
+ ++numConstructorCalls;
+ }
+
+ Constructable(const Constructable & src) : constructed(true) {
+ value = src.value;
+ ++numConstructorCalls;
+ ++numCopyConstructorCalls;
+ }
+
+ Constructable(Constructable && src) : constructed(true) {
+ value = src.value;
+ ++numConstructorCalls;
+ ++numMoveConstructorCalls;
+ }
+
+ ~Constructable() {
+ EXPECT_TRUE(constructed);
+ ++numDestructorCalls;
+ constructed = false;
+ }
+
+ Constructable & operator=(const Constructable & src) {
+ EXPECT_TRUE(constructed);
+ value = src.value;
+ ++numAssignmentCalls;
+ ++numCopyAssignmentCalls;
+ return *this;
+ }
+
+ Constructable & operator=(Constructable && src) {
+ EXPECT_TRUE(constructed);
+ value = src.value;
+ ++numAssignmentCalls;
+ ++numMoveAssignmentCalls;
+ return *this;
+ }
+
+ int getValue() const {
+ return abs(value);
+ }
+
+ static void reset() {
+ numConstructorCalls = 0;
+ numMoveConstructorCalls = 0;
+ numCopyConstructorCalls = 0;
+ numDestructorCalls = 0;
+ numAssignmentCalls = 0;
+ numMoveAssignmentCalls = 0;
+ numCopyAssignmentCalls = 0;
+ }
+
+ static int getNumConstructorCalls() {
+ return numConstructorCalls;
+ }
+
+ static int getNumMoveConstructorCalls() {
+ return numMoveConstructorCalls;
+ }
+
+ static int getNumCopyConstructorCalls() {
+ return numCopyConstructorCalls;
+ }
+
+ static int getNumDestructorCalls() {
+ return numDestructorCalls;
+ }
+
+ static int getNumAssignmentCalls() {
+ return numAssignmentCalls;
+ }
+
+ static int getNumMoveAssignmentCalls() {
+ return numMoveAssignmentCalls;
+ }
+
+ static int getNumCopyAssignmentCalls() {
+ return numCopyAssignmentCalls;
+ }
+
+ friend bool operator==(const Constructable & c0, const Constructable & c1) {
+ return c0.getValue() == c1.getValue();
+ }
+};
+
+int Constructable::numConstructorCalls;
+int Constructable::numCopyConstructorCalls;
+int Constructable::numMoveConstructorCalls;
+int Constructable::numDestructorCalls;
+int Constructable::numAssignmentCalls;
+int Constructable::numCopyAssignmentCalls;
+int Constructable::numMoveAssignmentCalls;
+
+struct NonCopyable {
+ NonCopyable() {}
+ NonCopyable(NonCopyable &&) {}
+ NonCopyable &operator=(NonCopyable &&) { return *this; }
+private:
+ NonCopyable(const NonCopyable &) = delete;
+ NonCopyable &operator=(const NonCopyable &) = delete;
+};
+
+[[maybe_unused]] void CompileTest() {
+ TCompactVector<NonCopyable, 0> V;
+ V.resize(42);
+}
+
+class CompactVectorTestBase : public testing::Test {
+protected:
+
+ void SetUp() override {
+ Constructable::reset();
+ }
+
+ template <typename VectorT>
+ void assertEmpty(VectorT & v) {
+ // Size tests
+ EXPECT_EQ(0u, v.size());
+ EXPECT_TRUE(v.empty());
+
+ // Iterator tests
+ EXPECT_TRUE(v.begin() == v.end());
+ }
+
+ // Assert that v contains the specified values, in order.
+ template <typename VectorT>
+ void assertValuesInOrder(VectorT & v, size_t size, ...) {
+ EXPECT_EQ(size, v.size());
+
+ va_list ap;
+ va_start(ap, size);
+ for (size_t i = 0; i < size; ++i) {
+ int value = va_arg(ap, int);
+ EXPECT_EQ(value, v[i].getValue());
+ }
+
+ va_end(ap);
+ }
+
+ // Generate a sequence of values to initialize the vector.
+ template <typename VectorT>
+ void makeSequence(VectorT & v, int start, int end) {
+ for (int i = start; i <= end; ++i) {
+ v.push_back(Constructable(i));
+ }
+ }
+};
+
+// Test fixture class
+template <typename VectorT>
+class CompactVectorTest : public CompactVectorTestBase {
+protected:
+ VectorT theVector;
+ VectorT otherVector;
+};
+
+
+using CompactVectorTestTypes = ::testing::Types<TCompactVector<Constructable, 0>,
+ TCompactVector<Constructable, 1>,
+ TCompactVector<Constructable, 2>,
+ TCompactVector<Constructable, 4>,
+ TCompactVector<Constructable, 5>
+ >;
+TYPED_TEST_SUITE(CompactVectorTest, CompactVectorTestTypes);
+
+// New vector test.
+TYPED_TEST(CompactVectorTest, EmptyVectorTest) {
+ SCOPED_TRACE("EmptyVectorTest");
+ this->assertEmpty(this->theVector);
+ EXPECT_TRUE(this->theVector.rbegin() == this->theVector.rend());
+ EXPECT_EQ(0, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(0, Constructable::getNumDestructorCalls());
+}
+
+// Simple insertions and deletions.
+TYPED_TEST(CompactVectorTest, PushPopTest) {
+ SCOPED_TRACE("PushPopTest");
+
+ // Track whether the vector will potentially have to grow.
+ bool RequiresGrowth = this->theVector.capacity() < 3;
+
+ // Push an element
+ this->theVector.push_back(Constructable(1));
+
+ // Size tests
+ this->assertValuesInOrder(this->theVector, 1u, 1);
+ EXPECT_FALSE(this->theVector.begin() == this->theVector.end());
+ EXPECT_FALSE(this->theVector.empty());
+
+ // Push another element
+ this->theVector.push_back(Constructable(2));
+ this->assertValuesInOrder(this->theVector, 2u, 1, 2);
+
+ // Insert at beginning
+ this->theVector.insert(this->theVector.begin(), this->theVector[1]);
+ this->assertValuesInOrder(this->theVector, 3u, 2, 1, 2);
+
+ // Pop one element
+ this->theVector.pop_back();
+ this->assertValuesInOrder(this->theVector, 2u, 2, 1);
+
+ // Pop remaining elements
+ this->theVector.pop_back();
+ this->theVector.pop_back();
+ this->assertEmpty(this->theVector);
+
+ // Check number of constructor calls. Should be 2 for each list element,
+ // one for the argument to push_back, one for the argument to insert,
+ // and one for the list element itself.
+ if (!RequiresGrowth) {
+ EXPECT_EQ(5, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(5, Constructable::getNumDestructorCalls());
+ } else {
+ // If we had to grow the vector, these only have a lower bound, but should
+ // always be equal.
+ EXPECT_LE(5, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(Constructable::getNumConstructorCalls(),
+ Constructable::getNumDestructorCalls());
+ }
+}
+
+TYPED_TEST(CompactVectorTest, InsertEnd)
+{
+ SCOPED_TRACE("InsertEnd");
+
+ TCompactVector<TString, 5> vector;
+ for (int index = 0; index < 10; ++index) {
+ vector.insert(vector.end(), ToString(index));
+ }
+ for (int index = 0; index < 10; ++index) {
+ EXPECT_EQ(vector[index], ToString(index));
+ }
+}
+
+TYPED_TEST(CompactVectorTest, ShrinkToSmall)
+{
+ SCOPED_TRACE("ShrinkToSmall");
+
+ TCompactVector<TString, 5> vector;
+ for (int index = 0; index < 10; ++index) {
+ vector.shrink_to_small();
+ vector.push_back(ToString(index));
+ }
+
+ for (int index = 0; index < 6; ++index) {
+ vector.pop_back();
+ }
+
+ EXPECT_EQ(std::ssize(vector), 4);
+ EXPECT_GE(static_cast<int>(vector.capacity()), 10);
+ vector.shrink_to_small();
+ EXPECT_EQ(std::ssize(vector), 4);
+ EXPECT_EQ(static_cast<int>(vector.capacity()), 5);
+ for (int index = 0; index < 4; ++index) {
+ EXPECT_EQ(vector[index], ToString(index));
+ }
+}
+
+// Clear test.
+TYPED_TEST(CompactVectorTest, ClearTest) {
+ SCOPED_TRACE("ClearTest");
+
+ this->theVector.reserve(2);
+ this->makeSequence(this->theVector, 1, 2);
+ this->theVector.clear();
+
+ this->assertEmpty(this->theVector);
+ EXPECT_EQ(4, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(4, Constructable::getNumDestructorCalls());
+}
+
+// Resize smaller test.
+TYPED_TEST(CompactVectorTest, ResizeShrinkTest) {
+ SCOPED_TRACE("ResizeShrinkTest");
+
+ this->theVector.reserve(3);
+ this->makeSequence(this->theVector, 1, 3);
+ this->theVector.resize(1);
+
+ this->assertValuesInOrder(this->theVector, 1u, 1);
+ EXPECT_EQ(6, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(5, Constructable::getNumDestructorCalls());
+}
+
+// Resize bigger test.
+TYPED_TEST(CompactVectorTest, ResizeGrowTest) {
+ SCOPED_TRACE("ResizeGrowTest");
+
+ this->theVector.resize(2);
+
+ EXPECT_EQ(2, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(0, Constructable::getNumDestructorCalls());
+ EXPECT_EQ(2u, this->theVector.size());
+}
+
+TYPED_TEST(CompactVectorTest, ResizeWithElementsTest) {
+ this->theVector.resize(2);
+
+ Constructable::reset();
+
+ this->theVector.resize(4);
+
+ size_t Ctors = Constructable::getNumConstructorCalls();
+ EXPECT_TRUE(Ctors == 2 || Ctors == 4);
+ size_t MoveCtors = Constructable::getNumMoveConstructorCalls();
+ EXPECT_TRUE(MoveCtors == 0 || MoveCtors == 2);
+ size_t Dtors = Constructable::getNumDestructorCalls();
+ EXPECT_TRUE(Dtors == 0 || Dtors == 2);
+}
+
+// Resize with fill value.
+TYPED_TEST(CompactVectorTest, ResizeFillTest) {
+ SCOPED_TRACE("ResizeFillTest");
+
+ this->theVector.resize(3, Constructable(77));
+ this->assertValuesInOrder(this->theVector, 3u, 77, 77, 77);
+}
+
+// Overflow past fixed size.
+TYPED_TEST(CompactVectorTest, OverflowTest) {
+ SCOPED_TRACE("OverflowTest");
+
+ // Push more elements than the fixed size.
+ this->makeSequence(this->theVector, 1, 10);
+
+ // Test size and values.
+ EXPECT_EQ(10u, this->theVector.size());
+ for (int i = 0; i < 10; ++i) {
+ EXPECT_EQ(i+1, this->theVector[i].getValue());
+ }
+
+ // Now resize back to fixed size.
+ this->theVector.resize(1);
+
+ this->assertValuesInOrder(this->theVector, 1u, 1);
+}
+
+// Iteration tests.
+TYPED_TEST(CompactVectorTest, IterationTest) {
+ this->makeSequence(this->theVector, 1, 2);
+
+ // Forward Iteration
+ typename TypeParam::iterator it = this->theVector.begin();
+ EXPECT_TRUE(*it == this->theVector.front());
+ EXPECT_TRUE(*it == this->theVector[0]);
+ EXPECT_EQ(1, it->getValue());
+ ++it;
+ EXPECT_TRUE(*it == this->theVector[1]);
+ EXPECT_TRUE(*it == this->theVector.back());
+ EXPECT_EQ(2, it->getValue());
+ ++it;
+ EXPECT_TRUE(it == this->theVector.end());
+ --it;
+ EXPECT_TRUE(*it == this->theVector[1]);
+ EXPECT_EQ(2, it->getValue());
+ --it;
+ EXPECT_TRUE(*it == this->theVector[0]);
+ EXPECT_EQ(1, it->getValue());
+
+ // Reverse Iteration
+ typename TypeParam::reverse_iterator rit = this->theVector.rbegin();
+ EXPECT_TRUE(*rit == this->theVector[1]);
+ EXPECT_EQ(2, rit->getValue());
+ ++rit;
+ EXPECT_TRUE(*rit == this->theVector[0]);
+ EXPECT_EQ(1, rit->getValue());
+ ++rit;
+ EXPECT_TRUE(rit == this->theVector.rend());
+ --rit;
+ EXPECT_TRUE(*rit == this->theVector[0]);
+ EXPECT_EQ(1, rit->getValue());
+ --rit;
+ EXPECT_TRUE(*rit == this->theVector[1]);
+ EXPECT_EQ(2, rit->getValue());
+}
+
+// Swap test.
+TYPED_TEST(CompactVectorTest, SwapTest) {
+ SCOPED_TRACE("SwapTest");
+
+ this->makeSequence(this->theVector, 1, 2);
+ std::swap(this->theVector, this->otherVector);
+
+ this->assertEmpty(this->theVector);
+ this->assertValuesInOrder(this->otherVector, 2u, 1, 2);
+}
+
+// Append test
+TYPED_TEST(CompactVectorTest, AppendTest) {
+ SCOPED_TRACE("AppendTest");
+
+ this->makeSequence(this->otherVector, 2, 3);
+
+ this->theVector.push_back(Constructable(1));
+ this->theVector.insert(this->theVector.end(), this->otherVector.begin(), this->otherVector.end());
+
+ this->assertValuesInOrder(this->theVector, 3u, 1, 2, 3);
+}
+
+// Append repeated test
+TYPED_TEST(CompactVectorTest, AppendRepeatedTest) {
+ SCOPED_TRACE("AppendRepeatedTest");
+
+ this->theVector.push_back(Constructable(1));
+ this->theVector.insert(this->theVector.end(), 2, Constructable(77));
+ this->assertValuesInOrder(this->theVector, 3u, 1, 77, 77);
+}
+
+// Assign test
+TYPED_TEST(CompactVectorTest, AssignTest) {
+ SCOPED_TRACE("AssignTest");
+
+ this->theVector.push_back(Constructable(1));
+ this->theVector.assign(2, Constructable(77));
+ this->assertValuesInOrder(this->theVector, 2u, 77, 77);
+}
+
+// Move-assign test
+TYPED_TEST(CompactVectorTest, MoveAssignTest) {
+ SCOPED_TRACE("MoveAssignTest");
+
+ // Set up our vector with a single element, but enough capacity for 4.
+ this->theVector.reserve(4);
+ this->theVector.push_back(Constructable(1));
+
+ // Set up the other vector with 2 elements.
+ this->otherVector.push_back(Constructable(2));
+ this->otherVector.push_back(Constructable(3));
+
+ // Move-assign from the other vector.
+ this->theVector = std::move(this->otherVector);
+
+ // Make sure we have the right result.
+ this->assertValuesInOrder(this->theVector, 2u, 2, 3);
+
+ // Make sure the # of constructor/destructor calls line up. There
+ // are two live objects after clearing the other vector.
+ this->otherVector.clear();
+ EXPECT_EQ(Constructable::getNumConstructorCalls()-2,
+ Constructable::getNumDestructorCalls());
+
+ // There shouldn't be any live objects any more.
+ this->theVector.clear();
+ EXPECT_EQ(Constructable::getNumConstructorCalls(),
+ Constructable::getNumDestructorCalls());
+}
+
+// Erase a single element
+TYPED_TEST(CompactVectorTest, EraseTest) {
+ SCOPED_TRACE("EraseTest");
+
+ this->makeSequence(this->theVector, 1, 3);
+ this->theVector.erase(this->theVector.begin());
+ this->assertValuesInOrder(this->theVector, 2u, 2, 3);
+}
+
+// Erase a range of elements
+TYPED_TEST(CompactVectorTest, EraseRangeTest) {
+ SCOPED_TRACE("EraseRangeTest");
+
+ this->makeSequence(this->theVector, 1, 3);
+ this->theVector.erase(this->theVector.begin(), this->theVector.begin() + 2);
+ this->assertValuesInOrder(this->theVector, 1u, 3);
+}
+
+// Insert a single element.
+TYPED_TEST(CompactVectorTest, InsertTest) {
+ SCOPED_TRACE("InsertTest");
+
+ this->makeSequence(this->theVector, 1, 3);
+ typename TypeParam::iterator I =
+ this->theVector.insert(this->theVector.begin() + 1, Constructable(77));
+ EXPECT_EQ(this->theVector.begin() + 1, I);
+ this->assertValuesInOrder(this->theVector, 4u, 1, 77, 2, 3);
+}
+
+// Insert a copy of a single element.
+TYPED_TEST(CompactVectorTest, InsertCopy) {
+ SCOPED_TRACE("InsertTest");
+
+ this->makeSequence(this->theVector, 1, 3);
+ Constructable C(77);
+ typename TypeParam::iterator I =
+ this->theVector.insert(this->theVector.begin() + 1, C);
+ EXPECT_EQ(this->theVector.begin() + 1, I);
+ this->assertValuesInOrder(this->theVector, 4u, 1, 77, 2, 3);
+}
+
+// Insert repeated elements.
+TYPED_TEST(CompactVectorTest, InsertRepeatedTest) {
+ SCOPED_TRACE("InsertRepeatedTest");
+
+ this->makeSequence(this->theVector, 1, 4);
+ Constructable::reset();
+ auto I =
+ this->theVector.insert(this->theVector.begin() + 1, 2, Constructable(16));
+ // Move construct the top element into newly allocated space, and optionally
+ // reallocate the whole buffer, move constructing into it.
+ // FIXME: This is inefficient, we shouldn't move things into newly allocated
+ // space, then move them up/around, there should only be 2 or 4 move
+ // constructions here.
+ EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 2 ||
+ Constructable::getNumMoveConstructorCalls() == 6);
+ // Move assign the next two to shift them up and make a gap.
+ EXPECT_EQ(1, Constructable::getNumMoveAssignmentCalls());
+ // Copy construct the two new elements from the parameter.
+ EXPECT_EQ(2, Constructable::getNumCopyAssignmentCalls());
+ // All without any copy construction.
+ EXPECT_EQ(0, Constructable::getNumCopyConstructorCalls());
+ EXPECT_EQ(this->theVector.begin() + 1, I);
+ this->assertValuesInOrder(this->theVector, 6u, 1, 16, 16, 2, 3, 4);
+}
+
+
+TYPED_TEST(CompactVectorTest, InsertRepeatedAtEndTest) {
+ SCOPED_TRACE("InsertRepeatedTest");
+
+ this->makeSequence(this->theVector, 1, 4);
+ Constructable::reset();
+ auto I = this->theVector.insert(this->theVector.end(), 2, Constructable(16));
+ // Just copy construct them into newly allocated space
+ EXPECT_EQ(2, Constructable::getNumCopyConstructorCalls());
+ // Move everything across if reallocation is needed.
+ EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 0 ||
+ Constructable::getNumMoveConstructorCalls() == 4);
+ // Without ever moving or copying anything else.
+ EXPECT_EQ(0, Constructable::getNumCopyAssignmentCalls());
+ EXPECT_EQ(0, Constructable::getNumMoveAssignmentCalls());
+
+ EXPECT_EQ(this->theVector.begin() + 4, I);
+ this->assertValuesInOrder(this->theVector, 6u, 1, 2, 3, 4, 16, 16);
+}
+
+TYPED_TEST(CompactVectorTest, InsertRepeatedEmptyTest) {
+ SCOPED_TRACE("InsertRepeatedTest");
+
+ this->makeSequence(this->theVector, 10, 15);
+
+ // Empty insert.
+ EXPECT_EQ(this->theVector.end(),
+ this->theVector.insert(this->theVector.end(),
+ 0, Constructable(42)));
+ EXPECT_EQ(this->theVector.begin() + 1,
+ this->theVector.insert(this->theVector.begin() + 1,
+ 0, Constructable(42)));
+}
+
+// Insert range.
+TYPED_TEST(CompactVectorTest, InsertRangeTest) {
+ SCOPED_TRACE("InsertRangeTest");
+
+ Constructable Arr[3] =
+ { Constructable(77), Constructable(77), Constructable(77) };
+
+ this->makeSequence(this->theVector, 1, 3);
+ Constructable::reset();
+ auto I = this->theVector.insert(this->theVector.begin() + 1, Arr, Arr + 3);
+ // Move construct the top 3 elements into newly allocated space.
+ // Possibly move the whole sequence into new space first.
+ // FIXME: This is inefficient, we shouldn't move things into newly allocated
+ // space, then move them up/around, there should only be 2 or 3 move
+ // constructions here.
+ EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 2 ||
+ Constructable::getNumMoveConstructorCalls() == 5);
+ // Copy assign the lower 2 new elements into existing space.
+ EXPECT_EQ(2, Constructable::getNumCopyAssignmentCalls());
+ // Copy construct the third element into newly allocated space.
+ EXPECT_EQ(1, Constructable::getNumCopyConstructorCalls());
+ EXPECT_EQ(this->theVector.begin() + 1, I);
+ this->assertValuesInOrder(this->theVector, 6u, 1, 77, 77, 77, 2, 3);
+}
+
+
+TYPED_TEST(CompactVectorTest, InsertRangeAtEndTest) {
+ SCOPED_TRACE("InsertRangeTest");
+
+ Constructable Arr[3] =
+ { Constructable(77), Constructable(77), Constructable(77) };
+
+ this->makeSequence(this->theVector, 1, 3);
+
+ // Insert at end.
+ Constructable::reset();
+ auto I = this->theVector.insert(this->theVector.end(), Arr, Arr+3);
+ // Copy construct the 3 elements into new space at the top.
+ EXPECT_EQ(3, Constructable::getNumCopyConstructorCalls());
+ // Don't copy/move anything else.
+ EXPECT_EQ(0, Constructable::getNumCopyAssignmentCalls());
+ // Reallocation might occur, causing all elements to be moved into the new
+ // buffer.
+ EXPECT_TRUE(Constructable::getNumMoveConstructorCalls() == 0 ||
+ Constructable::getNumMoveConstructorCalls() == 3);
+ EXPECT_EQ(0, Constructable::getNumMoveAssignmentCalls());
+ EXPECT_EQ(this->theVector.begin() + 3, I);
+ this->assertValuesInOrder(this->theVector, 6u,
+ 1, 2, 3, 77, 77, 77);
+}
+
+TYPED_TEST(CompactVectorTest, InsertEmptyRangeTest) {
+ SCOPED_TRACE("InsertRangeTest");
+
+ this->makeSequence(this->theVector, 1, 3);
+
+ // Empty insert.
+ EXPECT_EQ(this->theVector.end(),
+ this->theVector.insert(this->theVector.end(),
+ this->theVector.begin(),
+ this->theVector.begin()));
+ EXPECT_EQ(this->theVector.begin() + 1,
+ this->theVector.insert(this->theVector.begin() + 1,
+ this->theVector.begin(),
+ this->theVector.begin()));
+}
+
+// Comparison tests.
+TYPED_TEST(CompactVectorTest, ComparisonTest) {
+ SCOPED_TRACE("ComparisonTest");
+
+ this->makeSequence(this->theVector, 1, 3);
+ this->makeSequence(this->otherVector, 1, 3);
+
+ EXPECT_TRUE(this->theVector == this->otherVector);
+ EXPECT_FALSE(this->theVector != this->otherVector);
+
+ this->otherVector.clear();
+ this->makeSequence(this->otherVector, 2, 4);
+
+ EXPECT_FALSE(this->theVector == this->otherVector);
+ EXPECT_TRUE(this->theVector != this->otherVector);
+}
+
+// Constant vector tests.
+TYPED_TEST(CompactVectorTest, ConstVectorTest) {
+ const TypeParam constVector;
+
+ EXPECT_EQ(0u, constVector.size());
+ EXPECT_TRUE(constVector.empty());
+ EXPECT_TRUE(constVector.begin() == constVector.end());
+}
+
+// Direct array access.
+TYPED_TEST(CompactVectorTest, DirectVectorTest) {
+ EXPECT_EQ(0u, this->theVector.size());
+ this->theVector.reserve(4);
+ EXPECT_LE(4u, this->theVector.capacity());
+ EXPECT_EQ(0, Constructable::getNumConstructorCalls());
+ this->theVector.push_back(1);
+ this->theVector.push_back(2);
+ this->theVector.push_back(3);
+ this->theVector.push_back(4);
+ EXPECT_EQ(4u, this->theVector.size());
+ EXPECT_EQ(8, Constructable::getNumConstructorCalls());
+ EXPECT_EQ(1, this->theVector[0].getValue());
+ EXPECT_EQ(2, this->theVector[1].getValue());
+ EXPECT_EQ(3, this->theVector[2].getValue());
+ EXPECT_EQ(4, this->theVector[3].getValue());
+}
+
+TYPED_TEST(CompactVectorTest, IteratorTest) {
+ std::list<int> L;
+ this->theVector.insert(this->theVector.end(), L.begin(), L.end());
+}
+
+template <typename InvalidType> class DualCompactVectorsTest;
+
+template <typename VectorT1, typename VectorT2>
+class DualCompactVectorsTest<std::pair<VectorT1, VectorT2>> : public CompactVectorTestBase {
+protected:
+ VectorT1 theVector;
+ VectorT2 otherVector;
+
+ template <typename T, size_t N>
+ static size_t NumBuiltinElts(const TCompactVector<T, N>&) { return N; }
+};
+
+using DualCompactVectorTestTypes = ::testing::Types<
+ // Small mode -> Small mode.
+ std::pair<TCompactVector<Constructable, 4>, TCompactVector<Constructable, 4>>,
+ // Small mode -> Big mode.
+ std::pair<TCompactVector<Constructable, 4>, TCompactVector<Constructable, 2>>,
+ // Big mode -> Small mode.
+ std::pair<TCompactVector<Constructable, 2>, TCompactVector<Constructable, 4>>,
+ // Big mode -> Big mode.
+ std::pair<TCompactVector<Constructable, 2>, TCompactVector<Constructable, 2>>
+ >;
+
+TYPED_TEST_SUITE(DualCompactVectorsTest, DualCompactVectorTestTypes);
+
+TYPED_TEST(DualCompactVectorsTest, MoveAssignment) {
+ SCOPED_TRACE("MoveAssignTest-DualVectorTypes");
+
+ // Set up our vector with four elements.
+ for (unsigned I = 0; I < 4; ++I)
+ this->otherVector.push_back(Constructable(I));
+
+ const Constructable *OrigDataPtr = this->otherVector.data();
+
+ // Move-assign from the other vector.
+ this->theVector =
+ std::move(this->otherVector);
+
+ // Make sure we have the right result.
+ this->assertValuesInOrder(this->theVector, 4u, 0, 1, 2, 3);
+
+ // Make sure the # of constructor/destructor calls line up. There
+ // are two live objects after clearing the other vector.
+ this->otherVector.clear();
+ EXPECT_EQ(Constructable::getNumConstructorCalls()-4,
+ Constructable::getNumDestructorCalls());
+
+ // If the source vector (otherVector) was in small-mode, assert that we just
+ // moved the data pointer over.
+ EXPECT_TRUE(this->NumBuiltinElts(this->otherVector) == 4 ||
+ this->theVector.data() == OrigDataPtr);
+
+ // There shouldn't be any live objects any more.
+ this->theVector.clear();
+ EXPECT_EQ(Constructable::getNumConstructorCalls(),
+ Constructable::getNumDestructorCalls());
+
+ // We shouldn't have copied anything in this whole process.
+ EXPECT_EQ(Constructable::getNumCopyConstructorCalls(), 0);
+}
+
+struct notassignable {
+ int &x;
+ notassignable(int &x) : x(x) {}
+};
+
+TEST(CompactVectorCustomTest, NoAssignTest) {
+ int x = 0;
+ TCompactVector<notassignable, 2> vec;
+ vec.push_back(notassignable(x));
+ x = 42;
+ EXPECT_EQ(42, vec.back().x);
+}
+
+struct MovedFrom {
+ bool hasValue;
+ MovedFrom() : hasValue(true) {
+ }
+ MovedFrom(MovedFrom&& m) : hasValue(m.hasValue) {
+ m.hasValue = false;
+ }
+ MovedFrom &operator=(MovedFrom&& m) {
+ hasValue = m.hasValue;
+ m.hasValue = false;
+ return *this;
+ }
+};
+
+TEST(CompactVectorTest, MidInsert) {
+ TCompactVector<MovedFrom, 3> v;
+ v.push_back(MovedFrom());
+ v.insert(v.begin(), MovedFrom());
+ for (MovedFrom &m : v)
+ EXPECT_TRUE(m.hasValue);
+}
+
+enum EmplaceableArgState {
+ EAS_Defaulted,
+ EAS_Arg,
+ EAS_LValue,
+ EAS_RValue,
+ EAS_Failure
+};
+template <int I> struct EmplaceableArg {
+ EmplaceableArgState State;
+ EmplaceableArg() : State(EAS_Defaulted) {}
+ EmplaceableArg(EmplaceableArg &&X)
+ : State(X.State == EAS_Arg ? EAS_RValue : EAS_Failure) {}
+ EmplaceableArg(EmplaceableArg &X)
+ : State(X.State == EAS_Arg ? EAS_LValue : EAS_Failure) {}
+
+ explicit EmplaceableArg(bool) : State(EAS_Arg) {}
+
+private:
+ EmplaceableArg &operator=(EmplaceableArg &&) = delete;
+ EmplaceableArg &operator=(const EmplaceableArg &) = delete;
+};
+
+enum EmplaceableState { ES_Emplaced, ES_Moved };
+struct Emplaceable {
+ EmplaceableArg<0> A0;
+ EmplaceableArg<1> A1;
+ EmplaceableArg<2> A2;
+ EmplaceableArg<3> A3;
+ EmplaceableState State;
+
+ Emplaceable() : State(ES_Emplaced) {}
+
+ template <class A0Ty>
+ explicit Emplaceable(A0Ty &&A0)
+ : A0(std::forward<A0Ty>(A0)), State(ES_Emplaced) {}
+
+ template <class A0Ty, class A1Ty>
+ Emplaceable(A0Ty &&A0, A1Ty &&A1)
+ : A0(std::forward<A0Ty>(A0)), A1(std::forward<A1Ty>(A1)),
+ State(ES_Emplaced) {}
+
+ template <class A0Ty, class A1Ty, class A2Ty>
+ Emplaceable(A0Ty &&A0, A1Ty &&A1, A2Ty &&A2)
+ : A0(std::forward<A0Ty>(A0)), A1(std::forward<A1Ty>(A1)),
+ A2(std::forward<A2Ty>(A2)), State(ES_Emplaced) {}
+
+ template <class A0Ty, class A1Ty, class A2Ty, class A3Ty>
+ Emplaceable(A0Ty &&A0, A1Ty &&A1, A2Ty &&A2, A3Ty &&A3)
+ : A0(std::forward<A0Ty>(A0)), A1(std::forward<A1Ty>(A1)),
+ A2(std::forward<A2Ty>(A2)), A3(std::forward<A3Ty>(A3)),
+ State(ES_Emplaced) {}
+
+ Emplaceable(Emplaceable &&) : State(ES_Moved) {}
+ Emplaceable &operator=(Emplaceable &&) {
+ State = ES_Moved;
+ return *this;
+ }
+
+private:
+ Emplaceable(const Emplaceable &) = delete;
+ Emplaceable &operator=(const Emplaceable &) = delete;
+};
+
+TEST(CompactVectorTest, EmplaceBack) {
+ EmplaceableArg<0> A0(true);
+ EmplaceableArg<1> A1(true);
+ EmplaceableArg<2> A2(true);
+ EmplaceableArg<3> A3(true);
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace_back();
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A1.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace_back(std::move(A0));
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace_back(A0);
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace_back(A0, A1);
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace_back(std::move(A0), std::move(A1));
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace_back(std::move(A0), A1, std::move(A2), A3);
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A2.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A3.State == EAS_LValue);
+ }
+ {
+ TCompactVector<int, 1> V;
+ V.emplace_back();
+ V.emplace_back(42);
+ EXPECT_EQ(2U, V.size());
+ EXPECT_EQ(0, V[0]);
+ EXPECT_EQ(42, V[1]);
+ }
+}
+
+TEST(CompactVectorTest, Emplace) {
+ EmplaceableArg<0> A0(true);
+ EmplaceableArg<1> A1(true);
+ EmplaceableArg<2> A2(true);
+ EmplaceableArg<3> A3(true);
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace(V.end());
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A1.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace(V.end(), std::move(A0));
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace(V.end(), A0);
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace(V.end(), A0, A1);
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace(V.end(), std::move(A0), std::move(A1));
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A2.State == EAS_Defaulted);
+ EXPECT_TRUE(V.back().A3.State == EAS_Defaulted);
+ }
+ {
+ TCompactVector<Emplaceable, 3> V;
+ V.emplace(V.end(), std::move(A0), A1, std::move(A2), A3);
+ EXPECT_TRUE(V.size() == 1);
+ EXPECT_TRUE(V.back().State == ES_Emplaced);
+ EXPECT_TRUE(V.back().A0.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A1.State == EAS_LValue);
+ EXPECT_TRUE(V.back().A2.State == EAS_RValue);
+ EXPECT_TRUE(V.back().A3.State == EAS_LValue);
+ }
+ {
+ TCompactVector<int, 1> V;
+ V.emplace_back(42);
+ V.emplace(V.begin(), 0);
+ EXPECT_EQ(2U, V.size());
+ EXPECT_EQ(0, V[0]);
+ EXPECT_EQ(42, V[1]);
+ }
+}
+
+template <class T, size_t N>
+class TStubArray
+{
+public:
+ TStubArray(const TCompactVector<T, N>& vector)
+ : Vector_(vector)
+ { }
+
+ bool equals(std::initializer_list<T> list)
+ {
+ return std::equal(Vector_.begin(), Vector_.end(), list.begin());
+ }
+
+ TCompactVector<T, N> Vector_;
+};
+
+template <typename T, size_t N>
+TStubArray<T, N> makeArrayRef(const TCompactVector<T, N>& vector)
+{
+ return TStubArray<T, N>(vector);
+}
+
+TEST(CompactVectorTest, InitializerList) {
+ TCompactVector<int, 2> V1 = {};
+ EXPECT_TRUE(V1.empty());
+ V1 = {0, 0};
+ EXPECT_TRUE(makeArrayRef(V1).equals({0, 0}));
+ V1 = {-1, -1};
+ EXPECT_TRUE(makeArrayRef(V1).equals({-1, -1}));
+
+ TCompactVector<int, 2> V2 = {1, 2, 3, 4};
+ EXPECT_TRUE(makeArrayRef(V2).equals({1, 2, 3, 4}));
+ V2.assign({4});
+ EXPECT_TRUE(makeArrayRef(V2).equals({4}));
+ V2.insert(V2.end(), {3, 2});
+ EXPECT_TRUE(makeArrayRef(V2).equals({4, 3, 2}));
+ V2.insert(V2.begin() + 1, 5);
+ EXPECT_TRUE(makeArrayRef(V2).equals({4, 5, 3, 2}));
+}
+
+TEST(CompactVectorTest, AssignToShorter) {
+ TCompactVector<TString, 4> lhs;
+ TCompactVector<TString, 4> rhs;
+ rhs.emplace_back("foo");
+ lhs = rhs;
+ EXPECT_EQ(1U, lhs.size());
+ EXPECT_EQ("foo", lhs[0]);
+}
+
+TEST(CompactVectorTest, AssignToLonger) {
+ TCompactVector<TString, 4> lhs;
+ lhs.emplace_back("bar");
+ lhs.emplace_back("baz");
+ TCompactVector<TString, 4> rhs;
+ rhs.emplace_back("foo");
+ lhs = rhs;
+ EXPECT_EQ(1U, lhs.size());
+ EXPECT_EQ("foo", lhs[0]);
+}
+
+TEST(CompactVectorTest, ZeroPaddingOnHeapMeta) {
+ TCompactVector<uint8_t, 6> vector;
+ std::vector<uint8_t> expected;
+ for (int i = 0; i < 10; ++i) {
+ vector.push_back(i);
+ expected.push_back(i);
+
+ ASSERT_THAT(vector, ::testing::ElementsAreArray(expected));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/compact_containers/unittests/ya.make b/library/cpp/yt/compact_containers/unittests/ya.make
new file mode 100644
index 00000000000..430f28fef26
--- /dev/null
+++ b/library/cpp/yt/compact_containers/unittests/ya.make
@@ -0,0 +1,18 @@
+GTEST(unittester-small-containers)
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ compact_flat_map_ut.cpp
+ compact_heap_ut.cpp
+ compact_set_ut.cpp
+ compact_vector_ut.cpp
+ compact_queue_ut.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/compact_containers
+ library/cpp/testing/gtest
+)
+
+END()
diff --git a/library/cpp/yt/compact_containers/ya.make b/library/cpp/yt/compact_containers/ya.make
new file mode 100644
index 00000000000..e52beb943c1
--- /dev/null
+++ b/library/cpp/yt/compact_containers/ya.make
@@ -0,0 +1,23 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ library/cpp/yt/assert
+ library/cpp/yt/malloc
+ library/cpp/yt/misc
+)
+
+CHECK_DEPENDENT_DIRS(
+ ALLOW_ONLY ALL
+ build
+ contrib
+ library
+ util
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)