aboutsummaryrefslogtreecommitdiffstats
path: root/util/generic
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /util/generic
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/generic')
-rw-r--r--util/generic/adaptor.cpp1
-rw-r--r--util/generic/adaptor.h140
-rw-r--r--util/generic/adaptor_ut.cpp124
-rw-r--r--util/generic/algorithm.cpp1
-rw-r--r--util/generic/algorithm.h765
-rw-r--r--util/generic/algorithm_ut.cpp850
-rw-r--r--util/generic/array_ref.cpp1
-rw-r--r--util/generic/array_ref.h280
-rw-r--r--util/generic/array_ref.pxd25
-rw-r--r--util/generic/array_ref_ut.cpp322
-rw-r--r--util/generic/array_ref_ut.pyx28
-rw-r--r--util/generic/array_size.cpp1
-rw-r--r--util/generic/array_size.h24
-rw-r--r--util/generic/array_size_ut.cpp22
-rw-r--r--util/generic/benchmark/cont_speed/main.cpp117
-rw-r--r--util/generic/benchmark/cont_speed/ya.make9
-rw-r--r--util/generic/benchmark/fastclp2/main.cpp50
-rw-r--r--util/generic/benchmark/fastclp2/metrics/main.py5
-rw-r--r--util/generic/benchmark/fastclp2/metrics/ya.make21
-rw-r--r--util/generic/benchmark/fastclp2/ya.make13
-rw-r--r--util/generic/benchmark/log2/main.cpp140
-rw-r--r--util/generic/benchmark/log2/metrics/main.py5
-rw-r--r--util/generic/benchmark/log2/metrics/ya.make21
-rw-r--r--util/generic/benchmark/log2/ya.make17
-rw-r--r--util/generic/benchmark/rotate_bits/main.cpp66
-rw-r--r--util/generic/benchmark/rotate_bits/metrics/main.py5
-rw-r--r--util/generic/benchmark/rotate_bits/metrics/ya.make21
-rw-r--r--util/generic/benchmark/rotate_bits/ya.make13
-rw-r--r--util/generic/benchmark/singleton/f.cpp18
-rw-r--r--util/generic/benchmark/singleton/main.cpp54
-rw-r--r--util/generic/benchmark/singleton/ya.make11
-rw-r--r--util/generic/benchmark/smart_pointers/main.cpp14
-rw-r--r--util/generic/benchmark/smart_pointers/ya.make10
-rw-r--r--util/generic/benchmark/sort/main.cpp77
-rw-r--r--util/generic/benchmark/sort/ya.make10
-rw-r--r--util/generic/benchmark/string/benchmarks.h188
-rw-r--r--util/generic/benchmark/string/std_string.cpp8
-rw-r--r--util/generic/benchmark/string/string.cpp8
-rw-r--r--util/generic/benchmark/string/ya.make11
-rw-r--r--util/generic/benchmark/vector_count_ctor/f.cpp25
-rw-r--r--util/generic/benchmark/vector_count_ctor/f.h9
-rw-r--r--util/generic/benchmark/vector_count_ctor/main.cpp30
-rw-r--r--util/generic/benchmark/vector_count_ctor/metrics/main.py5
-rw-r--r--util/generic/benchmark/vector_count_ctor/metrics/ya.make21
-rw-r--r--util/generic/benchmark/vector_count_ctor/ya.make16
-rw-r--r--util/generic/benchmark/ya.make19
-rw-r--r--util/generic/bitmap.cpp1
-rw-r--r--util/generic/bitmap.h1114
-rw-r--r--util/generic/bitmap_ut.cpp597
-rw-r--r--util/generic/bitops.cpp141
-rw-r--r--util/generic/bitops.h459
-rw-r--r--util/generic/bitops_ut.cpp348
-rw-r--r--util/generic/bt_exception.cpp1
-rw-r--r--util/generic/bt_exception.h24
-rw-r--r--util/generic/buffer.cpp99
-rw-r--r--util/generic/buffer.h245
-rw-r--r--util/generic/buffer_ut.cpp205
-rw-r--r--util/generic/cast.cpp1
-rw-r--r--util/generic/cast.h176
-rw-r--r--util/generic/cast_ut.cpp112
-rw-r--r--util/generic/deque.cpp1
-rw-r--r--util/generic/deque.h25
-rw-r--r--util/generic/deque.pxd9
-rw-r--r--util/generic/deque_ut.cpp253
-rw-r--r--util/generic/deque_ut.pyx66
-rw-r--r--util/generic/explicit_type.cpp1
-rw-r--r--util/generic/explicit_type.h42
-rw-r--r--util/generic/explicit_type_ut.cpp52
-rw-r--r--util/generic/fastqueue.cpp1
-rw-r--r--util/generic/fastqueue.h54
-rw-r--r--util/generic/flags.cpp29
-rw-r--r--util/generic/flags.h244
-rw-r--r--util/generic/flags_ut.cpp117
-rw-r--r--util/generic/function.cpp1
-rw-r--r--util/generic/function.h103
-rw-r--r--util/generic/function_ut.cpp69
-rw-r--r--util/generic/fuzz/vector/main.cpp47
-rw-r--r--util/generic/fuzz/vector/ya.make13
-rw-r--r--util/generic/fuzz/ya.make3
-rw-r--r--util/generic/fwd.cpp1
-rw-r--r--util/generic/fwd.h168
-rw-r--r--util/generic/guid.cpp186
-rw-r--r--util/generic/guid.h79
-rw-r--r--util/generic/guid_ut.cpp128
-rw-r--r--util/generic/hash.cpp51
-rw-r--r--util/generic/hash.h2029
-rw-r--r--util/generic/hash.pxd66
-rw-r--r--util/generic/hash_primes.cpp121
-rw-r--r--util/generic/hash_primes.h138
-rw-r--r--util/generic/hash_primes_ut.cpp80
-rw-r--r--util/generic/hash_set.cpp1
-rw-r--r--util/generic/hash_set.h488
-rw-r--r--util/generic/hash_set.pxd43
-rw-r--r--util/generic/hash_set_ut.pyx53
-rw-r--r--util/generic/hash_ut.cpp1271
-rw-r--r--util/generic/hash_ut.pyx84
-rw-r--r--util/generic/hide_ptr.cpp5
-rw-r--r--util/generic/hide_ptr.h3
-rw-r--r--util/generic/intrlist.cpp1
-rw-r--r--util/generic/intrlist.h872
-rw-r--r--util/generic/intrlist_ut.cpp512
-rw-r--r--util/generic/is_in.cpp1
-rw-r--r--util/generic/is_in.h53
-rw-r--r--util/generic/is_in_ut.cpp116
-rw-r--r--util/generic/iterator.cpp1
-rw-r--r--util/generic/iterator.h139
-rw-r--r--util/generic/iterator_range.cpp1
-rw-r--r--util/generic/iterator_range.h104
-rw-r--r--util/generic/iterator_range_ut.cpp98
-rw-r--r--util/generic/iterator_ut.cpp63
-rw-r--r--util/generic/lazy_value.cpp1
-rw-r--r--util/generic/lazy_value.h66
-rw-r--r--util/generic/lazy_value_ut.cpp157
-rw-r--r--util/generic/list.cpp1
-rw-r--r--util/generic/list.h22
-rw-r--r--util/generic/list.pxd65
-rw-r--r--util/generic/list_ut.cpp14
-rw-r--r--util/generic/list_ut.pyx158
-rw-r--r--util/generic/map.cpp1
-rw-r--r--util/generic/map.h44
-rw-r--r--util/generic/map.pxd46
-rw-r--r--util/generic/map_ut.cpp496
-rw-r--r--util/generic/map_ut.pyx82
-rw-r--r--util/generic/mapfindptr.cpp1
-rw-r--r--util/generic/mapfindptr.h60
-rw-r--r--util/generic/mapfindptr_ut.cpp67
-rw-r--r--util/generic/maybe.cpp16
-rw-r--r--util/generic/maybe.h722
-rw-r--r--util/generic/maybe.pxd35
-rw-r--r--util/generic/maybe_traits.h173
-rw-r--r--util/generic/maybe_ut.cpp1006
-rw-r--r--util/generic/maybe_ut.pyx185
-rw-r--r--util/generic/mem_copy.cpp1
-rw-r--r--util/generic/mem_copy.h55
-rw-r--r--util/generic/mem_copy_ut.cpp113
-rw-r--r--util/generic/noncopyable.cpp1
-rw-r--r--util/generic/noncopyable.h38
-rw-r--r--util/generic/object_counter.cpp1
-rw-r--r--util/generic/object_counter.h53
-rw-r--r--util/generic/objects_counter_ut.cpp36
-rw-r--r--util/generic/overloaded.cpp1
-rw-r--r--util/generic/overloaded.h56
-rw-r--r--util/generic/overloaded_ut.cpp82
-rw-r--r--util/generic/ptr.cpp17
-rw-r--r--util/generic/ptr.h1145
-rw-r--r--util/generic/ptr.pxd42
-rw-r--r--util/generic/ptr_ut.cpp835
-rw-r--r--util/generic/ptr_ut.pyx26
-rw-r--r--util/generic/queue.cpp1
-rw-r--r--util/generic/queue.h58
-rw-r--r--util/generic/queue_ut.cpp210
-rw-r--r--util/generic/refcount.cpp1
-rw-r--r--util/generic/refcount.h162
-rw-r--r--util/generic/reserve.h11
-rw-r--r--util/generic/scope.cpp1
-rw-r--r--util/generic/scope.h65
-rw-r--r--util/generic/scope_ut.cpp47
-rw-r--r--util/generic/serialized_enum.cpp1
-rw-r--r--util/generic/serialized_enum.h399
-rw-r--r--util/generic/serialized_enum_ut.cpp120
-rw-r--r--util/generic/set.cpp1
-rw-r--r--util/generic/set.h42
-rw-r--r--util/generic/set_ut.cpp408
-rw-r--r--util/generic/singleton.cpp61
-rw-r--r--util/generic/singleton.h136
-rw-r--r--util/generic/singleton_ut.cpp46
-rw-r--r--util/generic/size_literals.cpp1
-rw-r--r--util/generic/size_literals.h65
-rw-r--r--util/generic/size_literals_ut.cpp68
-rw-r--r--util/generic/stack.cpp1
-rw-r--r--util/generic/stack.h18
-rw-r--r--util/generic/stack_ut.cpp16
-rw-r--r--util/generic/store_policy.cpp1
-rw-r--r--util/generic/store_policy.h120
-rw-r--r--util/generic/store_policy_ut.cpp87
-rw-r--r--util/generic/strbase.h607
-rw-r--r--util/generic/strbuf.cpp9
-rw-r--r--util/generic/strbuf.h539
-rw-r--r--util/generic/strbuf_ut.cpp373
-rw-r--r--util/generic/strfcpy.cpp50
-rw-r--r--util/generic/strfcpy.h17
-rw-r--r--util/generic/string.cpp156
-rw-r--r--util/generic/string.h1287
-rw-r--r--util/generic/string.pxd118
-rw-r--r--util/generic/string_hash.h21
-rw-r--r--util/generic/string_transparent_hash_ut.cpp19
-rw-r--r--util/generic/string_ut.cpp1270
-rw-r--r--util/generic/string_ut.h1156
-rw-r--r--util/generic/string_ut.pyx225
-rw-r--r--util/generic/typelist.cpp5
-rw-r--r--util/generic/typelist.h114
-rw-r--r--util/generic/typelist_ut.cpp85
-rw-r--r--util/generic/typetraits.cpp1
-rw-r--r--util/generic/typetraits.h320
-rw-r--r--util/generic/typetraits_ut.cpp468
-rw-r--r--util/generic/ut/ya.make73
-rw-r--r--util/generic/utility.cpp21
-rw-r--r--util/generic/utility.h132
-rw-r--r--util/generic/utility_ut.cpp180
-rw-r--r--util/generic/va_args.cpp15
-rw-r--r--util/generic/va_args.h791
-rwxr-xr-xutil/generic/va_args_gen.py211
-rw-r--r--util/generic/va_args_ut.cpp106
-rw-r--r--util/generic/variant.cpp1
-rw-r--r--util/generic/variant.h23
-rw-r--r--util/generic/vector.cpp1
-rw-r--r--util/generic/vector.h132
-rw-r--r--util/generic/vector.pxd83
-rw-r--r--util/generic/vector_ut.cpp596
-rw-r--r--util/generic/vector_ut.pyx221
-rw-r--r--util/generic/xrange.cpp1
-rw-r--r--util/generic/xrange.h268
-rw-r--r--util/generic/xrange_ut.cpp217
-rw-r--r--util/generic/ya.make6
-rw-r--r--util/generic/yexception.cpp122
-rw-r--r--util/generic/yexception.h231
-rw-r--r--util/generic/yexception_ut.c5
-rw-r--r--util/generic/yexception_ut.cpp392
-rw-r--r--util/generic/yexception_ut.h14
-rw-r--r--util/generic/ylimits.cpp1
-rw-r--r--util/generic/ylimits.h92
-rw-r--r--util/generic/ylimits_ut.cpp157
-rw-r--r--util/generic/ymath.cpp56
-rw-r--r--util/generic/ymath.h206
-rw-r--r--util/generic/ymath_ut.cpp220
225 files changed, 34711 insertions, 0 deletions
diff --git a/util/generic/adaptor.cpp b/util/generic/adaptor.cpp
new file mode 100644
index 0000000000..1909857ba5
--- /dev/null
+++ b/util/generic/adaptor.cpp
@@ -0,0 +1 @@
+#include "adaptor.h"
diff --git a/util/generic/adaptor.h b/util/generic/adaptor.h
new file mode 100644
index 0000000000..b88a65fc81
--- /dev/null
+++ b/util/generic/adaptor.h
@@ -0,0 +1,140 @@
+#pragma once
+
+#include "store_policy.h"
+#include "typetraits.h"
+
+namespace NPrivate {
+ template <class Range>
+ class TReverseRangeStorage {
+ public:
+ TReverseRangeStorage(Range&& range)
+ : Base_(std::forward<Range>(range))
+ {
+ }
+
+ decltype(auto) Base() const {
+ return *Base_.Ptr();
+ }
+
+ decltype(auto) Base() {
+ return *Base_.Ptr();
+ }
+
+ private:
+ TAutoEmbedOrPtrPolicy<Range> Base_;
+ };
+
+ template <class Range>
+ constexpr bool HasReverseIterators(i32, decltype(std::declval<Range>().rbegin())*) {
+ return true;
+ }
+
+ template <class Range>
+ constexpr bool HasReverseIterators(char, std::nullptr_t*) {
+ return false;
+ }
+
+ template <class Range, bool hasReverseIterators = HasReverseIterators<Range>((i32)0, nullptr)>
+ class TReverseRangeBase: public TReverseRangeStorage<Range> {
+ using TBase = TReverseRangeStorage<Range>;
+
+ public:
+ using TBase::Base;
+ using TBase::TBase;
+
+ auto begin() const {
+ return Base().rbegin();
+ }
+
+ auto end() const {
+ return Base().rend();
+ }
+
+ auto begin() {
+ return Base().rbegin();
+ }
+
+ auto end() {
+ return Base().rend();
+ }
+ };
+
+ template <class Range>
+ class TReverseRangeBase<Range, false>: public TReverseRangeStorage<Range> {
+ using TBase = TReverseRangeStorage<Range>;
+
+ public:
+ using TBase::Base;
+ using TBase::TBase;
+
+ auto begin() const {
+ using std::end;
+ return std::make_reverse_iterator(end(Base()));
+ }
+
+ auto end() const {
+ using std::begin;
+ return std::make_reverse_iterator(begin(Base()));
+ }
+
+ auto begin() {
+ using std::end;
+ return std::make_reverse_iterator(end(Base()));
+ }
+
+ auto end() {
+ using std::begin;
+ return std::make_reverse_iterator(begin(Base()));
+ }
+ };
+
+ template <class Range>
+ class TReverseRange: public TReverseRangeBase<Range> {
+ using TBase = TReverseRangeBase<Range>;
+
+ public:
+ using TBase::Base;
+ using TBase::TBase;
+
+ TReverseRange(TReverseRange&&) = default;
+ TReverseRange(const TReverseRange&) = default;
+
+ auto rbegin() const {
+ using std::begin;
+ return begin(Base());
+ }
+
+ auto rend() const {
+ using std::end;
+ return end(Base());
+ }
+
+ auto rbegin() {
+ using std::begin;
+ return begin(Base());
+ }
+
+ auto rend() {
+ using std::end;
+ return end(Base());
+ }
+ };
+}
+
+/**
+ * Provides a reverse view into the provided container.
+ *
+ * Example usage:
+ * @code
+ * for(auto&& value: Reversed(container)) {
+ * // use value here.
+ * }
+ * @endcode
+ *
+ * @param cont Container to provide a view into. Must be an lvalue.
+ * @returns A reverse view into the provided container.
+ */
+template <class Range>
+constexpr ::NPrivate::TReverseRange<Range> Reversed(Range&& range) {
+ return ::NPrivate::TReverseRange<Range>(std::forward<Range>(range));
+}
diff --git a/util/generic/adaptor_ut.cpp b/util/generic/adaptor_ut.cpp
new file mode 100644
index 0000000000..721f849f93
--- /dev/null
+++ b/util/generic/adaptor_ut.cpp
@@ -0,0 +1,124 @@
+#include "adaptor.h"
+#include "yexception.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+struct TOnCopy: yexception {
+};
+
+struct TOnMove: yexception {
+};
+
+struct TState {
+ explicit TState() {
+ }
+
+ TState(const TState&) {
+ ythrow TOnCopy();
+ }
+
+ TState(TState&&) {
+ ythrow TOnMove();
+ }
+
+ void operator=(const TState&) {
+ ythrow TOnCopy();
+ }
+
+ void rbegin() const {
+ }
+
+ void rend() const {
+ }
+};
+
+Y_UNIT_TEST_SUITE(TReverseAdaptor) {
+ Y_UNIT_TEST(ReadTest) {
+ TVector<int> cont = {1, 2, 3};
+ TVector<int> etalon = {3, 2, 1};
+ size_t idx = 0;
+ for (const auto& x : Reversed(cont)) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon[idx++], x);
+ }
+ idx = 0;
+ for (const auto& x : Reversed(std::move(cont))) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon[idx++], x);
+ }
+ }
+
+ Y_UNIT_TEST(WriteTest) {
+ TVector<int> cont = {1, 2, 3};
+ TVector<int> etalon = {3, 6, 9};
+ size_t idx = 0;
+ for (auto& x : Reversed(cont)) {
+ x *= x + idx++;
+ }
+ idx = 0;
+ for (auto& x : cont) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon[idx++], x);
+ }
+ }
+
+ Y_UNIT_TEST(InnerTypeTest) {
+ using TStub = TVector<int>;
+ TStub stub;
+ const TStub cstub;
+
+ using namespace NPrivate;
+ UNIT_ASSERT_TYPES_EQUAL(decltype(Reversed(stub)), TReverseRange<TStub&>);
+ UNIT_ASSERT_TYPES_EQUAL(decltype(Reversed(cstub)), TReverseRange<const TStub&>);
+ }
+
+ Y_UNIT_TEST(CopyMoveTest) {
+ TState lvalue;
+ const TState clvalue;
+ UNIT_ASSERT_NO_EXCEPTION(Reversed(lvalue));
+ UNIT_ASSERT_NO_EXCEPTION(Reversed(clvalue));
+ }
+
+ Y_UNIT_TEST(ReverseX2Test) {
+ TVector<int> cont = {1, 2, 3};
+ size_t idx = 0;
+ for (const auto& x : Reversed(Reversed(cont))) {
+ UNIT_ASSERT_VALUES_EQUAL(cont[idx++], x);
+ }
+ }
+
+ Y_UNIT_TEST(ReverseX3Test) {
+ TVector<int> cont = {1, 2, 3};
+ TVector<int> etalon = {3, 2, 1};
+ size_t idx = 0;
+ for (const auto& x : Reversed(Reversed(Reversed(cont)))) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon[idx++], x);
+ }
+ }
+
+ Y_UNIT_TEST(ReverseTemporaryTest) {
+ TVector<int> etalon = {3, 2, 1};
+ TVector<int> etalon2 = {1, 2, 3};
+ size_t idx = 0;
+ for (const auto& x : Reversed(TVector<int>{1, 2, 3})) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon[idx++], x);
+ }
+ idx = 0;
+ for (const auto& x : Reversed(Reversed(TVector<int>{1, 2, 3}))) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon2[idx++], x);
+ }
+ }
+
+ Y_UNIT_TEST(ReverseInitializerListTest) {
+ // initializer_list has no rbegin and rend
+ auto cont = {1, 2, 3};
+ TVector<int> etalon = {3, 2, 1};
+ TVector<int> etalon2 = {1, 2, 3};
+
+ size_t idx = 0;
+ for (const auto& x : Reversed(cont)) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon[idx++], x);
+ }
+ idx = 0;
+ for (const auto& x : Reversed(Reversed(cont))) {
+ UNIT_ASSERT_VALUES_EQUAL(etalon2[idx++], x);
+ }
+ }
+}
diff --git a/util/generic/algorithm.cpp b/util/generic/algorithm.cpp
new file mode 100644
index 0000000000..d89586737e
--- /dev/null
+++ b/util/generic/algorithm.cpp
@@ -0,0 +1 @@
+#include "algorithm.h"
diff --git a/util/generic/algorithm.h b/util/generic/algorithm.h
new file mode 100644
index 0000000000..badfb88993
--- /dev/null
+++ b/util/generic/algorithm.h
@@ -0,0 +1,765 @@
+#pragma once
+
+#include "is_in.h"
+#include "utility.h"
+
+#include <util/system/defaults.h>
+#include <util/generic/fwd.h>
+
+#include <numeric>
+#include <algorithm>
+#include <iterator>
+#include <utility>
+
+namespace NPrivate {
+ template <class I, class F, class P>
+ I ExtremeElementBy(I begin, I end, F func, P pred) {
+ if (begin == end) {
+ return end;
+ }
+
+ auto bestValue = func(*begin);
+ auto bestPos = begin;
+
+ for (auto i = ++begin; i != end; ++i) {
+ auto curValue = func(*i);
+ if (pred(curValue, bestValue)) {
+ bestValue = curValue;
+ bestPos = i;
+ }
+ }
+
+ return bestPos;
+ }
+}
+
+template <class T>
+static inline void Sort(T f, T l) {
+ std::sort(f, l);
+}
+
+template <class T, class C>
+static inline void Sort(T f, T l, C c) {
+ std::sort(f, l, c);
+}
+
+template <class TContainer>
+static inline void Sort(TContainer& container) {
+ Sort(container.begin(), container.end());
+}
+
+template <class TContainer, typename TCompare>
+static inline void Sort(TContainer& container, TCompare compare) {
+ Sort(container.begin(), container.end(), compare);
+}
+
+template <class TIterator, typename TGetKey>
+static inline void SortBy(TIterator begin, TIterator end, const TGetKey& getKey) {
+ Sort(begin, end, [&](auto&& left, auto&& right) { return getKey(left) < getKey(right); });
+}
+
+template <class TContainer, typename TGetKey>
+static inline void SortBy(TContainer& container, const TGetKey& getKey) {
+ SortBy(container.begin(), container.end(), getKey);
+}
+
+template <class T>
+static inline void StableSort(T f, T l) {
+ std::stable_sort(f, l);
+}
+
+template <class T, class C>
+static inline void StableSort(T f, T l, C c) {
+ std::stable_sort(f, l, c);
+}
+
+template <class TContainer>
+static inline void StableSort(TContainer& container) {
+ StableSort(container.begin(), container.end());
+}
+
+template <class TContainer, typename TCompare>
+static inline void StableSort(TContainer& container, TCompare compare) {
+ StableSort(container.begin(), container.end(), compare);
+}
+
+template <class TIterator, typename TGetKey>
+static inline void StableSortBy(TIterator begin, TIterator end, const TGetKey& getKey) {
+ StableSort(begin, end, [&](auto&& left, auto&& right) { return getKey(left) < getKey(right); });
+}
+
+template <class TContainer, typename TGetKey>
+static inline void StableSortBy(TContainer& container, const TGetKey& getKey) {
+ StableSortBy(container.begin(), container.end(), getKey);
+}
+
+template <class T>
+static inline void PartialSort(T f, T m, T l) {
+ std::partial_sort(f, m, l);
+}
+
+template <class T, class C>
+static inline void PartialSort(T f, T m, T l, C c) {
+ std::partial_sort(f, m, l, c);
+}
+
+template <class T, class R>
+static inline R PartialSortCopy(T f, T l, R of, R ol) {
+ return std::partial_sort_copy(f, l, of, ol);
+}
+
+template <class T, class R, class C>
+static inline R PartialSortCopy(T f, T l, R of, R ol, C c) {
+ return std::partial_sort_copy(f, l, of, ol, c);
+}
+
+template <class I, class T>
+static inline I Find(I f, I l, const T& v) {
+ return std::find(f, l, v);
+}
+
+template <class C, class T>
+static inline auto Find(C&& c, const T& v) {
+ using std::begin;
+ using std::end;
+
+ return std::find(begin(c), end(c), v);
+}
+
+// FindPtr - return NULL if not found. Works for arrays, containers, iterators
+template <class I, class T>
+static inline auto FindPtr(I f, I l, const T& v) -> decltype(&*f) {
+ I found = Find(f, l, v);
+ return (found != l) ? &*found : nullptr;
+}
+
+template <class C, class T>
+static inline auto FindPtr(C&& c, const T& v) {
+ using std::begin;
+ using std::end;
+ return FindPtr(begin(c), end(c), v);
+}
+
+template <class I, class P>
+static inline I FindIf(I f, I l, P p) {
+ return std::find_if(f, l, p);
+}
+
+template <class C, class P>
+static inline auto FindIf(C&& c, P p) {
+ using std::begin;
+ using std::end;
+
+ return FindIf(begin(c), end(c), p);
+}
+
+template <class I, class P>
+static inline bool AllOf(I f, I l, P pred) {
+ return std::all_of(f, l, pred);
+}
+
+template <class C, class P>
+static inline bool AllOf(const C& c, P pred) {
+ using std::begin;
+ using std::end;
+ return AllOf(begin(c), end(c), pred);
+}
+
+template <class I, class P>
+static inline bool AnyOf(I f, I l, P pred) {
+ return std::any_of(f, l, pred);
+}
+
+template <class C, class P>
+static inline bool AnyOf(const C& c, P pred) {
+ using std::begin;
+ using std::end;
+ return AnyOf(begin(c), end(c), pred);
+}
+
+// FindIfPtr - return NULL if not found. Works for arrays, containers, iterators
+template <class I, class P>
+static inline auto FindIfPtr(I f, I l, P pred) -> decltype(&*f) {
+ I found = FindIf(f, l, pred);
+ return (found != l) ? &*found : nullptr;
+}
+
+template <class C, class P>
+static inline auto FindIfPtr(C&& c, P pred) {
+ using std::begin;
+ using std::end;
+ return FindIfPtr(begin(c), end(c), pred);
+}
+
+template <class C, class T>
+static inline size_t FindIndex(C&& c, const T& x) {
+ using std::begin;
+ using std::end;
+ auto it = Find(begin(c), end(c), x);
+ return it == end(c) ? NPOS : (it - begin(c));
+}
+
+template <class C, class P>
+static inline size_t FindIndexIf(C&& c, P p) {
+ using std::begin;
+ using std::end;
+ auto it = FindIf(begin(c), end(c), p);
+ return it == end(c) ? NPOS : (it - begin(c));
+}
+
+//EqualToOneOf(x, "apple", "orange") means (x == "apple" || x == "orange")
+template <typename T>
+inline bool EqualToOneOf(const T&) {
+ return false;
+}
+
+template <typename T, typename U, typename... Other>
+inline bool EqualToOneOf(const T& x, const U& y, const Other&... other) {
+ return x == y || EqualToOneOf(x, other...);
+}
+
+template <typename T>
+static inline size_t CountOf(const T&) {
+ return 0;
+}
+
+template <typename T, typename U, typename... Other>
+static inline size_t CountOf(const T& x, const U& y, const Other&... other) {
+ return static_cast<size_t>(x == y) + CountOf(x, other...);
+}
+
+template <class I>
+static inline void PushHeap(I f, I l) {
+ std::push_heap(f, l);
+}
+
+template <class I, class C>
+static inline void PushHeap(I f, I l, C c) {
+ std::push_heap(f, l, c);
+}
+
+template <class I>
+static inline void PopHeap(I f, I l) {
+ std::pop_heap(f, l);
+}
+
+template <class I, class C>
+static inline void PopHeap(I f, I l, C c) {
+ std::pop_heap(f, l, c);
+}
+
+template <class I>
+static inline void MakeHeap(I f, I l) {
+ std::make_heap(f, l);
+}
+
+template <class I, class C>
+static inline void MakeHeap(I f, I l, C c) {
+ std::make_heap(f, l, c);
+}
+
+template <class I>
+static inline void SortHeap(I f, I l) {
+ std::sort_heap(f, l);
+}
+
+template <class I, class C>
+static inline void SortHeap(I f, I l, C c) {
+ std::sort_heap(f, l, c);
+}
+
+template <class I, class T>
+static inline I LowerBound(I f, I l, const T& v) {
+ return std::lower_bound(f, l, v);
+}
+
+template <class I, class T, class C>
+static inline I LowerBound(I f, I l, const T& v, C c) {
+ return std::lower_bound(f, l, v, c);
+}
+
+template <class I, class T, class TGetKey>
+static inline I LowerBoundBy(I f, I l, const T& v, const TGetKey& getKey) {
+ return std::lower_bound(f, l, v, [&](auto&& left, auto&& right) { return getKey(left) < right; });
+}
+
+template <class I, class T>
+static inline I UpperBound(I f, I l, const T& v) {
+ return std::upper_bound(f, l, v);
+}
+
+template <class I, class T, class C>
+static inline I UpperBound(I f, I l, const T& v, C c) {
+ return std::upper_bound(f, l, v, c);
+}
+
+template <class I, class T, class TGetKey>
+static inline I UpperBoundBy(I f, I l, const T& v, const TGetKey& getKey) {
+ return std::upper_bound(f, l, v, [&](auto&& left, auto&& right) { return left < getKey(right); });
+}
+
+template <class T>
+static inline T Unique(T f, T l) {
+ return std::unique(f, l);
+}
+
+template <class T, class P>
+static inline T Unique(T f, T l, P p) {
+ return std::unique(f, l, p);
+}
+
+template <class T, class TGetKey>
+static inline T UniqueBy(T f, T l, const TGetKey& getKey) {
+ return Unique(f, l, [&](auto&& left, auto&& right) { return getKey(left) == getKey(right); });
+}
+
+template <class C>
+void SortUnique(C& c) {
+ Sort(c.begin(), c.end());
+ c.erase(Unique(c.begin(), c.end()), c.end());
+}
+
+template <class C, class Cmp>
+void SortUnique(C& c, Cmp cmp) {
+ Sort(c.begin(), c.end(), cmp);
+ c.erase(Unique(c.begin(), c.end()), c.end());
+}
+
+template <class C, class TGetKey>
+void SortUniqueBy(C& c, const TGetKey& getKey) {
+ SortBy(c, getKey);
+ c.erase(UniqueBy(c.begin(), c.end(), getKey), c.end());
+}
+
+template <class C, class TGetKey>
+void StableSortUniqueBy(C& c, const TGetKey& getKey) {
+ StableSortBy(c, getKey);
+ c.erase(UniqueBy(c.begin(), c.end(), getKey), c.end());
+}
+
+template <class C, class TValue>
+void Erase(C& c, const TValue& value) {
+ c.erase(std::remove(c.begin(), c.end(), value), c.end());
+}
+
+template <class C, class P>
+void EraseIf(C& c, P p) {
+ c.erase(std::remove_if(c.begin(), c.end(), p), c.end());
+}
+
+template <class C, class P>
+void EraseNodesIf(C& c, P p) {
+ for (auto iter = c.begin(), last = c.end(); iter != last;) {
+ if (p(*iter)) {
+ c.erase(iter++);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+template <class T1, class T2>
+static inline bool Equal(T1 f1, T1 l1, T2 f2) {
+ return std::equal(f1, l1, f2);
+}
+
+template <class T1, class T2, class P>
+static inline bool Equal(T1 f1, T1 l1, T2 f2, P p) {
+ return std::equal(f1, l1, f2, p);
+}
+
+template <class TI, class TO>
+static inline TO Copy(TI f, TI l, TO t) {
+ return std::copy(f, l, t);
+}
+
+template <class TI, class TO>
+static inline TO UniqueCopy(TI f, TI l, TO t) {
+ return std::unique_copy(f, l, t);
+}
+
+template <class TI, class TO, class TP>
+static inline TO UniqueCopy(TI f, TI l, TO t, TP p) {
+ return std::unique_copy(f, l, t, p);
+}
+
+template <class TI, class TO, class TP>
+static inline TO RemoveCopyIf(TI f, TI l, TO t, TP p) {
+ return std::remove_copy_if(f, l, t, p);
+}
+
+template <class TI, class TO>
+static inline TO ReverseCopy(TI f, TI l, TO t) {
+ return std::reverse_copy(f, l, t);
+}
+
+template <class TI1, class TI2, class TO>
+static inline TO SetUnion(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p) {
+ return std::set_union(f1, l1, f2, l2, p);
+}
+
+template <class TI1, class TI2, class TO, class TC>
+static inline TO SetUnion(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p, TC c) {
+ return std::set_union(f1, l1, f2, l2, p, c);
+}
+
+template <class TI1, class TI2, class TO>
+static inline TO SetDifference(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p) {
+ return std::set_difference(f1, l1, f2, l2, p);
+}
+
+template <class TI1, class TI2, class TO, class TC>
+static inline TO SetDifference(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p, TC c) {
+ return std::set_difference(f1, l1, f2, l2, p, c);
+}
+
+template <class TI1, class TI2, class TO>
+static inline TO SetSymmetricDifference(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p) {
+ return std::set_symmetric_difference(f1, l1, f2, l2, p);
+}
+
+template <class TI1, class TI2, class TO, class TC>
+static inline TO SetSymmetricDifference(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p, TC c) {
+ return std::set_symmetric_difference(f1, l1, f2, l2, p, c);
+}
+
+template <class TI1, class TI2, class TO>
+static inline TO SetIntersection(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p) {
+ return std::set_intersection(f1, l1, f2, l2, p);
+}
+
+template <class TI1, class TI2, class TO, class TC>
+static inline TO SetIntersection(TI1 f1, TI1 l1, TI2 f2, TI2 l2, TO p, TC c) {
+ return std::set_intersection(f1, l1, f2, l2, p, c);
+}
+
+template <class I, class T>
+static inline void Fill(I f, I l, const T& v) {
+ std::fill(f, l, v);
+}
+
+template <typename I, typename S, typename T>
+static inline I FillN(I f, S n, const T& v) {
+ return std::fill_n(f, n, v);
+}
+
+template <class T>
+static inline void Reverse(T f, T l) {
+ std::reverse(f, l);
+}
+
+template <class T>
+static inline void Rotate(T f, T m, T l) {
+ std::rotate(f, m, l);
+}
+
+template <typename It, typename Val>
+Val Accumulate(It begin, It end, Val val) {
+ // std::move since C++20
+ return std::accumulate(begin, end, std::move(val));
+}
+
+template <typename It, typename Val, typename BinOp>
+Val Accumulate(It begin, It end, Val val, BinOp binOp) {
+ // std::move since C++20
+ return std::accumulate(begin, end, std::move(val), binOp);
+}
+
+template <typename C, typename Val>
+Val Accumulate(const C& c, Val val) {
+ // std::move since C++20
+ return Accumulate(std::begin(c), std::end(c), std::move(val));
+}
+
+template <typename C, typename Val, typename BinOp>
+Val Accumulate(const C& c, Val val, BinOp binOp) {
+ // std::move since C++20
+ return Accumulate(std::begin(c), std::end(c), std::move(val), binOp);
+}
+
+template <typename It1, typename It2, typename Val>
+static inline Val InnerProduct(It1 begin1, It1 end1, It2 begin2, Val val) {
+ return std::inner_product(begin1, end1, begin2, val);
+}
+
+template <typename It1, typename It2, typename Val, typename BinOp1, typename BinOp2>
+static inline Val InnerProduct(It1 begin1, It1 end1, It2 begin2, Val val, BinOp1 binOp1, BinOp2 binOp2) {
+ return std::inner_product(begin1, end1, begin2, val, binOp1, binOp2);
+}
+
+template <typename TVectorType>
+static inline typename TVectorType::value_type InnerProduct(const TVectorType& lhs, const TVectorType& rhs, typename TVectorType::value_type val = typename TVectorType::value_type()) {
+ return std::inner_product(lhs.begin(), lhs.end(), rhs.begin(), val);
+}
+
+template <typename TVectorType, typename BinOp1, typename BinOp2>
+static inline typename TVectorType::value_type InnerProduct(const TVectorType& lhs, const TVectorType& rhs, typename TVectorType::value_type val, BinOp1 binOp1, BinOp2 binOp2) {
+ return std::inner_product(lhs.begin(), lhs.end(), rhs.begin(), val, binOp1, binOp2);
+}
+
+template <class T>
+static inline T MinElement(T begin, T end) {
+ return std::min_element(begin, end);
+}
+
+template <class T, class C>
+static inline T MinElement(T begin, T end, C comp) {
+ return std::min_element(begin, end, comp);
+}
+
+template <class T>
+static inline T MaxElement(T begin, T end) {
+ return std::max_element(begin, end);
+}
+
+template <class T, class C>
+static inline T MaxElement(T begin, T end, C comp) {
+ return std::max_element(begin, end, comp);
+}
+
+template <class I, class F>
+I MaxElementBy(I begin, I end, F&& func) {
+ using TValue = decltype(func(*begin));
+ return ::NPrivate::ExtremeElementBy(begin, end, std::forward<F>(func), TGreater<TValue>());
+}
+
+template <class C, class F>
+auto MaxElementBy(C& c, F&& func) {
+ return MaxElementBy(std::begin(c), std::end(c), std::forward<F>(func));
+}
+
+template <class C, class F>
+auto MaxElementBy(const C& c, F&& func) {
+ return MaxElementBy(std::begin(c), std::end(c), std::forward<F>(func));
+}
+
+template <class I, class F>
+I MinElementBy(I begin, I end, F&& func) {
+ using TValue = decltype(func(*begin));
+ return ::NPrivate::ExtremeElementBy(begin, end, std::forward<F>(func), TLess<TValue>());
+}
+
+template <class C, class F>
+auto MinElementBy(C& c, F&& func) {
+ return MinElementBy(std::begin(c), std::end(c), std::forward<F>(func));
+}
+
+template <class C, class F>
+auto MinElementBy(const C& c, F&& func) {
+ return MinElementBy(std::begin(c), std::end(c), std::forward<F>(func));
+}
+
+template <class TOp, class... TArgs>
+void ApplyToMany(TOp op, TArgs&&... args) {
+ int dummy[] = {((void)op(std::forward<TArgs>(args)), 0)...};
+ Y_UNUSED(dummy);
+}
+
+template <class TI, class TOp>
+inline void ForEach(TI f, TI l, TOp op) {
+ std::for_each(f, l, op);
+}
+
+namespace NPrivate {
+ template <class T, class TOp, size_t... Is>
+ constexpr bool AllOfImpl(T&& t, TOp&& op, std::index_sequence<Is...>) {
+#if _LIBCPP_STD_VER >= 17
+ return (true && ... && op(std::get<Is>(std::forward<T>(t))));
+#else
+ bool result = true;
+ auto wrapper = [&result, &op](auto&& x) { result = result && op(std::forward<decltype(x)>(x)); };
+ int dummy[] = {(wrapper(std::get<Is>(std::forward<T>(t))), 0)...};
+ Y_UNUSED(dummy);
+ return result;
+#endif
+ }
+
+ template <class T, class TOp, size_t... Is>
+ constexpr bool AnyOfImpl(T&& t, TOp&& op, std::index_sequence<Is...>) {
+#if _LIBCPP_STD_VER >= 17
+ return (false || ... || op(std::get<Is>(std::forward<T>(t))));
+#else
+ bool result = false;
+ auto wrapper = [&result, &op](auto&& x) { result = result || op(std::forward<decltype(x)>(x)); };
+ int dummy[] = {(wrapper(std::get<Is>(std::forward<T>(t))), 0)...};
+ Y_UNUSED(dummy);
+ return result;
+#endif
+ }
+
+ template <class T, class TOp, size_t... Is>
+ constexpr void ForEachImpl(T&& t, TOp&& op, std::index_sequence<Is...>) {
+#if _LIBCPP_STD_VER >= 17
+ (..., op(std::get<Is>(std::forward<T>(t))));
+#else
+ ::ApplyToMany(std::forward<TOp>(op), std::get<Is>(std::forward<T>(t))...);
+#endif
+ }
+}
+
+// check that TOp return true for all of element from tuple T
+template <class T, class TOp>
+constexpr ::TEnableIfTuple<T, bool> AllOf(T&& t, TOp&& op) {
+ return ::NPrivate::AllOfImpl(
+ std::forward<T>(t),
+ std::forward<TOp>(op),
+ std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>{});
+}
+
+// check that TOp return true for at least one element from tuple T
+template <class T, class TOp>
+constexpr ::TEnableIfTuple<T, bool> AnyOf(T&& t, TOp&& op) {
+ return ::NPrivate::AnyOfImpl(
+ std::forward<T>(t),
+ std::forward<TOp>(op),
+ std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>{});
+}
+
+template <class T, class TOp>
+constexpr ::TEnableIfTuple<T> ForEach(T&& t, TOp&& op) {
+ ::NPrivate::ForEachImpl(
+ std::forward<T>(t),
+ std::forward<TOp>(op),
+ std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>{});
+}
+
+template <class T1, class T2, class O>
+static inline void Transform(T1 b, T1 e, T2 o, O f) {
+ std::transform(b, e, o, f);
+}
+
+template <class T1, class T2, class T3, class O>
+static inline void Transform(T1 b1, T1 e1, T2 b2, T3 o, O f) {
+ std::transform(b1, e1, b2, o, f);
+}
+
+template <class T, class V>
+inline typename std::iterator_traits<T>::difference_type Count(T first, T last, const V& value) {
+ return std::count(first, last, value);
+}
+
+template <class TContainer, class TValue>
+static inline auto Count(const TContainer& container, const TValue& value) {
+ return Count(std::cbegin(container), std::cend(container), value);
+}
+
+template <class It, class P>
+static inline auto CountIf(It first, It last, P p) {
+ return std::count_if(first, last, p);
+}
+
+template <class C, class P>
+static inline auto CountIf(const C& c, P pred) {
+ using std::begin;
+ using std::end;
+ return CountIf(begin(c), end(c), pred);
+}
+
+template <class I1, class I2>
+static inline std::pair<I1, I2> Mismatch(I1 b1, I1 e1, I2 b2) {
+ return std::mismatch(b1, e1, b2);
+}
+
+template <class I1, class I2, class P>
+static inline std::pair<I1, I2> Mismatch(I1 b1, I1 e1, I2 b2, P p) {
+ return std::mismatch(b1, e1, b2, p);
+}
+
+template <class RandomIterator>
+static inline void NthElement(RandomIterator begin, RandomIterator nth, RandomIterator end) {
+ std::nth_element(begin, nth, end);
+}
+
+template <class RandomIterator, class Compare>
+static inline void NthElement(RandomIterator begin, RandomIterator nth, RandomIterator end, Compare compare) {
+ std::nth_element(begin, nth, end, compare);
+}
+
+// no standard implementation until C++14
+template <class I1, class I2>
+static inline std::pair<I1, I2> Mismatch(I1 b1, I1 e1, I2 b2, I2 e2) {
+ while (b1 != e1 && b2 != e2 && *b1 == *b2) {
+ ++b1;
+ ++b2;
+ }
+ return std::make_pair(b1, b2);
+}
+
+template <class I1, class I2, class P>
+static inline std::pair<I1, I2> Mismatch(I1 b1, I1 e1, I2 b2, I2 e2, P p) {
+ while (b1 != e1 && b2 != e2 && p(*b1, *b2)) {
+ ++b1;
+ ++b2;
+ }
+ return std::make_pair(b1, b2);
+}
+
+template <class It, class Val>
+static inline bool BinarySearch(It begin, It end, const Val& val) {
+ return std::binary_search(begin, end, val);
+}
+
+template <class It, class Val, class Comp>
+static inline bool BinarySearch(It begin, It end, const Val& val, Comp comp) {
+ return std::binary_search(begin, end, val, comp);
+}
+
+template <class It, class Val>
+static inline std::pair<It, It> EqualRange(It begin, It end, const Val& val) {
+ return std::equal_range(begin, end, val);
+}
+
+template <class It, class Val, class Comp>
+static inline std::pair<It, It> EqualRange(It begin, It end, const Val& val, Comp comp) {
+ return std::equal_range(begin, end, val, comp);
+}
+
+template <class ForwardIt>
+bool IsSorted(ForwardIt begin, ForwardIt end) {
+ return std::is_sorted(begin, end);
+}
+
+template <class ForwardIt, class Compare>
+bool IsSorted(ForwardIt begin, ForwardIt end, Compare comp) {
+ return std::is_sorted(begin, end, comp);
+}
+
+template <class TIterator, typename TGetKey>
+bool IsSortedBy(TIterator begin, TIterator end, const TGetKey& getKey) {
+ return IsSorted(begin, end, [&](auto&& left, auto&& right) { return getKey(left) < getKey(right); });
+}
+
+template <class It, class Val>
+void Iota(It begin, It end, Val val) {
+ std::iota(begin, end, val);
+}
+
+template <class TI, class TO, class S>
+TO CopyN(TI from, S s, TO to) {
+ return std::copy_n(from, s, to);
+}
+
+template <class TI, class TO, class P>
+TO CopyIf(TI begin, TI end, TO to, P pred) {
+ return std::copy_if(begin, end, to, pred);
+}
+
+template <class T>
+std::pair<const T&, const T&> MinMax(const T& first, const T& second) {
+ return std::minmax(first, second);
+}
+
+template <class It>
+std::pair<It, It> MinMaxElement(It first, It last) {
+ return std::minmax_element(first, last);
+}
+
+template <class TIterator, class TGenerator>
+void Generate(TIterator first, TIterator last, TGenerator generator) {
+ std::generate(first, last, generator);
+}
+
+template <class TIterator, class TSize, class TGenerator>
+void GenerateN(TIterator first, TSize count, TGenerator generator) {
+ std::generate_n(first, count, generator);
+}
diff --git a/util/generic/algorithm_ut.cpp b/util/generic/algorithm_ut.cpp
new file mode 100644
index 0000000000..8d732fcc0c
--- /dev/null
+++ b/util/generic/algorithm_ut.cpp
@@ -0,0 +1,850 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+#include "algorithm.h"
+#include "strbuf.h"
+#include "string.h"
+
+static auto isOne = [](char c) { return c == '1'; };
+
+Y_UNIT_TEST_SUITE(TAlgorithm) {
+ Y_UNIT_TEST(AnyTest) {
+ UNIT_ASSERT(0 == AnyOf(TStringBuf("00"), isOne));
+ UNIT_ASSERT(1 == AnyOf(TStringBuf("01"), isOne));
+ UNIT_ASSERT(1 == AnyOf(TStringBuf("10"), isOne));
+ UNIT_ASSERT(1 == AnyOf(TStringBuf("11"), isOne));
+ UNIT_ASSERT(0 == AnyOf(TStringBuf(), isOne));
+
+ const char array00[]{'0', '0'};
+ UNIT_ASSERT(0 == AnyOf(array00, isOne));
+ const char array01[]{'0', '1'};
+ UNIT_ASSERT(1 == AnyOf(array01, isOne));
+ }
+
+ Y_UNIT_TEST(AllOfTest) {
+ UNIT_ASSERT(0 == AllOf(TStringBuf("00"), isOne));
+ UNIT_ASSERT(0 == AllOf(TStringBuf("01"), isOne));
+ UNIT_ASSERT(0 == AllOf(TStringBuf("10"), isOne));
+ UNIT_ASSERT(1 == AllOf(TStringBuf("11"), isOne));
+ UNIT_ASSERT(1 == AllOf(TStringBuf(), isOne));
+
+ const char array01[]{'0', '1'};
+ UNIT_ASSERT(0 == AllOf(array01, isOne));
+ const char array11[]{'1', '1'};
+ UNIT_ASSERT(1 == AllOf(array11, isOne));
+ }
+
+ Y_UNIT_TEST(CountIfTest) {
+ UNIT_ASSERT(3 == CountIf(TStringBuf("____1________1____1_______"), isOne));
+ UNIT_ASSERT(5 == CountIf(TStringBuf("1____1________1____1_______1"), isOne));
+ UNIT_ASSERT(0 == CountIf(TStringBuf("___________"), isOne));
+ UNIT_ASSERT(0 == CountIf(TStringBuf(), isOne));
+ UNIT_ASSERT(1 == CountIf(TStringBuf("1"), isOne));
+
+ const char array[] = "____1________1____1_______";
+ UNIT_ASSERT(3 == CountIf(array, isOne));
+ }
+
+ Y_UNIT_TEST(CountTest) {
+ UNIT_ASSERT(3 == Count("____1________1____1_______", '1'));
+ UNIT_ASSERT(3 == Count(TStringBuf("____1________1____1_______"), '1'));
+ UNIT_ASSERT(5 == Count(TStringBuf("1____1________1____1_______1"), '1'));
+ UNIT_ASSERT(0 == Count(TStringBuf("___________"), '1'));
+ UNIT_ASSERT(0 == Count(TStringBuf(), '1'));
+ UNIT_ASSERT(1 == Count(TStringBuf("1"), '1'));
+
+ const char array[] = "____1________1____1_______";
+ UNIT_ASSERT(3 == Count(array, '1'));
+ }
+
+ struct TStrokaNoCopy: TString {
+ public:
+ TStrokaNoCopy(const char* p)
+ : TString(p)
+ {
+ }
+
+ private:
+ TStrokaNoCopy(const TStrokaNoCopy&);
+ void operator=(const TStrokaNoCopy&);
+ };
+
+ Y_UNIT_TEST(CountOfTest) {
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(1, 2), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(1, 1), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(2, 4, 5), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(2, 4, 2), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(3, 3, 3), 2);
+
+ // Checking comparison of different types.
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(0x61, 'x', 'y', 'z'), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(0x61, 'a', 'b', 'c', 0x61), 2);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(0x61, 'a', 'b', 'c', 0x61ll), 2);
+
+ // TString and const char *
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TString("xyz"), "123", "poi"), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TString("xyz"), "123", "poi", "xyz"), 1);
+
+ // TString and TStringBuf
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TString("xyz"), TStringBuf("123"), TStringBuf("poi")), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TString("xyz"), TStringBuf("123"), TStringBuf("poi"),
+ TStringBuf("xyz")),
+ 1);
+
+ // TStringBuf and const char *
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TStringBuf("xyz"), "123", "poi"), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TStringBuf("xyz"), "123", "poi", "xyz"), 1);
+
+ // TStringBuf and TString
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TStringBuf("xyz"), TString("123"), TString("poi")), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CountOf(TStringBuf("xyz"), TString("123"), TString("poi"),
+ TString("xyz")),
+ 1);
+ }
+
+ Y_UNIT_TEST(EqualToOneOfTest) {
+ UNIT_ASSERT(1 == EqualToOneOf(1, 1, 2));
+ UNIT_ASSERT(1 == EqualToOneOf(2, 1, 2));
+ UNIT_ASSERT(0 == EqualToOneOf(3, 1, 2));
+ UNIT_ASSERT(1 == EqualToOneOf(1, 1));
+ UNIT_ASSERT(0 == EqualToOneOf(1, 2));
+ UNIT_ASSERT(0 == EqualToOneOf(3));
+
+ //test, that EqualToOneOf can compare different types, and don't copy objects:
+ TStrokaNoCopy x("x");
+ TStrokaNoCopy y("y");
+ TStrokaNoCopy z("z");
+ const char* px = "x";
+ const char* py = "y";
+ const char* pz = "z";
+
+ UNIT_ASSERT(1 == EqualToOneOf(x, px, py));
+ UNIT_ASSERT(1 == EqualToOneOf(y, px, py));
+ UNIT_ASSERT(1 == EqualToOneOf(y, px, y));
+ UNIT_ASSERT(1 == EqualToOneOf(y, x, py));
+ UNIT_ASSERT(0 == EqualToOneOf(z, px, py));
+ UNIT_ASSERT(1 == EqualToOneOf(px, x, y));
+ UNIT_ASSERT(1 == EqualToOneOf(py, x, y));
+ UNIT_ASSERT(0 == EqualToOneOf(pz, x, y));
+ }
+
+ template <class TTestConstPtr>
+ void TestFindPtrFoundValue(int j, TTestConstPtr root) {
+ if (j == 3) {
+ UNIT_ASSERT(root && *root == 3);
+ } else if (j == 4) {
+ UNIT_ASSERT(root == nullptr);
+ } else {
+ ythrow yexception() << "invalid param " << j;
+ }
+ }
+
+ template <class TTestConstPtr>
+ void TestFindIfPtrFoundValue(int j, TTestConstPtr root) {
+ if (j == 3) {
+ UNIT_ASSERT(root == nullptr);
+ } else if (j == 4) {
+ UNIT_ASSERT(root && *root == 2);
+ } else {
+ ythrow yexception() << "invalid param " << j;
+ }
+ }
+
+ struct TVectorNoCopy: std::vector<int> {
+ public:
+ TVectorNoCopy() = default;
+
+ private:
+ TVectorNoCopy(const TVectorNoCopy&);
+ void operator=(const TVectorNoCopy&);
+ };
+
+ Y_UNIT_TEST(FindPtrTest) {
+ TVectorNoCopy v;
+ v.push_back(1);
+ v.push_back(2);
+ v.push_back(3);
+
+ int array[3] = {1, 2, 3};
+ const int array_const[3] = {1, 2, 3};
+
+ //test (const, non-const) * (iterator, vector, array) * (found, not found) variants.
+ // value '3' is in container, value '4' is not
+ for (int j = 3; j <= 4; ++j) {
+ TestFindPtrFoundValue<int*>(j, FindPtr(v, j));
+ TestFindPtrFoundValue<int*>(j, FindPtr(v.begin(), v.end(), j));
+ const TVectorNoCopy& q = v;
+ TestFindPtrFoundValue<const int*>(j, FindPtr(q, j));
+ TestFindPtrFoundValue<const int*>(j, FindPtr(q.begin(), q.end(), j));
+ TestFindPtrFoundValue<int*>(j, FindPtr(array, j));
+ TestFindPtrFoundValue<const int*>(j, FindPtr(array_const, j));
+ }
+ }
+
+ Y_UNIT_TEST(FindIfPtrTest) {
+ TVectorNoCopy v;
+ v.push_back(1);
+ v.push_back(2);
+ v.push_back(3);
+
+ int array[3] = {1, 2, 3};
+ const int array_const[3] = {1, 2, 3};
+
+ //test (const, non-const) * (iterator, vector, array) * (found, not found) variants.
+ // search, that 2*2 == 4, but there is no value 'x' in array that (x*x == 3)
+ for (int j = 3; j <= 4; ++j) {
+ TestFindIfPtrFoundValue<int*>(j, FindIfPtr(v, [j](int i) { return i * i == j; }));
+ TestFindIfPtrFoundValue<int*>(j, FindIfPtr(v.begin(), v.end(), [j](int i) { return i * i == j; }));
+ const TVectorNoCopy& q = v;
+ TestFindIfPtrFoundValue<const int*>(j, FindIfPtr(q, [j](int i) { return i * i == j; }));
+
+ TestFindIfPtrFoundValue<const int*>(j, FindIfPtr(q.begin(), q.end(), [j](int i) { return i * i == j; }));
+ TestFindIfPtrFoundValue<int*>(j, FindIfPtr(array, [j](int i) { return i * i == j; }));
+ TestFindIfPtrFoundValue<const int*>(j, FindIfPtr(array_const, [j](int i) { return i * i == j; }));
+ }
+ }
+
+ Y_UNIT_TEST(FindIndexTest) {
+ TVectorNoCopy v;
+ v.push_back(1);
+ v.push_back(2);
+ v.push_back(3);
+
+ UNIT_ASSERT_EQUAL(0, FindIndex(v, 1));
+ UNIT_ASSERT_EQUAL(1, FindIndex(v, 2));
+ UNIT_ASSERT_EQUAL(2, FindIndex(v, 3));
+ UNIT_ASSERT_EQUAL(NPOS, FindIndex(v, 42));
+
+ int array[3] = {1, 2, 3};
+
+ UNIT_ASSERT_EQUAL(0, FindIndex(array, 1));
+ UNIT_ASSERT_EQUAL(1, FindIndex(array, 2));
+ UNIT_ASSERT_EQUAL(2, FindIndex(array, 3));
+ UNIT_ASSERT_EQUAL(NPOS, FindIndex(array, 42));
+
+ TVector<int> empty;
+ UNIT_ASSERT_EQUAL(NPOS, FindIndex(empty, 0));
+ }
+
+ Y_UNIT_TEST(FindIndexIfTest) {
+ TVectorNoCopy v;
+ v.push_back(1);
+ v.push_back(2);
+ v.push_back(3);
+
+ UNIT_ASSERT_EQUAL(0, FindIndexIf(v, [](int x) { return x == 1; }));
+ UNIT_ASSERT_EQUAL(1, FindIndexIf(v, [](int x) { return x == 2; }));
+ UNIT_ASSERT_EQUAL(2, FindIndexIf(v, [](int x) { return x == 3; }));
+ UNIT_ASSERT_EQUAL(NPOS, FindIndexIf(v, [](int x) { return x == 42; }));
+
+ int array[3] = {1, 2, 3};
+
+ UNIT_ASSERT_EQUAL(0, FindIndexIf(array, [](int x) { return x == 1; }));
+ UNIT_ASSERT_EQUAL(1, FindIndexIf(array, [](int x) { return x == 2; }));
+ UNIT_ASSERT_EQUAL(2, FindIndexIf(array, [](int x) { return x == 3; }));
+ UNIT_ASSERT_EQUAL(NPOS, FindIndexIf(array, [](int x) { return x == 42; }));
+
+ TVector<int> empty;
+ UNIT_ASSERT_EQUAL(NPOS, FindIndexIf(empty, [](int x) { return x == 3; }));
+ }
+
+ Y_UNIT_TEST(SortUniqueTest) {
+ {
+ TVector<TString> v;
+ SortUnique(v);
+ UNIT_ASSERT_EQUAL(v, TVector<TString>());
+ }
+
+ {
+ const char* ar[] = {"345", "3", "123", "2", "23", "3", "2"};
+ TVector<TString> v(ar, ar + Y_ARRAY_SIZE(ar));
+ SortUnique(v);
+
+ const char* suAr[] = {"123", "2", "23", "3", "345"};
+ TVector<TString> suV(suAr, suAr + Y_ARRAY_SIZE(suAr));
+
+ UNIT_ASSERT_EQUAL(v, suV);
+ }
+ }
+
+ Y_UNIT_TEST(EraseTest) {
+ TVector<int> data = {5, 4, 3, 2, 1, 0};
+ TVector<int> expected = {5, 4, 2, 1, 0};
+ Erase(data, 3);
+ UNIT_ASSERT_EQUAL(data, expected);
+ }
+
+ Y_UNIT_TEST(EraseIfTest) {
+ TVector<int> data = {5, 4, 3, 2, 1, 0};
+ TVector<int> expected = {2, 1, 0};
+ EraseIf(data, [](int i) { return i >= 3; });
+ UNIT_ASSERT_EQUAL(data, expected);
+ }
+
+ Y_UNIT_TEST(EraseNodesIfTest) {
+ TMap<int, int> map{{1, 1}, {2, 2}, {3, 5}};
+ TMap<int, int> expectedMap{{1, 1}};
+ EraseNodesIf(map, [](auto p) { return p.first >= 2; });
+ UNIT_ASSERT_EQUAL(map, expectedMap);
+
+ TMultiMap<int, int> multiMap{{1, 1}, {1, 3}, {2, 2}, {3, 5}};
+ TMultiMap<int, int> expectedMultiMap{{1, 1}, {1, 3}};
+ EraseNodesIf(multiMap, [](auto p) { return p.first >= 2; });
+ UNIT_ASSERT_EQUAL(multiMap, expectedMultiMap);
+
+ TSet<int> set{1, 2, 3, 4, 5, 6, 7};
+ TSet<int> expectedSet{1, 3, 5, 7};
+ EraseNodesIf(set, [](int i) { return i % 2 == 0; });
+ UNIT_ASSERT_EQUAL(set, expectedSet);
+
+ TMultiSet<int> multiSet{1, 1, 2, 3, 4, 4, 4, 5, 5, 5, 6, 7};
+ TMultiSet<int> expectedMultiSet{1, 1, 3, 5, 5, 5, 7};
+ EraseNodesIf(multiSet, [](int i) { return i % 2 == 0; });
+ UNIT_ASSERT_EQUAL(multiSet, expectedMultiSet);
+
+ THashMap<int, int> hashMap{{1, 0}, {3, 0}, {4, 0}, {10, 0}, {2, 0}, {5, 2}};
+ THashMap<int, int> expectedHashMap{{1, 0}, {3, 0}, {5, 2}};
+ EraseNodesIf(hashMap, [](auto p) { return p.first % 2 == 0; });
+ UNIT_ASSERT_EQUAL(hashMap, expectedHashMap);
+
+ THashMultiMap<int, int> hashMultiMap{{1, 0}, {3, 0}, {4, 0}, {10, 0}, {2, 0}, {5, 0}, {1, 0}, {1, 0}, {2, 0}, {2, 2}};
+ THashMultiMap<int, int> expectedHashMultiMap{{1, 0}, {1, 0}, {1, 0}, {3, 0}, {5, 0}};
+ EraseNodesIf(hashMultiMap, [](auto p) { return p.first % 2 == 0; });
+ UNIT_ASSERT_EQUAL(hashMultiMap, expectedHashMultiMap);
+ }
+
+ Y_UNIT_TEST(NthElementTest) {
+ {
+ TVector<TString> v;
+ NthElement(v.begin(), v.begin(), v.end());
+ UNIT_ASSERT_EQUAL(v, TVector<TString>());
+ }
+
+ {
+ int data[] = {3, 2, 1, 4, 6, 5, 7, 9, 8};
+ TVector<int> testVector(data, data + Y_ARRAY_SIZE(data));
+
+ size_t medianInd = testVector.size() / 2;
+
+ NthElement(testVector.begin(), testVector.begin() + medianInd, testVector.end());
+ UNIT_ASSERT_EQUAL(testVector[medianInd], 5);
+
+ NthElement(testVector.begin(), testVector.begin() + medianInd, testVector.end(), [](int lhs, int rhs) { return lhs > rhs; });
+ UNIT_ASSERT_EQUAL(testVector[medianInd], 5);
+ }
+
+ {
+ const char* data[] = {"3", "234", "1231", "333", "545345", "11", "111", "55", "66"};
+ TVector<TString> testVector(data, data + Y_ARRAY_SIZE(data));
+
+ size_t medianInd = testVector.size() / 2;
+ NthElement(testVector.begin(), testVector.begin() + medianInd, testVector.end());
+
+ auto median = testVector.begin() + medianInd;
+ for (auto it0 = testVector.begin(); it0 != median; ++it0) {
+ for (auto it1 = median; it1 != testVector.end(); ++it1) {
+ UNIT_ASSERT(*it0 <= *it1);
+ }
+ }
+ }
+ }
+
+ Y_UNIT_TEST(BinarySearchTest) {
+ {
+ TVector<TString> v;
+ bool test = BinarySearch(v.begin(), v.end(), "test");
+ UNIT_ASSERT_EQUAL(test, false);
+ }
+
+ {
+ int data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ bool test = BinarySearch(data, data + Y_ARRAY_SIZE(data), 2);
+ UNIT_ASSERT_EQUAL(test, true);
+
+ test = BinarySearch(data, data + Y_ARRAY_SIZE(data), 10);
+ UNIT_ASSERT_EQUAL(test, false);
+ }
+
+ {
+ TVector<size_t> data = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+
+ bool test = BinarySearch(data.begin(), data.end(), (size_t)9, TGreater<size_t>());
+ UNIT_ASSERT_EQUAL(test, true);
+
+ test = BinarySearch(data.begin(), data.end(), (size_t)11, TGreater<size_t>());
+ UNIT_ASSERT_EQUAL(test, false);
+
+ test = BinarySearch(data.rbegin(), data.rend(), (size_t)1);
+ UNIT_ASSERT_EQUAL(test, true);
+ }
+ }
+
+ Y_UNIT_TEST(EqualRangeTest) {
+ {
+ TVector<TString> v;
+ using PairOfVector = std::pair<TVector<TString>::iterator, TVector<TString>::iterator>;
+ PairOfVector tmp = EqualRange(v.begin(), v.end(), "tmp");
+
+ UNIT_ASSERT_EQUAL(tmp.first, tmp.second);
+ UNIT_ASSERT_EQUAL(tmp.first, v.end());
+ }
+
+ {
+ int data[] = {1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 5};
+ using PairOfInt = std::pair<int*, int*>;
+ PairOfInt tmp = EqualRange(data, data + Y_ARRAY_SIZE(data), 3);
+
+ UNIT_ASSERT_EQUAL(tmp.second - tmp.first, 4);
+ UNIT_ASSERT_EQUAL(tmp.first - data, 7);
+ UNIT_ASSERT_EQUAL(data + Y_ARRAY_SIZE(data) - tmp.second, 2);
+ }
+
+ {
+ TVector<size_t> data = {9, 9, 8, 8, 8, 5, 4, 3, 3, 0, 0};
+
+ using PairOfVector = std::pair<TVector<size_t>::iterator, TVector<size_t>::iterator>;
+ PairOfVector tmp = EqualRange(data.begin(), data.end(), 8, TGreater<size_t>());
+
+ UNIT_ASSERT_EQUAL(tmp.first - data.begin(), 2);
+ UNIT_ASSERT_EQUAL(tmp.second - tmp.first, 3);
+
+ using PairOfVectorReverse = std::pair<TVector<size_t>::reverse_iterator, TVector<size_t>::reverse_iterator>;
+ PairOfVectorReverse tmpR = EqualRange(data.rbegin(), data.rend(), (size_t)0);
+
+ UNIT_ASSERT_EQUAL(tmpR.first, data.rbegin());
+ UNIT_ASSERT_EQUAL(tmpR.second - tmpR.first, 2);
+ }
+ }
+
+ Y_UNIT_TEST(IsSortedTest) {
+ TVector<int> v0;
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v0.begin(), v0.end()), true);
+
+ TVector<int> v1 = {1, 2, 3, 4, 5, 5, 5, 6, 6, 7, 8};
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v1.begin(), v1.end()), true);
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v1.begin(), v1.end(), TLess<int>()), true);
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v1.begin(), v1.end(), TGreater<int>()), false);
+
+ TVector<int> v2 = {1, 2, 1};
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v2.begin(), v2.end()), false);
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v2.begin(), v2.end(), TLess<int>()), false);
+ UNIT_ASSERT_VALUES_EQUAL(IsSorted(v2.begin(), v2.end(), TGreater<int>()), false);
+ }
+
+ Y_UNIT_TEST(IsSortedByTest) {
+ TVector<int> v0;
+ UNIT_ASSERT_VALUES_EQUAL(IsSortedBy(v0.begin(), v0.end(), std::negate<int>()), true);
+
+ TVector<int> v1 = {1};
+ UNIT_ASSERT_VALUES_EQUAL(IsSortedBy(v1.begin(), v1.end(), std::negate<int>()), true);
+
+ TVector<int> v2 = {8, 7, 6, 6, 5, 5, 5, 4, 3, 2, 1};
+ UNIT_ASSERT_VALUES_EQUAL(IsSortedBy(v2.begin(), v2.end(), std::negate<int>()), true);
+
+ TVector<int> v3 = {1, 2, 1};
+ UNIT_ASSERT_VALUES_EQUAL(IsSortedBy(v3.begin(), v3.end(), std::negate<int>()), false);
+ }
+
+ Y_UNIT_TEST(SortTestTwoIterators) {
+ TVector<int> collection = {10, 2, 7};
+ Sort(collection.begin(), collection.end());
+ TVector<int> expected = {2, 7, 10};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(SortTestTwoIteratorsAndComparator) {
+ TVector<int> collection = {10, 2, 7};
+ Sort(collection.begin(), collection.end(), [](int l, int r) { return l > r; });
+ TVector<int> expected = {10, 7, 2};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(SortTestContainer) {
+ TVector<int> collection = {10, 2, 7};
+ Sort(collection);
+ TVector<int> expected = {2, 7, 10};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(SortTestContainerAndComparator) {
+ TVector<int> collection = {10, 2, 7};
+ Sort(collection, [](int l, int r) { return l > r; });
+ TVector<int> expected = {10, 7, 2};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(StableSortTestTwoIterators) {
+ TVector<int> collection = {10, 2, 7};
+ StableSort(collection.begin(), collection.end());
+ TVector<int> expected = {2, 7, 10};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(StableSortTestTwoIteratorsAndComparator) {
+ TVector<int> collection = {404, 101, 106, 203, 102, 205, 401};
+ StableSort(collection.begin(), collection.end(), [](int l, int r) { return (l / 100) < (r / 100); });
+ TVector<int> expected = {101, 106, 102, 203, 205, 404, 401};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(StableSortTestContainer) {
+ TVector<int> collection = {10, 2, 7};
+ StableSort(collection);
+ TVector<int> expected = {2, 7, 10};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(StableSortTestContainerAndComparator) {
+ TVector<int> collection = {404, 101, 106, 203, 102, 205, 401};
+ StableSort(collection, [](int l, int r) { return (l / 100) < (r / 100); });
+ TVector<int> expected = {101, 106, 102, 203, 205, 404, 401};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(SortByTest) {
+ TVector<int> collection = {10, 2, 7};
+ SortBy(collection, [](int x) { return -x; });
+ TVector<int> expected = {10, 7, 2};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(StableSortByTest) {
+ TVector<int> collection = {404, 101, 106, 203, 102, 205, 401};
+ StableSortBy(collection, [](int x) { return x / 100; });
+ TVector<int> expected = {101, 106, 102, 203, 205, 404, 401};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(SortUniqueByTest) {
+ TVector<int> collection = {404, 101, 101, 203, 101, 203, 404};
+ StableSortUniqueBy(collection, [](int x) { return x / 100; });
+ TVector<int> expected = {101, 203, 404};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(StableSortUniqueByTest) {
+ TVector<int> collection = {404, 101, 106, 203, 102, 205, 401};
+ StableSortUniqueBy(collection, [](int x) { return x / 100; });
+ TVector<int> expected = {101, 203, 404};
+ UNIT_ASSERT_VALUES_EQUAL(collection, expected);
+ }
+
+ Y_UNIT_TEST(IotaTest) {
+ TVector<int> v(10);
+
+ Iota(v.begin(), v.end(), 0);
+ UNIT_ASSERT_VALUES_EQUAL(v[0], 0);
+ UNIT_ASSERT_VALUES_EQUAL(v[5], 5);
+ UNIT_ASSERT_VALUES_EQUAL(v[9], 9);
+
+ Iota(v.begin() + 2, v.begin() + 5, 162);
+ UNIT_ASSERT_VALUES_EQUAL(v[0], 0);
+ UNIT_ASSERT_VALUES_EQUAL(v[3], 163);
+ UNIT_ASSERT_VALUES_EQUAL(v[9], 9);
+ }
+
+ Y_UNIT_TEST(CopyNTest) {
+ int data[] = {1, 2, 3, 4, 8, 7, 6, 5};
+ const size_t vSize = 10;
+ TVector<int> result(10, 0);
+ size_t toCopy = 5;
+
+ TVector<int>::iterator iter = CopyN(data, toCopy, result.begin());
+ UNIT_ASSERT_VALUES_EQUAL(iter - result.begin(), toCopy);
+ UNIT_ASSERT_VALUES_EQUAL(result.size(), 10);
+ for (size_t idx = 0; idx < toCopy; ++idx) {
+ UNIT_ASSERT_VALUES_EQUAL(data[idx], result[idx]);
+ }
+ for (size_t idx = toCopy; idx < vSize; ++idx) {
+ UNIT_ASSERT_VALUES_EQUAL(result[idx], 0);
+ }
+
+ toCopy = 8;
+ const size_t start = 1;
+ result.assign(vSize, 0);
+ iter = CopyN(data, toCopy, result.begin() + start);
+ UNIT_ASSERT_VALUES_EQUAL(iter - result.begin(), start + toCopy);
+ for (size_t idx = 0; idx < start; ++idx) {
+ UNIT_ASSERT_VALUES_EQUAL(result[idx], 0);
+ }
+ for (size_t idx = 0; idx < toCopy; ++idx) {
+ UNIT_ASSERT_VALUES_EQUAL(result[start + idx], data[idx]);
+ }
+ for (size_t idx = start + toCopy; idx < vSize; ++idx) {
+ UNIT_ASSERT_VALUES_EQUAL(result[idx], 0);
+ }
+ }
+
+ Y_UNIT_TEST(CopyIfTest) {
+ const size_t count = 9;
+ int data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ const size_t vSize = 10;
+ TVector<int> v(vSize, 0);
+
+ TVector<int>::iterator iter = CopyIf(data, data + count, v.begin(), [](int x) { return !(x % 3); });
+ UNIT_ASSERT_VALUES_EQUAL(v.size(), vSize);
+ UNIT_ASSERT_VALUES_EQUAL(iter - v.begin(), 3);
+ v.resize(iter - v.begin());
+ for (size_t idx = 0; idx < v.size(); ++idx) {
+ UNIT_ASSERT_VALUES_EQUAL(v[idx], 3 * (idx + 1));
+ }
+ }
+
+ Y_UNIT_TEST(MinMaxElementTest) {
+ TVector<int> v(10);
+ Iota(v.begin(), v.end(), 0);
+ UNIT_ASSERT_EQUAL(*MinMaxElement(v.begin(), v.end()).first, 0);
+ UNIT_ASSERT_EQUAL(*MinMaxElement(v.begin(), v.end()).second, 9);
+
+ v[3] = -2;
+ v[7] = 11;
+ UNIT_ASSERT_EQUAL(*MinMaxElement(v.begin(), v.end()).first, -2);
+ UNIT_ASSERT_EQUAL(*MinMaxElement(v.begin(), v.end()).second, 11);
+ }
+
+ Y_UNIT_TEST(MinMaxTest) {
+ std::pair<int, int> p1 = MinMax(5, 12);
+ UNIT_ASSERT_EQUAL(p1.first, 5);
+ UNIT_ASSERT_EQUAL(p1.second, 12);
+
+ std::pair<TString, TString> p2 = MinMax(TString("test"), TString("data"));
+ UNIT_ASSERT_EQUAL(p2.first, TString("data"));
+ UNIT_ASSERT_EQUAL(p2.second, TString("test"));
+ }
+
+ Y_UNIT_TEST(TestMaxElementBy) {
+ const int array[] = {1, 2, 5, 3, 4, 5};
+ UNIT_ASSERT_VALUES_EQUAL(*MaxElementBy(array, [](int x) {
+ return x * x;
+ }), 5);
+
+ const TVector<int> vec(array, array + Y_ARRAY_SIZE(array));
+ UNIT_ASSERT_VALUES_EQUAL(*MaxElementBy(vec, [](int x) {
+ return -1.0 * x;
+ }), 1);
+
+ int arrayMutable[] = {1, 2, 5, 3, 4, 5};
+ auto maxPtr = MaxElementBy(arrayMutable, [](int x) { return x; });
+ *maxPtr += 100;
+ UNIT_ASSERT_VALUES_EQUAL(*maxPtr, 105);
+
+ auto identity = [](char x) {
+ return x;
+ };
+ auto singleElementSequence = {'z'};
+ UNIT_ASSERT_VALUES_EQUAL(*MaxElementBy(singleElementSequence, identity), 'z');
+
+ const TString strings[] = {"one", "two", "three", "four"};
+ UNIT_ASSERT_STRINGS_EQUAL(*MaxElementBy(strings, [](TString s) { return s.size(); }), "three");
+ }
+
+ Y_UNIT_TEST(TestMinElementBy) {
+ const int array[] = {2, 3, 4, 1, 5};
+ UNIT_ASSERT_VALUES_EQUAL(*MinElementBy(array, [](int x) -> char {
+ return 'a' + x;
+ }), 1);
+
+ const TVector<int> vec(std::begin(array), std::end(array));
+ UNIT_ASSERT_VALUES_EQUAL(*MinElementBy(vec, [](int x) {
+ return -x;
+ }), 5);
+
+ int arrayMutable[] = {1, 2, 5, 3, 4, 5};
+ auto minPtr = MinElementBy(arrayMutable, [](int x) { return x; });
+ *minPtr += 100;
+ UNIT_ASSERT_VALUES_EQUAL(*minPtr, 101);
+
+ auto identity = [](char x) {
+ return x;
+ };
+ auto singleElementSequence = {'z'};
+ UNIT_ASSERT_VALUES_EQUAL(*MinElementBy(singleElementSequence, identity), 'z');
+
+ const TVector<TStringBuf> strings = {"one", "two", "three", "four"};
+ auto stringLength = [](TStringBuf s) {
+ return s.size();
+ };
+ UNIT_ASSERT_STRINGS_EQUAL(*MinElementBy(strings, stringLength), "one");
+ UNIT_ASSERT_STRINGS_EQUAL(*MinElementBy(strings.rbegin(), strings.rend(), stringLength), "two");
+ }
+
+ Y_UNIT_TEST(MaxElementByReturnsEndForEmptyRange) {
+ const TVector<int> empty;
+ UNIT_ASSERT_EQUAL(MaxElementBy(empty, [](int) { return 0; }), empty.end());
+ }
+
+ Y_UNIT_TEST(MaxElementByDoesntCallFunctorForEmptyRange) {
+ const TVector<int> empty;
+ auto functor = [](int) {
+ UNIT_ASSERT(false);
+ return 0;
+ };
+ MaxElementBy(empty, functor);
+ }
+
+ Y_UNIT_TEST(MinElementByReturnsEndForEmptyRange) {
+ const TVector<int> empty;
+ UNIT_ASSERT_EQUAL(MinElementBy(empty, [](int) { return 0; }), empty.end());
+ }
+
+ Y_UNIT_TEST(MinElementByDoesntCallFunctorForEmptyRange) {
+ const TVector<int> empty;
+ auto functor = [](int) {
+ UNIT_ASSERT(false);
+ return 0;
+ };
+ MinElementBy(empty, functor);
+ }
+
+ Y_UNIT_TEST(TestApplyToMany) {
+ int res = 0;
+ ApplyToMany([&res](auto v) { res += v; }, 1, 2, 3, 4, 5);
+ UNIT_ASSERT_EQUAL(res, 15);
+
+ struct TVisitor {
+ TVisitor(int& acc)
+ : Acc(acc)
+ {
+ }
+ void operator()(const TString& s) {
+ Acc += s.size();
+ };
+ void operator()(int v) {
+ Acc += v * 2;
+ };
+ int& Acc;
+ };
+ TString s{"8-800-555-35-35"};
+ ApplyToMany(TVisitor{res = 0}, 1, s, 5, s);
+ UNIT_ASSERT_EQUAL(res, 12 + 2 * static_cast<int>(s.size()));
+ }
+
+ Y_UNIT_TEST(TestTupleForEach) {
+ ForEach(std::tuple<>{}, [&](auto) { UNIT_ASSERT(false); });
+ auto t = std::make_tuple(5, 6, 2, 3, 6);
+ ForEach(t, [](auto& v) { v *= -1; });
+ UNIT_ASSERT_EQUAL(t, std::make_tuple(-5, -6, -2, -3, -6));
+ }
+
+ Y_UNIT_TEST(TestTupleAllOf) {
+ UNIT_ASSERT(AllOf(std::tuple<>{}, [](auto) { return false; }));
+ UNIT_ASSERT(!AllOf(std::make_tuple(1, 2, 0, 4, 5), [&](auto v) { UNIT_ASSERT_LT(v, 3); return 0 != v; }));
+ UNIT_ASSERT(AllOf(std::make_tuple(1, 2, 3, 4, 5), [](auto v) { return 0 != v; }));
+ {
+ auto pred = std::function<bool(int)>([x = TVector<int>(1, 0)](auto v) { return x.front() != v; });
+ UNIT_ASSERT(AllOf(std::make_tuple(1, 2), pred));
+ UNIT_ASSERT(AllOf(std::make_tuple(1, 2), pred));
+ }
+ {
+ auto ts = std::make_tuple(TString{"foo"}, TString{"bar"});
+ auto pred = [](auto s) { return s.size() == 3; };
+ UNIT_ASSERT_VALUES_EQUAL(AllOf(ts, pred), AllOf(ts, pred));
+ }
+ }
+
+ Y_UNIT_TEST(TestTupleAnyOf) {
+ UNIT_ASSERT(!AnyOf(std::tuple<>{}, [](auto) { return true; }));
+ UNIT_ASSERT(AnyOf(std::make_tuple(0, 1, 2, 3, 4), [&](auto v) { UNIT_ASSERT_LT(v, 2); return 1 == v; }));
+ UNIT_ASSERT(AnyOf(std::make_tuple(1, 2, 3, 4, 5), [](auto v) { return 5 == v; }));
+ auto pred = std::function<bool(int)>([x = TVector<int>(1, 0)](auto v) { return x.front() == v; });
+ UNIT_ASSERT(!AnyOf(std::make_tuple(1, 2), pred));
+ UNIT_ASSERT(!AnyOf(std::make_tuple(1, 2), pred));
+ {
+ auto ts = std::make_tuple(TString{"f"}, TString{"bar"});
+ auto pred = [](auto s) { return s.size() == 3; };
+ UNIT_ASSERT_VALUES_EQUAL(AnyOf(ts, pred), AnyOf(ts, pred));
+ }
+ }
+
+ Y_UNIT_TEST(FindIfForContainer) {
+ using std::begin;
+ using std::end;
+
+ int array[] = {1, 2, 3, 4, 5};
+ UNIT_ASSERT_EQUAL(FindIf(array, [](int x) { return x == 1; }), begin(array));
+ UNIT_ASSERT_EQUAL(FindIf(array, [](int x) { return x > 5; }), end(array));
+
+ TVector<int> vector = {1, 2, 3, 4, 5};
+ UNIT_ASSERT_EQUAL(FindIf(vector, [](int x) { return x == 1; }), begin(vector));
+ UNIT_ASSERT_EQUAL(FindIf(vector, [](int x) { return x > 5; }), end(vector));
+
+ // Compilability test. Check if the returned iterator is non const
+ auto iter = FindIf(vector, [](int x) { return x == 1; });
+ *iter = 5;
+
+ // Compilability test. Check if the returned iterator is const. Should not compile
+ const TVector<int> constVector = {1, 2, 3, 4, 5};
+ auto constIter = FindIf(constVector, [](int x) { return x == 1; });
+ Y_UNUSED(constIter);
+ // *constIter = 5;
+ }
+
+ struct TRange {
+ };
+
+ const TRange* begin(const TRange& r) {
+ return &r;
+ }
+
+ const TRange* end(const TRange& r) {
+ return &r + 1;
+ }
+
+ Y_UNIT_TEST(FindIfForUserType) {
+ // Compileability test. Should work for user types with begin/end overloads
+ TRange range;
+ auto i = FindIf(range, [](auto) { return false; });
+ Y_UNUSED(i);
+ }
+
+ Y_UNIT_TEST(TestLowerBoundBy) {
+ using TIntPairs = TVector<std::pair<i32, i32>>;
+
+ auto data = TIntPairs{{1, 5}, {3, 2}, {3, 4}, {8, 0}, {5, 4}};
+ auto getKey = [](const auto& x) { return x.second; };
+
+ StableSortBy(data, getKey);
+
+ auto it = LowerBoundBy(data.begin(), data.end(), 4, getKey);
+ UNIT_ASSERT(it != data.end());
+ UNIT_ASSERT_EQUAL(it->second, 4);
+ UNIT_ASSERT_EQUAL(it->first, 3);
+
+ UNIT_ASSERT(it > data.begin());
+ UNIT_ASSERT_EQUAL((it - 1)->second, 2);
+
+ UNIT_ASSERT((it + 1) < data.end());
+ UNIT_ASSERT_EQUAL((it + 1)->second, 4);
+ }
+
+ Y_UNIT_TEST(TestUpperBoundBy) {
+ using TIntPairs = TVector<std::pair<i32, i32>>;
+
+ auto data = TIntPairs{{1, 5}, {3, 2}, {3, 4}, {8, 0}, {5, 4}};
+ auto getKey = [](const auto& x) { return x.second; };
+
+ StableSortBy(data, getKey);
+
+ auto it = UpperBoundBy(data.begin(), data.end(), 4, getKey);
+ UNIT_ASSERT(it != data.end());
+ UNIT_ASSERT_EQUAL(it->second, 5);
+ UNIT_ASSERT_EQUAL(it->first, 1);
+
+ UNIT_ASSERT(it > data.begin());
+ UNIT_ASSERT_EQUAL((it - 1)->second, 4);
+
+ UNIT_ASSERT((it + 1) == data.end());
+ }
+
+ Y_UNIT_TEST(TestFindInContainer) {
+ std::vector<int> v = {1, 2, 1000, 15, 100};
+ UNIT_ASSERT(Find(v, 5) == v.end());
+ UNIT_ASSERT(Find(v, 1) == v.begin());
+ UNIT_ASSERT(Find(v, 100) == v.end() - 1);
+ }
+
+ Y_UNIT_TEST(AccumulateWithBinOp) {
+ std::vector<int> v = {1, 2, 777};
+ UNIT_ASSERT_VALUES_EQUAL(TString("begin;1;2;777"), Accumulate(v, TString("begin"), [](auto&& a, auto& b) { return a + ";" + ToString(b); }));
+ }
+};
diff --git a/util/generic/array_ref.cpp b/util/generic/array_ref.cpp
new file mode 100644
index 0000000000..e5b6a3c1a6
--- /dev/null
+++ b/util/generic/array_ref.cpp
@@ -0,0 +1 @@
+#include "array_ref.h"
diff --git a/util/generic/array_ref.h b/util/generic/array_ref.h
new file mode 100644
index 0000000000..1ac60ac7d3
--- /dev/null
+++ b/util/generic/array_ref.h
@@ -0,0 +1,280 @@
+#pragma once
+
+#include <util/generic/yexception.h>
+
+#include <algorithm>
+#include <initializer_list>
+#include <iterator>
+
+/**
+ * `TArrayRef` works pretty much like `std::span` with dynamic extent, presenting
+ * an array-like interface into a contiguous sequence of objects.
+ *
+ * It can be used at interface boundaries instead of `TVector` or
+ * pointer-size pairs, and is actually a preferred way to pass contiguous data
+ * into functions.
+ *
+ * Note that `TArrayRef` can be auto-constructed from any contiguous container
+ * (with `size` and `data` members), and thus you don't have to change client code
+ * when switching over from passing `TVector` to `TArrayRef`.
+ *
+ * Note that `TArrayRef` has the same const-semantics as raw pointers:
+ * - `TArrayRef<T>` is a non-const reference to non-const data (like `T*`);
+ * - `TArrayRef<const T>` is a non-const reference to const data (like `const T*`);
+ * - `const TArrayRef<T>` is a const reference to non-const data (like `T* const`);
+ * - `const TArrayRef<const T>` is a const reference to const data (like `const T* const`).
+ */
+template <class T>
+class TArrayRef {
+public:
+ using iterator = T*;
+ using const_iterator = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+ using value_type = T;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ constexpr inline TArrayRef() noexcept
+ : T_(nullptr)
+ , S_(0)
+ {
+ }
+
+ constexpr inline TArrayRef(T* data, size_t len) noexcept
+ : T_(data)
+ , S_(len)
+ {
+ }
+
+ constexpr inline TArrayRef(T* begin, T* end) noexcept
+ : T_(begin)
+ , S_(end - begin)
+ {
+ }
+
+ constexpr inline TArrayRef(std::initializer_list<T> list) noexcept
+ : T_(list.begin())
+ , S_(list.size())
+ {
+ }
+
+ template <class Container>
+ constexpr inline TArrayRef(Container&& container, decltype(std::declval<T*&>() = container.data(), nullptr) = nullptr) noexcept
+ : T_(container.data())
+ , S_(container.size())
+ {
+ }
+
+ template <size_t N>
+ constexpr inline TArrayRef(T (&array)[N]) noexcept
+ : T_(array)
+ , S_(N)
+ {
+ }
+
+ template <class TT, typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, std::remove_const_t<TT>>::value>>
+ bool operator==(const TArrayRef<TT>& other) const noexcept {
+ return (S_ == other.size()) && std::equal(begin(), end(), other.begin());
+ }
+
+ constexpr inline T* data() const noexcept {
+ return T_;
+ }
+
+ constexpr inline size_t size() const noexcept {
+ return S_;
+ }
+
+ constexpr size_t size_bytes() const noexcept {
+ return (size() * sizeof(T));
+ }
+
+ constexpr inline bool empty() const noexcept {
+ return (S_ == 0);
+ }
+
+ constexpr inline iterator begin() const noexcept {
+ return T_;
+ }
+
+ constexpr inline iterator end() const noexcept {
+ return (T_ + S_);
+ }
+
+ constexpr inline const_iterator cbegin() const noexcept {
+ return T_;
+ }
+
+ constexpr inline const_iterator cend() const noexcept {
+ return (T_ + S_);
+ }
+
+ constexpr inline reverse_iterator rbegin() const noexcept {
+ return reverse_iterator(T_ + S_);
+ }
+
+ constexpr inline reverse_iterator rend() const noexcept {
+ return reverse_iterator(T_);
+ }
+
+ constexpr inline const_reverse_iterator crbegin() const noexcept {
+ return const_reverse_iterator(T_ + S_);
+ }
+
+ constexpr inline const_reverse_iterator crend() const noexcept {
+ return const_reverse_iterator(T_);
+ }
+
+ constexpr inline reference front() const noexcept {
+ return *T_;
+ }
+
+ inline reference back() const noexcept {
+ Y_ASSERT(S_ > 0);
+
+ return *(end() - 1);
+ }
+
+ inline reference operator[](size_t n) const noexcept {
+ Y_ASSERT(n < S_);
+
+ return *(T_ + n);
+ }
+
+ inline reference at(size_t n) const {
+ if (n >= S_) {
+ throw std::out_of_range("array ref range error");
+ }
+
+ return (*this)[n];
+ }
+
+ constexpr inline explicit operator bool() const noexcept {
+ return (S_ > 0);
+ }
+
+ /**
+ * Obtains a ref that is a view over the first `count` elements of this TArrayRef.
+ *
+ * The behavior is undefined if count > size().
+ */
+ TArrayRef first(size_t count) const {
+ Y_ASSERT(count <= size());
+ return TArrayRef(data(), count);
+ }
+
+ /**
+ * Obtains a ref that is a view over the last `count` elements of this TArrayRef.
+ *
+ * The behavior is undefined if count > size().
+ */
+ TArrayRef last(size_t count) const {
+ Y_ASSERT(count <= size());
+ return TArrayRef(end() - count, end());
+ }
+
+ /**
+ * Obtains a ref that is a view over the `count` elements of this TArrayRef starting at `offset`.
+ *
+ * The behavior is undefined in either offset or count is out of range.
+ */
+ TArrayRef subspan(size_t offset) const {
+ Y_ASSERT(offset <= size());
+ return TArrayRef(data() + offset, size() - offset);
+ }
+
+ TArrayRef subspan(size_t offset, size_t count) const {
+ Y_ASSERT(offset + count <= size());
+ return TArrayRef(data() + offset, count);
+ }
+
+ TArrayRef Slice(size_t offset) const {
+ return subspan(offset);
+ }
+
+ TArrayRef Slice(size_t offset, size_t size) const {
+ return subspan(offset, size);
+ }
+
+ /* FIXME:
+ * This method is placed here for backward compatibility only and should be removed.
+ * Keep in mind that it's behavior is different from Slice():
+ * SubRegion() never throws. It returns empty TArrayRef in case of invalid input.
+ *
+ * DEPRECATED. DO NOT USE.
+ */
+ TArrayRef SubRegion(size_t offset, size_t size) const {
+ if (size == 0 || offset >= S_) {
+ return TArrayRef();
+ }
+
+ if (size > S_ - offset) {
+ size = S_ - offset;
+ }
+
+ return TArrayRef(T_ + offset, size);
+ }
+
+ constexpr inline yssize_t ysize() const noexcept {
+ return static_cast<yssize_t>(this->size());
+ }
+
+private:
+ T* T_;
+ size_t S_;
+};
+
+/**
+ * Obtains a view to the object representation of the elements of the TArrayRef arrayRef.
+ *
+ * Named as its std counterparts, std::as_bytes.
+ */
+template <typename T>
+TArrayRef<const char> as_bytes(TArrayRef<T> arrayRef) noexcept {
+ return TArrayRef<const char>(
+ reinterpret_cast<const char*>(arrayRef.data()),
+ arrayRef.size_bytes());
+}
+
+/**
+ * Obtains a view to the writable object representation of the elements of the TArrayRef arrayRef.
+ *
+ * Named as its std counterparts, std::as_writable_bytes.
+ */
+template <typename T>
+TArrayRef<char> as_writable_bytes(TArrayRef<T> arrayRef) noexcept {
+ return TArrayRef<char>(
+ reinterpret_cast<char*>(arrayRef.data()),
+ arrayRef.size_bytes());
+}
+
+template <class Range>
+constexpr TArrayRef<const typename Range::value_type> MakeArrayRef(const Range& range) {
+ return TArrayRef<const typename Range::value_type>(range);
+}
+
+template <class Range>
+constexpr TArrayRef<typename Range::value_type> MakeArrayRef(Range& range) {
+ return TArrayRef<typename Range::value_type>(range);
+}
+
+template <class Range>
+constexpr TArrayRef<const typename Range::value_type> MakeConstArrayRef(const Range& range) {
+ return TArrayRef<const typename Range::value_type>(range);
+}
+
+template <class Range>
+constexpr TArrayRef<const typename Range::value_type> MakeConstArrayRef(Range& range) {
+ return TArrayRef<const typename Range::value_type>(range);
+}
+
+template <class T>
+constexpr TArrayRef<T> MakeArrayRef(T* data, size_t size) {
+ return TArrayRef<T>(data, size);
+}
+
+template <class T>
+constexpr TArrayRef<T> MakeArrayRef(T* begin, T* end) {
+ return TArrayRef<T>(begin, end);
+}
diff --git a/util/generic/array_ref.pxd b/util/generic/array_ref.pxd
new file mode 100644
index 0000000000..41a4c72d69
--- /dev/null
+++ b/util/generic/array_ref.pxd
@@ -0,0 +1,25 @@
+from libcpp cimport bool as bool_t
+
+
+cdef extern from "util/generic/array_ref.h" nogil:
+ cdef cppclass TArrayRef[T]:
+ TArrayRef(...) except +
+
+ T& operator[](size_t)
+
+ bool_t empty()
+ T* data() except +
+ size_t size() except +
+ T* begin() except +
+ T* end() except +
+
+ cdef cppclass TConstArrayRef[T]:
+ TConstArrayRef(...) except +
+
+ const T& operator[](size_t)
+
+ bool_t empty()
+ const T* data() except +
+ size_t size() except +
+ const T* begin() except +
+ const T* end() except +
diff --git a/util/generic/array_ref_ut.cpp b/util/generic/array_ref_ut.cpp
new file mode 100644
index 0000000000..4c8eaf7135
--- /dev/null
+++ b/util/generic/array_ref_ut.cpp
@@ -0,0 +1,322 @@
+#include "array_ref.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TestArrayRef) {
+ Y_UNIT_TEST(TestDefaultConstructor) {
+ TArrayRef<int> defaulted;
+ UNIT_ASSERT_VALUES_EQUAL(defaulted.data(), nullptr);
+ UNIT_ASSERT_VALUES_EQUAL(defaulted.size(), 0u);
+ }
+
+ Y_UNIT_TEST(TestConstructorFromArray) {
+ int x[] = {10, 20, 30};
+ TArrayRef<int> ref(x);
+ UNIT_ASSERT_VALUES_EQUAL(3u, ref.size());
+ UNIT_ASSERT_VALUES_EQUAL(30, ref[2]);
+ ref[2] = 50;
+ UNIT_ASSERT_VALUES_EQUAL(50, x[2]);
+
+ TArrayRef<const int> constRef(x);
+ UNIT_ASSERT_VALUES_EQUAL(3u, constRef.size());
+ UNIT_ASSERT_VALUES_EQUAL(50, constRef[2]);
+ ref[0] = 100;
+ UNIT_ASSERT_VALUES_EQUAL(constRef[0], 100);
+ }
+
+ Y_UNIT_TEST(TestAccessingElements) {
+ int a[]{1, 2, 3};
+ TArrayRef<int> ref(a);
+
+ UNIT_ASSERT_VALUES_EQUAL(ref[0], 1);
+ UNIT_ASSERT_VALUES_EQUAL(ref.at(0), 1);
+
+ ref[0] = 5;
+ UNIT_ASSERT_VALUES_EQUAL(a[0], 5);
+
+ //FIXME: size checks are implemented via Y_ASSERT, hence there is no way to test them
+ }
+
+ Y_UNIT_TEST(TestFrontBack) {
+ const int x[] = {1, 2, 3};
+ const TArrayRef<const int> rx{x};
+ UNIT_ASSERT_VALUES_EQUAL(rx.front(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(rx.back(), 3);
+
+ int y[] = {1, 2, 3};
+ TArrayRef<int> ry{y};
+ UNIT_ASSERT_VALUES_EQUAL(ry.front(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(ry.back(), 3);
+
+ ry.front() = 100;
+ ry.back() = 500;
+ UNIT_ASSERT_VALUES_EQUAL(ry.front(), 100);
+ UNIT_ASSERT_VALUES_EQUAL(ry.back(), 500);
+ UNIT_ASSERT_VALUES_EQUAL(y[0], 100);
+ UNIT_ASSERT_VALUES_EQUAL(y[2], 500);
+ }
+
+ Y_UNIT_TEST(TestIterator) {
+ int array[] = {17, 19, 21};
+ TArrayRef<int> r(array, 3);
+
+ TArrayRef<int>::iterator iterator = r.begin();
+ for (auto& i : array) {
+ UNIT_ASSERT(iterator != r.end());
+ UNIT_ASSERT_VALUES_EQUAL(i, *iterator);
+ ++iterator;
+ }
+ UNIT_ASSERT(iterator == r.end());
+ }
+
+ Y_UNIT_TEST(TestReverseIterators) {
+ const int x[] = {1, 2, 3};
+ const TArrayRef<const int> rx{x};
+ auto i = rx.crbegin();
+ UNIT_ASSERT_VALUES_EQUAL(*i, 3);
+ ++i;
+ UNIT_ASSERT_VALUES_EQUAL(*i, 2);
+ ++i;
+ UNIT_ASSERT_VALUES_EQUAL(*i, 1);
+ ++i;
+ UNIT_ASSERT_EQUAL(i, rx.crend());
+ }
+
+ Y_UNIT_TEST(TestConstIterators) {
+ int x[] = {1, 2, 3};
+ TArrayRef<int> rx{x};
+ UNIT_ASSERT_EQUAL(rx.begin(), rx.cbegin());
+ UNIT_ASSERT_EQUAL(rx.end(), rx.cend());
+ UNIT_ASSERT_EQUAL(rx.rbegin(), rx.crbegin());
+ UNIT_ASSERT_EQUAL(rx.rend(), rx.crend());
+
+ int w[] = {1, 2, 3};
+ const TArrayRef<int> rw{w};
+ UNIT_ASSERT_EQUAL(rw.begin(), rw.cbegin());
+ UNIT_ASSERT_EQUAL(rw.end(), rw.cend());
+ UNIT_ASSERT_EQUAL(rw.rbegin(), rw.crbegin());
+ UNIT_ASSERT_EQUAL(rw.rend(), rw.crend());
+
+ int y[] = {1, 2, 3};
+ TArrayRef<const int> ry{y};
+ UNIT_ASSERT_EQUAL(ry.begin(), ry.cbegin());
+ UNIT_ASSERT_EQUAL(ry.end(), ry.cend());
+ UNIT_ASSERT_EQUAL(ry.rbegin(), ry.crbegin());
+ UNIT_ASSERT_EQUAL(ry.rend(), ry.crend());
+
+ const int z[] = {1, 2, 3};
+ TArrayRef<const int> rz{z};
+ UNIT_ASSERT_EQUAL(rz.begin(), rz.cbegin());
+ UNIT_ASSERT_EQUAL(rz.end(), rz.cend());
+ UNIT_ASSERT_EQUAL(rz.rbegin(), rz.crbegin());
+ UNIT_ASSERT_EQUAL(rz.rend(), rz.crend());
+
+ const int q[] = {1, 2, 3};
+ const TArrayRef<const int> rq{q};
+ UNIT_ASSERT_EQUAL(rq.begin(), rq.cbegin());
+ UNIT_ASSERT_EQUAL(rq.end(), rq.cend());
+ UNIT_ASSERT_EQUAL(rq.rbegin(), rq.crbegin());
+ UNIT_ASSERT_EQUAL(rq.rend(), rq.crend());
+ }
+
+ Y_UNIT_TEST(TestCreatingFromStringLiteral) {
+ TConstArrayRef<char> knownSizeRef("123", 3);
+ size_t ret = 0;
+
+ for (char ch : knownSizeRef) {
+ ret += ch - '0';
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(ret, 6);
+ UNIT_ASSERT_VALUES_EQUAL(knownSizeRef.size(), 3);
+ UNIT_ASSERT_VALUES_EQUAL(knownSizeRef.at(0), '1');
+
+ /*
+ * When TArrayRef is being constructed from string literal,
+ * trailing zero will be added into it.
+ */
+ TConstArrayRef<char> autoSizeRef("456");
+ UNIT_ASSERT_VALUES_EQUAL(autoSizeRef[0], '4');
+ UNIT_ASSERT_VALUES_EQUAL(autoSizeRef[3], '\0');
+ }
+
+ Y_UNIT_TEST(TestEqualityOperator) {
+ static constexpr size_t size = 5;
+ int a[size]{1, 2, 3, 4, 5};
+ int b[size]{5, 4, 3, 2, 1};
+ int c[size - 1]{5, 4, 3, 2};
+ float d[size]{1.f, 2.f, 3.f, 4.f, 5.f};
+
+ TArrayRef<int> aRef(a);
+ TConstArrayRef<int> aConstRef(a, size);
+
+ TArrayRef<int> bRef(b);
+
+ TArrayRef<int> cRef(c, size - 1);
+
+ TArrayRef<float> dRef(d, size);
+ TConstArrayRef<float> dConstRef(d, size);
+
+ UNIT_ASSERT_EQUAL(aRef, aConstRef);
+ UNIT_ASSERT_EQUAL(dRef, dConstRef);
+
+ UNIT_ASSERT_UNEQUAL(aRef, cRef);
+ UNIT_ASSERT_UNEQUAL(aRef, bRef);
+
+ TArrayRef<int> bSubRef(b, size - 1);
+
+ //Testing if operator== compares values, not pointers
+ UNIT_ASSERT_EQUAL(cRef, bSubRef);
+ }
+
+ Y_UNIT_TEST(TestImplicitConstructionFromContainer) {
+ /* Just test compilation. */
+ auto fc = [](TArrayRef<const int>) {};
+ auto fm = [](TArrayRef<int>) {};
+
+ fc(TVector<int>({1}));
+
+ const TVector<int> ac = {1};
+ TVector<int> am = {1};
+
+ fc(ac);
+ fc(am);
+ fm(am);
+ // fm(ac); // This one shouldn't compile.
+ }
+
+ Y_UNIT_TEST(TestFirstLastSubspan) {
+ const int arr[] = {1, 2, 3, 4, 5};
+ TArrayRef<const int> aRef(arr);
+
+ UNIT_ASSERT_EQUAL(aRef.first(2), MakeArrayRef(std::vector<int>{1, 2}));
+ UNIT_ASSERT_EQUAL(aRef.last(2), MakeArrayRef(std::vector<int>{4, 5}));
+ UNIT_ASSERT_EQUAL(aRef.subspan(2), MakeArrayRef(std::vector<int>{3, 4, 5}));
+ UNIT_ASSERT_EQUAL(aRef.subspan(1, 3), MakeArrayRef(std::vector<int>{2, 3, 4}));
+ }
+
+ Y_UNIT_TEST(TestSlice) {
+ const int a0[] = {1, 2, 3};
+ TArrayRef<const int> r0(a0);
+ TArrayRef<const int> s0 = r0.Slice(2);
+
+ UNIT_ASSERT_VALUES_EQUAL(s0.size(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(s0[0], 3);
+
+ const int a1[] = {1, 2, 3, 4};
+ TArrayRef<const int> r1(a1);
+ TArrayRef<const int> s1 = r1.Slice(2, 1);
+
+ UNIT_ASSERT_VALUES_EQUAL(s1.size(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(s1[0], 3);
+
+ //FIXME: size checks are implemented via Y_ASSERT, hence there is no way to test them
+ }
+
+ Y_UNIT_TEST(SubRegion) {
+ TVector<char> x;
+ for (size_t i = 0; i < 42; ++i) {
+ x.push_back('a' + (i * 42424243) % 13);
+ }
+ TArrayRef<const char> ref(x.data(), 42);
+ for (size_t i = 0; i <= 50; ++i) {
+ TVector<char> expected;
+ for (size_t j = 0; j <= 100; ++j) {
+ UNIT_ASSERT(MakeArrayRef(expected) == ref.SubRegion(i, j));
+ if (i + j < 42) {
+ expected.push_back(x[i + j]);
+ }
+ }
+ }
+ }
+
+ Y_UNIT_TEST(TestAsBytes) {
+ const int16_t constArr[] = {1, 2, 3};
+ TArrayRef<const int16_t> constRef(constArr);
+ auto bytesRef = as_bytes(constRef);
+
+ UNIT_ASSERT_VALUES_EQUAL(bytesRef.size(), sizeof(int16_t) * constRef.size());
+ UNIT_ASSERT_EQUAL(
+ bytesRef,
+ MakeArrayRef(std::vector<char>{0x01, 0x00, 0x02, 0x00, 0x03, 0x00}));
+
+ //should not compile
+ //as_writable_bytes(constRef);
+ }
+
+ Y_UNIT_TEST(TestAsWritableBytes) {
+ uint32_t uintArr[] = {0x0c'00'0d'0e};
+ TArrayRef<uint32_t> uintRef(uintArr);
+ auto writableBytesRef = as_writable_bytes(uintRef);
+
+ UNIT_ASSERT_VALUES_EQUAL(writableBytesRef.size(), sizeof(uint32_t));
+ UNIT_ASSERT_EQUAL(
+ writableBytesRef,
+ MakeArrayRef(std::vector<char>{0x0e, 0x0d, 0x00, 0x0c}));
+
+ uint32_t newVal = 0xde'ad'be'ef;
+ std::memcpy(writableBytesRef.data(), &newVal, writableBytesRef.size());
+ UNIT_ASSERT_VALUES_EQUAL(uintArr[0], newVal);
+ }
+
+ Y_UNIT_TEST(TestTypeDeductionViaMakeArrayRef) {
+ TVector<int> vec{17, 19, 21};
+ TArrayRef<int> ref = MakeArrayRef(vec);
+ UNIT_ASSERT_VALUES_EQUAL(21, ref[2]);
+ ref[1] = 23;
+ UNIT_ASSERT_VALUES_EQUAL(23, vec[1]);
+
+ const TVector<int>& constVec(vec);
+ TArrayRef<const int> constRef = MakeArrayRef(constVec);
+ UNIT_ASSERT_VALUES_EQUAL(21, constRef[2]);
+
+ TArrayRef<const int> constRefFromNonConst = MakeArrayRef(vec);
+ UNIT_ASSERT_VALUES_EQUAL(23, constRefFromNonConst[1]);
+ }
+
+ static void Do(const TArrayRef<int> a) {
+ a[0] = 8;
+ }
+
+ Y_UNIT_TEST(TestConst) {
+ int a[] = {1, 2};
+ Do(a);
+ UNIT_ASSERT_VALUES_EQUAL(a[0], 8);
+ }
+
+ Y_UNIT_TEST(TestConstexpr) {
+ static constexpr const int a[] = {1, 2, -3, -4};
+ static constexpr const auto r0 = MakeArrayRef(a, 1);
+ static_assert(r0.size() == 1, "r0.size() is not equal 1");
+ static_assert(r0.data()[0] == 1, "r0.data()[0] is not equal to 1");
+
+ static constexpr const TArrayRef<const int> r1{a};
+ static_assert(r1.size() == 4, "r1.size() is not equal to 4");
+ static_assert(r1.data()[3] == -4, "r1.data()[3] is not equal to -4");
+
+ static constexpr const TArrayRef<const int> r2 = r1;
+ static_assert(r2.size() == 4, "r2.size() is not equal to 4");
+ static_assert(r2.data()[2] == -3, "r2.data()[2] is not equal to -3");
+ }
+
+ template <typename T>
+ static void Foo(const TConstArrayRef<T>) {
+ // noop
+ }
+
+ Y_UNIT_TEST(TestMakeConstArrayRef) {
+ TVector<int> data;
+
+ // Won't compile because can't deduce `T` for `Foo`
+ // Foo(data);
+
+ // Won't compile because again can't deduce `T` for `Foo`
+ // Foo(MakeArrayRef(data));
+
+ // Success!
+ Foo(MakeConstArrayRef(data));
+
+ const TVector<int> constData;
+ Foo(MakeConstArrayRef(constData));
+ }
+}
diff --git a/util/generic/array_ref_ut.pyx b/util/generic/array_ref_ut.pyx
new file mode 100644
index 0000000000..67b69a365c
--- /dev/null
+++ b/util/generic/array_ref_ut.pyx
@@ -0,0 +1,28 @@
+import pytest
+import unittest
+from util.generic.array_ref cimport TArrayRef
+from util.generic.vector cimport TVector
+
+
+class TestArrayRef(unittest.TestCase):
+ def test_array_data_reference(self):
+ array_size = 30
+ cdef TVector[int] vec
+ for i in xrange(array_size):
+ vec.push_back(i)
+ cdef TArrayRef[int] array_ref = TArrayRef[int](vec.data(), vec.size())
+ for i in xrange(array_size / 2):
+ array_ref[array_size - 1 - i] = array_ref[i]
+ for i in xrange(array_size):
+ self.assertEqual(array_ref[i], array_size - 1 - i)
+
+ def test_array_vec_reference(self):
+ array_size = 30
+ cdef TVector[int] vec
+ for i in xrange(array_size):
+ vec.push_back(i)
+ cdef TArrayRef[int] array_ref = TArrayRef[int](vec)
+ for i in xrange(array_size / 2):
+ array_ref[array_size - 1 - i] = array_ref[i]
+ for i in xrange(array_size):
+ self.assertEqual(array_ref[i], array_size - 1 - i) \ No newline at end of file
diff --git a/util/generic/array_size.cpp b/util/generic/array_size.cpp
new file mode 100644
index 0000000000..763f96e4ed
--- /dev/null
+++ b/util/generic/array_size.cpp
@@ -0,0 +1 @@
+#include "array_size.h"
diff --git a/util/generic/array_size.h b/util/generic/array_size.h
new file mode 100644
index 0000000000..4d5f18ce63
--- /dev/null
+++ b/util/generic/array_size.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <cstddef>
+
+namespace NArraySizePrivate {
+ template <class T>
+ struct TArraySize;
+
+ template <class T, size_t N>
+ struct TArraySize<T[N]> {
+ enum {
+ Result = N
+ };
+ };
+
+ template <class T, size_t N>
+ struct TArraySize<T (&)[N]> {
+ enum {
+ Result = N
+ };
+ };
+}
+
+#define Y_ARRAY_SIZE(arr) ((size_t)::NArraySizePrivate::TArraySize<decltype(arr)>::Result)
diff --git a/util/generic/array_size_ut.cpp b/util/generic/array_size_ut.cpp
new file mode 100644
index 0000000000..13f45903c5
--- /dev/null
+++ b/util/generic/array_size_ut.cpp
@@ -0,0 +1,22 @@
+#include "array_size.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(ArraySizeTest) {
+ Y_UNIT_TEST(Test1) {
+ int x[100];
+ Y_UNUSED(x); /* Make MSVC happy. */
+
+ UNIT_ASSERT_VALUES_EQUAL(Y_ARRAY_SIZE(x), 100);
+ }
+
+ Y_UNIT_TEST(Test2) {
+ struct T {
+ };
+
+ T x[1];
+ Y_UNUSED(x); /* Make MSVC happy. */
+
+ UNIT_ASSERT_VALUES_EQUAL(Y_ARRAY_SIZE(x), 1);
+ }
+}
diff --git a/util/generic/benchmark/cont_speed/main.cpp b/util/generic/benchmark/cont_speed/main.cpp
new file mode 100644
index 0000000000..01428c9974
--- /dev/null
+++ b/util/generic/benchmark/cont_speed/main.cpp
@@ -0,0 +1,117 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/xrange.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/buffer.h>
+
+template <class C>
+Y_NO_INLINE void Run(const C& c) {
+ for (size_t i = 0; i < c.size(); ++i) {
+ Y_DO_NOT_OPTIMIZE_AWAY(c[i]);
+ }
+}
+
+template <class C>
+void Do(size_t len, auto& iface) {
+ C c(len, 0);
+
+ for (auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Run(c);
+ }
+}
+
+Y_CPU_BENCHMARK(TVector10, iface) {
+ Do<TVector<char>>(10, iface);
+}
+
+Y_CPU_BENCHMARK(TVector100, iface) {
+ Do<TVector<char>>(100, iface);
+}
+
+Y_CPU_BENCHMARK(TVector1000, iface) {
+ Do<TVector<char>>(1000, iface);
+}
+
+Y_CPU_BENCHMARK(TString10, iface) {
+ Do<TString>(10, iface);
+}
+
+Y_CPU_BENCHMARK(TString100, iface) {
+ Do<TString>(100, iface);
+}
+
+Y_CPU_BENCHMARK(TString1000, iface) {
+ Do<TString>(1000, iface);
+}
+
+Y_CPU_BENCHMARK(StdString10, iface) {
+ Do<std::string>(10, iface);
+}
+
+Y_CPU_BENCHMARK(StdString100, iface) {
+ Do<std::string>(100, iface);
+}
+
+Y_CPU_BENCHMARK(StdString1000, iface) {
+ Do<std::string>(1000, iface);
+}
+
+struct TBuf: public TBuffer {
+ TBuf(size_t len, char v) {
+ for (size_t i = 0; i < len; ++i) {
+ Append(v);
+ }
+ }
+
+ inline const auto& operator[](size_t i) const noexcept {
+ return *(data() + i);
+ }
+};
+
+Y_CPU_BENCHMARK(TBuffer10, iface) {
+ Do<TBuf>(10, iface);
+}
+
+Y_CPU_BENCHMARK(TBuffer100, iface) {
+ Do<TBuf>(100, iface);
+}
+
+Y_CPU_BENCHMARK(TBuffer1000, iface) {
+ Do<TBuf>(1000, iface);
+}
+
+struct TArr {
+ inline TArr(size_t len, char ch)
+ : A(new char[len])
+ , L(len)
+ {
+ for (size_t i = 0; i < L; ++i) {
+ A[i] = ch;
+ }
+ }
+
+ inline const auto& operator[](size_t i) const noexcept {
+ return A[i];
+ }
+
+ inline size_t size() const noexcept {
+ return L;
+ }
+
+ char* A;
+ size_t L;
+};
+
+Y_CPU_BENCHMARK(Pointer10, iface) {
+ Do<TArr>(10, iface);
+}
+
+Y_CPU_BENCHMARK(Pointer100, iface) {
+ Do<TArr>(100, iface);
+}
+
+Y_CPU_BENCHMARK(Pointer1000, iface) {
+ Do<TArr>(1000, iface);
+}
diff --git a/util/generic/benchmark/cont_speed/ya.make b/util/generic/benchmark/cont_speed/ya.make
new file mode 100644
index 0000000000..6ff3fe767c
--- /dev/null
+++ b/util/generic/benchmark/cont_speed/ya.make
@@ -0,0 +1,9 @@
+Y_BENCHMARK()
+
+OWNER(g:util)
+
+SRCS(
+ main.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/fastclp2/main.cpp b/util/generic/benchmark/fastclp2/main.cpp
new file mode 100644
index 0000000000..49277db077
--- /dev/null
+++ b/util/generic/benchmark/fastclp2/main.cpp
@@ -0,0 +1,50 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/bitops.h>
+#include <util/generic/vector.h>
+#include <util/generic/xrange.h>
+#include <util/generic/singleton.h>
+
+#include <util/random/fast.h>
+
+namespace {
+ template <typename T, size_t N>
+ struct TExamplesHolder {
+ TExamplesHolder()
+ : Examples(N)
+ {
+ TFastRng<ui64> prng{42u * sizeof(T) * N};
+ for (auto& x : Examples) {
+ x = prng();
+ }
+ }
+
+ TVector<T> Examples;
+ };
+}
+
+#define DEFINE_BENCHMARK(type, count) \
+ Y_CPU_BENCHMARK(FastClp2_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FastClp2(e)); \
+ } \
+ } \
+ }
+
+DEFINE_BENCHMARK(ui8, 1)
+DEFINE_BENCHMARK(ui8, 10)
+DEFINE_BENCHMARK(ui8, 100)
+DEFINE_BENCHMARK(ui16, 1)
+DEFINE_BENCHMARK(ui16, 10)
+DEFINE_BENCHMARK(ui16, 100)
+DEFINE_BENCHMARK(ui32, 1)
+DEFINE_BENCHMARK(ui32, 10)
+DEFINE_BENCHMARK(ui32, 100)
+DEFINE_BENCHMARK(ui64, 1)
+DEFINE_BENCHMARK(ui64, 10)
+DEFINE_BENCHMARK(ui64, 100)
+
+#undef DEFINE_BENCHMARKS
diff --git a/util/generic/benchmark/fastclp2/metrics/main.py b/util/generic/benchmark/fastclp2/metrics/main.py
new file mode 100644
index 0000000000..5573c6a5d7
--- /dev/null
+++ b/util/generic/benchmark/fastclp2/metrics/main.py
@@ -0,0 +1,5 @@
+import yatest.common as yc
+
+
+def test_export_metrics(metrics):
+ metrics.set_benchmark(yc.execute_benchmark('util/generic/benchmark/fastclp2/fastclp2', threads=8))
diff --git a/util/generic/benchmark/fastclp2/metrics/ya.make b/util/generic/benchmark/fastclp2/metrics/ya.make
new file mode 100644
index 0000000000..b2d17ebad3
--- /dev/null
+++ b/util/generic/benchmark/fastclp2/metrics/ya.make
@@ -0,0 +1,21 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+PY2TEST()
+
+SIZE(LARGE)
+
+TAG(
+ ya:force_sandbox
+ sb:intel_e5_2660v1
+ ya:fat
+)
+
+TEST_SRCS(main.py)
+
+DEPENDS(util/generic/benchmark/fastclp2)
+
+END()
diff --git a/util/generic/benchmark/fastclp2/ya.make b/util/generic/benchmark/fastclp2/ya.make
new file mode 100644
index 0000000000..976977014f
--- /dev/null
+++ b/util/generic/benchmark/fastclp2/ya.make
@@ -0,0 +1,13 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+Y_BENCHMARK()
+
+SRCS(
+ main.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/log2/main.cpp b/util/generic/benchmark/log2/main.cpp
new file mode 100644
index 0000000000..969f09a309
--- /dev/null
+++ b/util/generic/benchmark/log2/main.cpp
@@ -0,0 +1,140 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <library/cpp/fast_log/fast_log.h>
+
+#include <util/generic/singleton.h>
+#include <util/generic/vector.h>
+#include <util/random/fast.h>
+#include <util/generic/xrange.h>
+
+#include <cmath>
+namespace {
+ template <typename T, size_t N>
+ struct TExamplesHolder {
+ TVector<T> Examples;
+
+ TExamplesHolder()
+ : Examples(N)
+ {
+ TFastRng<ui64> prng{N * 42};
+ for (auto& x : Examples) {
+ x = prng.GenRandReal4() + prng.Uniform(1932); // 1934 is just a random number
+ }
+ }
+ };
+}
+
+#define DEFINE_BENCHMARK(type, count) \
+ Y_CPU_BENCHMARK(libm_log2f_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(log2f(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(libm_logf_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(logf(e)); \
+ } \
+ } \
+ } \
+ Y_CPU_BENCHMARK(STL_Log2_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(std::log2(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(STL_Log_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(std::log(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(Fast_Log2_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FastLog2f(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(FastLogf##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FastLogf(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(Faster_Log2_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FasterLog2f(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(Faster_Log_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FasterLogf(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(Fastest_Log2f_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FastestLog2f(e)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(Fastest_Log_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(FastestLogf(e)); \
+ } \
+ } \
+ }
+
+DEFINE_BENCHMARK(float, 1)
+DEFINE_BENCHMARK(float, 2)
+DEFINE_BENCHMARK(float, 4)
+DEFINE_BENCHMARK(float, 8)
+DEFINE_BENCHMARK(float, 16)
+DEFINE_BENCHMARK(float, 32)
+DEFINE_BENCHMARK(float, 64)
+DEFINE_BENCHMARK(float, 128)
+DEFINE_BENCHMARK(float, 256)
+DEFINE_BENCHMARK(float, 1024)
+DEFINE_BENCHMARK(float, 2048)
+DEFINE_BENCHMARK(float, 4096)
+
+#undef DEFINE_BENCHMARK
diff --git a/util/generic/benchmark/log2/metrics/main.py b/util/generic/benchmark/log2/metrics/main.py
new file mode 100644
index 0000000000..26f6b57812
--- /dev/null
+++ b/util/generic/benchmark/log2/metrics/main.py
@@ -0,0 +1,5 @@
+import yatest.common as yc
+
+
+def test_export_metrics(metrics):
+ metrics.set_benchmark(yc.execute_benchmark('util/generic/benchmark/log2/log2', threads=8))
diff --git a/util/generic/benchmark/log2/metrics/ya.make b/util/generic/benchmark/log2/metrics/ya.make
new file mode 100644
index 0000000000..eb987e38d2
--- /dev/null
+++ b/util/generic/benchmark/log2/metrics/ya.make
@@ -0,0 +1,21 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+PY2TEST()
+
+SIZE(LARGE)
+
+TAG(
+ ya:force_sandbox
+ sb:intel_e5_2660v1
+ ya:fat
+)
+
+TEST_SRCS(main.py)
+
+DEPENDS(util/generic/benchmark/log2)
+
+END()
diff --git a/util/generic/benchmark/log2/ya.make b/util/generic/benchmark/log2/ya.make
new file mode 100644
index 0000000000..45d751909e
--- /dev/null
+++ b/util/generic/benchmark/log2/ya.make
@@ -0,0 +1,17 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+Y_BENCHMARK()
+
+SRCS(
+ main.cpp
+)
+
+PEERDIR(
+ library/cpp/fast_log
+)
+
+END()
diff --git a/util/generic/benchmark/rotate_bits/main.cpp b/util/generic/benchmark/rotate_bits/main.cpp
new file mode 100644
index 0000000000..057edbe864
--- /dev/null
+++ b/util/generic/benchmark/rotate_bits/main.cpp
@@ -0,0 +1,66 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/vector.h>
+#include <util/generic/xrange.h>
+#include <util/generic/singleton.h>
+
+#include <util/random/fast.h>
+
+namespace {
+ template <typename T>
+ struct TExample {
+ T Value;
+ ui8 Shift;
+ };
+
+ template <typename T, size_t N>
+ struct TExamplesHolder {
+ TExamplesHolder()
+ : Examples(N)
+ {
+ TFastRng<ui64> prng{42u * sizeof(T) * N};
+ for (auto& e : Examples) {
+ e.Value = prng();
+ e.Shift = prng() % (8 * sizeof(T));
+ }
+ }
+
+ TVector<TExample<T>> Examples;
+ };
+}
+
+#define DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(type, count) \
+ Y_CPU_BENCHMARK(LeftRotate_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(RotateBitsLeft(e.Value, e.Shift)); \
+ } \
+ } \
+ } \
+ \
+ Y_CPU_BENCHMARK(RightRotate_##type##_##count, iface) { \
+ const auto& examples = Default<TExamplesHolder<type, count>>().Examples; \
+ for (const auto i : xrange(iface.Iterations())) { \
+ Y_UNUSED(i); \
+ for (const auto e : examples) { \
+ Y_DO_NOT_OPTIMIZE_AWAY(RotateBitsRight(e.Value, e.Shift)); \
+ } \
+ } \
+ }
+
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui8, 1)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui8, 10)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui8, 100)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui16, 1)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui16, 10)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui16, 100)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui32, 1)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui32, 10)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui32, 100)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui64, 1)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui64, 10)
+DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES(ui64, 100)
+
+#undef DEFINE_BENCHMARKS_FOR_UNSIGNED_TYPES
diff --git a/util/generic/benchmark/rotate_bits/metrics/main.py b/util/generic/benchmark/rotate_bits/metrics/main.py
new file mode 100644
index 0000000000..b30555775f
--- /dev/null
+++ b/util/generic/benchmark/rotate_bits/metrics/main.py
@@ -0,0 +1,5 @@
+import yatest.common as yc
+
+
+def test_export_metrics(metrics):
+ metrics.set_benchmark(yc.execute_benchmark('util/generic/benchmark/rotate_bits/rotate_bits', threads=8))
diff --git a/util/generic/benchmark/rotate_bits/metrics/ya.make b/util/generic/benchmark/rotate_bits/metrics/ya.make
new file mode 100644
index 0000000000..ac27d2f845
--- /dev/null
+++ b/util/generic/benchmark/rotate_bits/metrics/ya.make
@@ -0,0 +1,21 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+PY2TEST()
+
+SIZE(LARGE)
+
+TAG(
+ ya:force_sandbox
+ sb:intel_e5_2660v1
+ ya:fat
+)
+
+TEST_SRCS(main.py)
+
+DEPENDS(util/generic/benchmark/rotate_bits)
+
+END()
diff --git a/util/generic/benchmark/rotate_bits/ya.make b/util/generic/benchmark/rotate_bits/ya.make
new file mode 100644
index 0000000000..976977014f
--- /dev/null
+++ b/util/generic/benchmark/rotate_bits/ya.make
@@ -0,0 +1,13 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+Y_BENCHMARK()
+
+SRCS(
+ main.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/singleton/f.cpp b/util/generic/benchmark/singleton/f.cpp
new file mode 100644
index 0000000000..bf6da53d9c
--- /dev/null
+++ b/util/generic/benchmark/singleton/f.cpp
@@ -0,0 +1,18 @@
+#include <util/generic/singleton.h>
+
+struct X {
+ inline X() {
+ }
+
+ char Buf[100];
+};
+
+char& FF1() noexcept {
+ static X x;
+
+ return x.Buf[0];
+}
+
+char& FF2() noexcept {
+ return Singleton<X>()->Buf[0];
+}
diff --git a/util/generic/benchmark/singleton/main.cpp b/util/generic/benchmark/singleton/main.cpp
new file mode 100644
index 0000000000..2b06bd371d
--- /dev/null
+++ b/util/generic/benchmark/singleton/main.cpp
@@ -0,0 +1,54 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/singleton.h>
+#include <util/generic/xrange.h>
+
+char& FF1() noexcept;
+char& FF2() noexcept;
+
+namespace {
+ struct X {
+ inline X() {
+ }
+
+ char Buf[100];
+ };
+
+ inline X& F1() noexcept {
+ static X x;
+
+ return x;
+ }
+
+ inline X& F2() noexcept {
+ return *Singleton<X>();
+ }
+}
+
+Y_CPU_BENCHMARK(MagicStatic, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Y_DO_NOT_OPTIMIZE_AWAY(F1().Buf);
+ }
+}
+
+Y_CPU_BENCHMARK(Singleton, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Y_DO_NOT_OPTIMIZE_AWAY(F2().Buf);
+ }
+}
+
+Y_CPU_BENCHMARK(MagicStaticNI, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Y_DO_NOT_OPTIMIZE_AWAY(FF1());
+ }
+}
+
+Y_CPU_BENCHMARK(SingletonNI, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Y_DO_NOT_OPTIMIZE_AWAY(FF2());
+ }
+}
diff --git a/util/generic/benchmark/singleton/ya.make b/util/generic/benchmark/singleton/ya.make
new file mode 100644
index 0000000000..12d3d316c8
--- /dev/null
+++ b/util/generic/benchmark/singleton/ya.make
@@ -0,0 +1,11 @@
+Y_BENCHMARK()
+
+OWNER(g:util)
+SUBSCRIBER(g:util-subscribers)
+
+SRCS(
+ f.cpp
+ main.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/smart_pointers/main.cpp b/util/generic/benchmark/smart_pointers/main.cpp
new file mode 100644
index 0000000000..92c2f923bb
--- /dev/null
+++ b/util/generic/benchmark/smart_pointers/main.cpp
@@ -0,0 +1,14 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/ptr.h>
+#include <util/generic/xrange.h>
+
+struct X: public TAtomicRefCount<X> {
+};
+
+Y_CPU_BENCHMARK(SimplePtrConstruct, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Y_DO_NOT_OPTIMIZE_AWAY(TSimpleIntrusivePtr<X>());
+ }
+}
diff --git a/util/generic/benchmark/smart_pointers/ya.make b/util/generic/benchmark/smart_pointers/ya.make
new file mode 100644
index 0000000000..7059abc3a4
--- /dev/null
+++ b/util/generic/benchmark/smart_pointers/ya.make
@@ -0,0 +1,10 @@
+Y_BENCHMARK()
+
+OWNER(g:util)
+SUBSCRIBER(g:util-subscribers)
+
+SRCS(
+ main.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/sort/main.cpp b/util/generic/benchmark/sort/main.cpp
new file mode 100644
index 0000000000..d58f491f4d
--- /dev/null
+++ b/util/generic/benchmark/sort/main.cpp
@@ -0,0 +1,77 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/algorithm.h>
+#include <util/generic/vector.h>
+#include <util/generic/xrange.h>
+
+Y_CPU_BENCHMARK(Sort1, iface) {
+ TVector<int> x = {1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Sort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(Sort2, iface) {
+ TVector<int> x = {2, 1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Sort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(Sort4, iface) {
+ TVector<int> x = {4, 3, 2, 1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Sort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(Sort16, iface) {
+ TVector<int> x = {16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ Sort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(StableSort1, iface) {
+ TVector<int> x = {1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ StableSort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(StableSort2, iface) {
+ TVector<int> x = {2, 1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ StableSort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(StableSort4, iface) {
+ TVector<int> x = {4, 3, 2, 1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ StableSort(x);
+ }
+}
+
+Y_CPU_BENCHMARK(StableSort16, iface) {
+ TVector<int> x = {16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ StableSort(x);
+ }
+}
diff --git a/util/generic/benchmark/sort/ya.make b/util/generic/benchmark/sort/ya.make
new file mode 100644
index 0000000000..7059abc3a4
--- /dev/null
+++ b/util/generic/benchmark/sort/ya.make
@@ -0,0 +1,10 @@
+Y_BENCHMARK()
+
+OWNER(g:util)
+SUBSCRIBER(g:util-subscribers)
+
+SRCS(
+ main.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/string/benchmarks.h b/util/generic/benchmark/string/benchmarks.h
new file mode 100644
index 0000000000..e347d7ff47
--- /dev/null
+++ b/util/generic/benchmark/string/benchmarks.h
@@ -0,0 +1,188 @@
+#pragma once
+
+// Define BENCHMARK_PREFIX and BENCHMARKED_CLASS before including this file.
+
+#include <util/generic/xrange.h>
+
+#define Y_CPU_PREFIXED_BENCHMARK_HELPER(prefix, name, iface) Y_CPU_BENCHMARK(prefix##name, iface)
+#define Y_CPU_PREFIXED_BENCHMARK(prefix, name, iface) Y_CPU_PREFIXED_BENCHMARK_HELPER(prefix, name, iface)
+#define CONCATENATE3_HELPER(a, b, c) a##b##c
+#define CONCATENATE3(a, b, c) CONCATENATE3_HELPER(a, b, c)
+
+namespace {
+ namespace CONCATENATE3(N, BENCHMARK_PREFIX, Benchmark) {
+ using TBenchmarkedClass = BENCHMARKED_CLASS;
+
+ const auto defaultString = TBenchmarkedClass();
+ const auto emptyString = TBenchmarkedClass("");
+ const auto lengthOneString = TBenchmarkedClass("1");
+ const auto length1KString = TBenchmarkedClass(1000, '1');
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CreateDefault, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass();
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CreateFromEmptyLiteral, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass("");
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CreateFromLengthOneLiteral, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass("1");
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CreateLength1K, iface) {
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass(1000, '1');
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyDefaultString, iface) {
+ const auto& sourceString = defaultString;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass(sourceString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyEmptyString, iface) {
+ const auto& sourceString = emptyString;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass(sourceString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyLengthOneString, iface) {
+ const auto& sourceString = lengthOneString;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass(sourceString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyLength1KString, iface) {
+ const auto& sourceString = length1KString;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto result = TBenchmarkedClass(sourceString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndUpdateLengthOneString, iface) {
+ const auto& sourceString = lengthOneString;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString[0] = '0';
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndAppendDefaultString, iface) {
+ const auto& sourceString = defaultString;
+ const TBenchmarkedClass::size_type insertPosition = sourceString.size();
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString.insert(insertPosition, 1, '0');
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndAppendEmptyString, iface) {
+ const auto& sourceString = emptyString;
+ const TBenchmarkedClass::size_type insertPosition = sourceString.size();
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString.insert(insertPosition, 1, '0');
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndAppendLengthOneString, iface) {
+ const auto& sourceString = lengthOneString;
+ const TBenchmarkedClass::size_type insertPosition = sourceString.size();
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString.insert(insertPosition, 1, '0');
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndPrependLengthOneString, iface) {
+ const auto& sourceString = lengthOneString;
+ const TBenchmarkedClass::size_type insertPosition = 0;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString.insert(insertPosition, 1, '0');
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndUpdateLength1KString, iface) {
+ const auto& sourceString = length1KString;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString[0] = '0';
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndAppendLength1KString, iface) {
+ const auto& sourceString = length1KString;
+ const TBenchmarkedClass::size_type insertPosition = sourceString.size();
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString.insert(insertPosition, 1, '0');
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+
+ Y_CPU_PREFIXED_BENCHMARK(BENCHMARK_PREFIX, CopyAndPrependLength1KString, iface) {
+ const auto& sourceString = length1KString;
+ const TBenchmarkedClass::size_type insertPosition = 0;
+ for (const auto i : xrange(iface.Iterations())) {
+ Y_UNUSED(i);
+ auto targetString = TBenchmarkedClass(sourceString);
+ auto result = targetString.insert(insertPosition, 1, '0');
+ Y_DO_NOT_OPTIMIZE_AWAY(targetString);
+ Y_DO_NOT_OPTIMIZE_AWAY(result);
+ }
+ }
+ }
+}
+
+#undef CONCATENATE3
+#undef CONCATENATE3_HELPER
+#undef Y_CPU_PREFIXED_BENCHMARK
+#undef Y_CPU_PREFIXED_BENCHMARK_HELPER
diff --git a/util/generic/benchmark/string/std_string.cpp b/util/generic/benchmark/string/std_string.cpp
new file mode 100644
index 0000000000..cb10b4dc0c
--- /dev/null
+++ b/util/generic/benchmark/string/std_string.cpp
@@ -0,0 +1,8 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <string>
+
+#define BENCHMARK_PREFIX StdString
+#define BENCHMARKED_CLASS std::string
+
+#include "benchmarks.h"
diff --git a/util/generic/benchmark/string/string.cpp b/util/generic/benchmark/string/string.cpp
new file mode 100644
index 0000000000..c634c204d8
--- /dev/null
+++ b/util/generic/benchmark/string/string.cpp
@@ -0,0 +1,8 @@
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/string.h>
+
+#define BENCHMARK_PREFIX TString
+#define BENCHMARKED_CLASS ::TString
+
+#include "benchmarks.h"
diff --git a/util/generic/benchmark/string/ya.make b/util/generic/benchmark/string/ya.make
new file mode 100644
index 0000000000..c2956de6a1
--- /dev/null
+++ b/util/generic/benchmark/string/ya.make
@@ -0,0 +1,11 @@
+Y_BENCHMARK()
+
+OWNER(g:util)
+SUBSCRIBER(g:util-subscribers)
+
+SRCS(
+ string.cpp
+ std_string.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/vector_count_ctor/f.cpp b/util/generic/benchmark/vector_count_ctor/f.cpp
new file mode 100644
index 0000000000..b89e351ba7
--- /dev/null
+++ b/util/generic/benchmark/vector_count_ctor/f.cpp
@@ -0,0 +1,25 @@
+#include "f.h"
+
+#include <library/cpp/testing/benchmark/bench.h>
+
+#include <util/generic/vector.h>
+#include <util/generic/ptr.h>
+
+void CreateYvector(const size_t size, const size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ NBench::Clobber();
+ TVector<ui8> v(size);
+ NBench::Escape(v.data());
+ NBench::Clobber();
+ }
+}
+
+void CreateCarray(const size_t size, const size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ NBench::Clobber();
+ TArrayHolder<ui8> v(new ui8[size]);
+ memset(v.Get(), 0, size * sizeof(ui8));
+ NBench::Escape(v.Get());
+ NBench::Clobber();
+ }
+}
diff --git a/util/generic/benchmark/vector_count_ctor/f.h b/util/generic/benchmark/vector_count_ctor/f.h
new file mode 100644
index 0000000000..a568341a45
--- /dev/null
+++ b/util/generic/benchmark/vector_count_ctor/f.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <cstddef>
+
+// functions are declared in a separate translation unit so that compiler won't be able to see the
+// value of `size` during compilation.
+
+void CreateYvector(const size_t size, const size_t count);
+void CreateCarray(const size_t size, const size_t count);
diff --git a/util/generic/benchmark/vector_count_ctor/main.cpp b/util/generic/benchmark/vector_count_ctor/main.cpp
new file mode 100644
index 0000000000..6fb1fda9c9
--- /dev/null
+++ b/util/generic/benchmark/vector_count_ctor/main.cpp
@@ -0,0 +1,30 @@
+#include "f.h"
+
+#include <library/cpp/testing/benchmark/bench.h>
+
+#define DEFINE_BENCHMARK(N) \
+ Y_CPU_BENCHMARK(Yvector_##N, iface) { \
+ CreateYvector(N, iface.Iterations()); \
+ } \
+ Y_CPU_BENCHMARK(Carray_##N, iface) { \
+ CreateCarray(N, iface.Iterations()); \
+ }
+
+DEFINE_BENCHMARK(1)
+DEFINE_BENCHMARK(2)
+DEFINE_BENCHMARK(8)
+DEFINE_BENCHMARK(10)
+DEFINE_BENCHMARK(16)
+DEFINE_BENCHMARK(20)
+DEFINE_BENCHMARK(1000)
+DEFINE_BENCHMARK(1024)
+DEFINE_BENCHMARK(8192)
+DEFINE_BENCHMARK(10000)
+DEFINE_BENCHMARK(65536)
+DEFINE_BENCHMARK(100000)
+DEFINE_BENCHMARK(4194304)
+DEFINE_BENCHMARK(1000000)
+DEFINE_BENCHMARK(33554432)
+DEFINE_BENCHMARK(10000000)
+DEFINE_BENCHMARK(268435456)
+DEFINE_BENCHMARK(100000000)
diff --git a/util/generic/benchmark/vector_count_ctor/metrics/main.py b/util/generic/benchmark/vector_count_ctor/metrics/main.py
new file mode 100644
index 0000000000..835b44fe5f
--- /dev/null
+++ b/util/generic/benchmark/vector_count_ctor/metrics/main.py
@@ -0,0 +1,5 @@
+import yatest.common as yc
+
+
+def test_export_metrics(metrics):
+ metrics.set_benchmark(yc.execute_benchmark('util/generic/benchmark/vector_count_ctor/vector_count_ctor', threads=8))
diff --git a/util/generic/benchmark/vector_count_ctor/metrics/ya.make b/util/generic/benchmark/vector_count_ctor/metrics/ya.make
new file mode 100644
index 0000000000..c48f89b564
--- /dev/null
+++ b/util/generic/benchmark/vector_count_ctor/metrics/ya.make
@@ -0,0 +1,21 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+PY2TEST()
+
+SIZE(LARGE)
+
+TAG(
+ ya:force_sandbox
+ sb:intel_e5_2660v1
+ ya:fat
+)
+
+TEST_SRCS(main.py)
+
+DEPENDS(util/generic/benchmark/vector_count_ctor)
+
+END()
diff --git a/util/generic/benchmark/vector_count_ctor/ya.make b/util/generic/benchmark/vector_count_ctor/ya.make
new file mode 100644
index 0000000000..42ce442819
--- /dev/null
+++ b/util/generic/benchmark/vector_count_ctor/ya.make
@@ -0,0 +1,16 @@
+OWNER(
+ yazevnul
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+Y_BENCHMARK()
+
+ALLOCATOR(B)
+
+SRCS(
+ main.cpp
+ f.cpp
+)
+
+END()
diff --git a/util/generic/benchmark/ya.make b/util/generic/benchmark/ya.make
new file mode 100644
index 0000000000..635860a646
--- /dev/null
+++ b/util/generic/benchmark/ya.make
@@ -0,0 +1,19 @@
+OWNER(yazevnul g:util)
+
+SUBSCRIBER(g:util-subscribers)
+
+RECURSE(
+ fastclp2
+ fastclp2/metrics
+ log2
+ log2/metrics
+ rotate_bits
+ rotate_bits/metrics
+ singleton
+ smart_pointers
+ sort
+ string
+ vector_count_ctor
+ vector_count_ctor/metrics
+ cont_speed
+)
diff --git a/util/generic/bitmap.cpp b/util/generic/bitmap.cpp
new file mode 100644
index 0000000000..a629a870d0
--- /dev/null
+++ b/util/generic/bitmap.cpp
@@ -0,0 +1 @@
+#include "bitmap.h"
diff --git a/util/generic/bitmap.h b/util/generic/bitmap.h
new file mode 100644
index 0000000000..f77d182460
--- /dev/null
+++ b/util/generic/bitmap.h
@@ -0,0 +1,1114 @@
+#pragma once
+
+#include "fwd.h"
+#include "ptr.h"
+#include "bitops.h"
+#include "typetraits.h"
+#include "algorithm.h"
+#include "utility.h"
+
+#include <util/system/yassert.h>
+#include <util/system/defaults.h>
+#include <util/str_stl.h>
+#include <util/ysaveload.h>
+
+namespace NBitMapPrivate {
+ // Returns number of bits set; result is in most significatnt byte
+ inline ui64 ByteSums(ui64 x) {
+ ui64 byteSums = x - ((x & 0xAAAAAAAAAAAAAAAAULL) >> 1);
+
+ byteSums = (byteSums & 0x3333333333333333ULL) + ((byteSums >> 2) & 0x3333333333333333ULL);
+ byteSums = (byteSums + (byteSums >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
+
+ return byteSums * 0x0101010101010101ULL;
+ }
+
+ // better than intrinsics without -mpopcnt
+ template <typename T>
+ static unsigned CountBitsPrivate(T v) noexcept {
+ return static_cast<unsigned>(ByteSums(v) >> 56);
+ }
+
+ template <typename TChunkType, size_t ExtraBits>
+ struct TSanitizeMask {
+ static constexpr TChunkType Value = ~((~TChunkType(0)) << ExtraBits);
+ };
+
+ template <typename TChunkType>
+ struct TSanitizeMask<TChunkType, 0> {
+ static constexpr TChunkType Value = (TChunkType)~TChunkType(0u);
+ };
+
+ template <typename TTargetChunk, typename TSourceChunk>
+ struct TBigToSmallDataCopier {
+ static_assert(sizeof(TTargetChunk) < sizeof(TSourceChunk), "expect sizeof(TTargetChunk) < sizeof(TSourceChunk)");
+ static_assert(0 == sizeof(TSourceChunk) % sizeof(TTargetChunk), "expect 0 == sizeof(TSourceChunk) % sizeof(TTargetChunk)");
+
+ static constexpr size_t BLOCK_SIZE = sizeof(TSourceChunk) / sizeof(TTargetChunk);
+
+ union TCnv {
+ TSourceChunk BigData;
+ TTargetChunk SmallData[BLOCK_SIZE];
+ };
+
+ static inline void CopyChunk(TTargetChunk* target, TSourceChunk source) {
+ TCnv c;
+ c.BigData = source;
+#if defined(_big_endian_)
+ ::ReverseCopy(c.SmallData, c.SmallData + Y_ARRAY_SIZE(c.SmallData), target);
+#else
+ ::Copy(c.SmallData, c.SmallData + Y_ARRAY_SIZE(c.SmallData), target);
+#endif
+ }
+
+ static inline void Copy(TTargetChunk* target, size_t targetSize, const TSourceChunk* source, size_t sourceSize) {
+ Y_ASSERT(targetSize >= sourceSize * BLOCK_SIZE);
+ if (targetSize > sourceSize * BLOCK_SIZE) {
+ ::Fill(target + sourceSize * BLOCK_SIZE, target + targetSize, 0);
+ }
+ for (size_t i = 0; i < sourceSize; ++i) {
+ CopyChunk(target + i * BLOCK_SIZE, source[i]);
+ }
+ }
+ };
+
+ template <typename TTargetChunk, typename TSourceChunk>
+ struct TSmallToBigDataCopier {
+ static_assert(sizeof(TTargetChunk) > sizeof(TSourceChunk), "expect sizeof(TTargetChunk) > sizeof(TSourceChunk)");
+ static_assert(0 == sizeof(TTargetChunk) % sizeof(TSourceChunk), "expect 0 == sizeof(TTargetChunk) % sizeof(TSourceChunk)");
+
+ static constexpr size_t BLOCK_SIZE = sizeof(TTargetChunk) / sizeof(TSourceChunk);
+
+ union TCnv {
+ TSourceChunk SmallData[BLOCK_SIZE];
+ TTargetChunk BigData;
+ };
+
+ static inline TTargetChunk CopyFullChunk(const TSourceChunk* source) {
+ TCnv c;
+#if defined(_big_endian_)
+ ::ReverseCopy(source, source + BLOCK_SIZE, c.SmallData);
+#else
+ ::Copy(source, source + BLOCK_SIZE, c.SmallData);
+#endif
+ return c.BigData;
+ }
+
+ static inline TTargetChunk CopyPartChunk(const TSourceChunk* source, size_t count) {
+ Y_ASSERT(count <= BLOCK_SIZE);
+ TCnv c;
+ c.BigData = 0;
+#if defined(_big_endian_)
+ ::ReverseCopy(source, source + count, c.SmallData);
+#else
+ ::Copy(source, source + count, c.SmallData);
+#endif
+ return c.BigData;
+ }
+
+ static inline void Copy(TTargetChunk* target, size_t targetSize, const TSourceChunk* source, size_t sourceSize) {
+ Y_ASSERT(targetSize * BLOCK_SIZE >= sourceSize);
+ if (targetSize * BLOCK_SIZE > sourceSize) {
+ ::Fill(target + sourceSize / BLOCK_SIZE, target + targetSize, 0);
+ }
+ size_t i = 0;
+ for (; i < sourceSize / BLOCK_SIZE; ++i) {
+ target[i] = CopyFullChunk(source + i * BLOCK_SIZE);
+ }
+ if (0 != sourceSize % BLOCK_SIZE) {
+ target[i] = CopyPartChunk(source + i * BLOCK_SIZE, sourceSize % BLOCK_SIZE);
+ }
+ }
+ };
+
+ template <typename TChunk>
+ struct TUniformDataCopier {
+ static inline void Copy(TChunk* target, size_t targetSize, const TChunk* source, size_t sourceSize) {
+ Y_ASSERT(targetSize >= sourceSize);
+ for (size_t i = 0; i < sourceSize; ++i) {
+ target[i] = source[i];
+ }
+ for (size_t i = sourceSize; i < targetSize; ++i) {
+ target[i] = 0;
+ }
+ }
+ };
+
+ template <typename TFirst, typename TSecond>
+ struct TIsSmaller {
+ enum {
+ Result = sizeof(TFirst) < sizeof(TSecond)
+ };
+ };
+
+ template <typename TTargetChunk, typename TSourceChunk>
+ struct TDataCopier: public std::conditional_t<std::is_same<TTargetChunk, TSourceChunk>::value, TUniformDataCopier<TTargetChunk>, std::conditional_t<TIsSmaller<TTargetChunk, TSourceChunk>::Result, TBigToSmallDataCopier<TTargetChunk, TSourceChunk>, TSmallToBigDataCopier<TTargetChunk, TSourceChunk>>> {
+ };
+
+ template <typename TTargetChunk, typename TSourceChunk>
+ inline void CopyData(TTargetChunk* target, size_t targetSize, const TSourceChunk* source, size_t sourceSize) {
+ TDataCopier<TTargetChunk, TSourceChunk>::Copy(target, targetSize, source, sourceSize);
+ }
+
+ template <size_t BitCount, typename TChunkType>
+ struct TFixedStorage {
+ using TChunk = TChunkType;
+
+ static constexpr size_t Size = (BitCount + 8 * sizeof(TChunk) - 1) / (8 * sizeof(TChunk));
+
+ TChunk Data[Size];
+
+ TFixedStorage() {
+ Zero(Data);
+ }
+
+ TFixedStorage(const TFixedStorage<BitCount, TChunkType>& st) {
+ for (size_t i = 0; i < Size; ++i) {
+ Data[i] = st.Data[i];
+ }
+ }
+
+ template <typename TOtherChunk>
+ TFixedStorage(const TOtherChunk* data, size_t size) {
+ Y_VERIFY(Size * sizeof(TChunk) >= size * sizeof(TOtherChunk), "Exceeding bitmap storage capacity");
+ CopyData(Data, Size, data, size);
+ }
+
+ Y_FORCE_INLINE void Swap(TFixedStorage<BitCount, TChunkType>& st) {
+ for (size_t i = 0; i < Size; ++i) {
+ DoSwap(Data[i], st.Data[i]);
+ }
+ }
+
+ Y_FORCE_INLINE static constexpr size_t GetBitCapacity() noexcept {
+ return BitCount;
+ }
+
+ Y_FORCE_INLINE static constexpr size_t GetChunkCapacity() noexcept {
+ return Size;
+ }
+
+ // Returns true if the resulting storage capacity is enough to fit the requested size
+ Y_FORCE_INLINE static constexpr bool ExpandBitSize(const size_t bitSize) noexcept {
+ return bitSize <= BitCount;
+ }
+
+ Y_FORCE_INLINE void Sanitize() {
+ Data[Size - 1] &= TSanitizeMask<TChunk, BitCount % (8 * sizeof(TChunk))>::Value;
+ }
+ };
+
+ // Dynamically expanded storage.
+ // It uses "on stack" realization with no allocation for one chunk spaces
+ template <typename TChunkType>
+ struct TDynamicStorage {
+ using TChunk = TChunkType;
+
+ size_t Size;
+ TChunk StackData;
+ TArrayHolder<TChunk> ArrayData;
+ TChunk* Data;
+
+ TDynamicStorage()
+ : Size(1)
+ , StackData(0)
+ , Data(&StackData)
+ {
+ }
+
+ TDynamicStorage(const TDynamicStorage<TChunk>& st)
+ : Size(1)
+ , StackData(0)
+ , Data(&StackData)
+ {
+ ExpandSize(st.Size, false);
+ for (size_t i = 0; i < st.Size; ++i) {
+ Data[i] = st.Data[i];
+ }
+ for (size_t i = st.Size; i < Size; ++i) {
+ Data[i] = 0;
+ }
+ }
+
+ template <typename TOtherChunk>
+ TDynamicStorage(const TOtherChunk* data, size_t size)
+ : Size(1)
+ , StackData(0)
+ , Data(&StackData)
+ {
+ ExpandBitSize(size * sizeof(TOtherChunk) * 8, false);
+ CopyData(Data, Size, data, size);
+ }
+
+ Y_FORCE_INLINE void Swap(TDynamicStorage<TChunkType>& st) {
+ DoSwap(Size, st.Size);
+ DoSwap(StackData, st.StackData);
+ DoSwap(ArrayData, st.ArrayData);
+ Data = 1 == Size ? &StackData : ArrayData.Get();
+ st.Data = 1 == st.Size ? &st.StackData : st.ArrayData.Get();
+ }
+
+ Y_FORCE_INLINE size_t GetBitCapacity() const {
+ return Size * 8 * sizeof(TChunk);
+ }
+
+ Y_FORCE_INLINE size_t GetChunkCapacity() const {
+ return Size;
+ }
+
+ // Returns true if the resulting storage capacity is enough to fit the requested size
+ Y_FORCE_INLINE bool ExpandSize(size_t size, bool keepData = true) {
+ if (size > Size) {
+ size = Max(size, Size * 2);
+ TArrayHolder<TChunk> newData(new TChunk[size]);
+ if (keepData) {
+ for (size_t i = 0; i < Size; ++i) {
+ newData[i] = Data[i];
+ }
+ for (size_t i = Size; i < size; ++i) {
+ newData[i] = 0;
+ }
+ }
+ DoSwap(ArrayData, newData);
+ Data = ArrayData.Get();
+ Size = size;
+ }
+ return true;
+ }
+
+ Y_FORCE_INLINE bool ExpandBitSize(size_t bitSize, bool keepData = true) {
+ return ExpandSize((bitSize + 8 * sizeof(TChunk) - 1) / (8 * sizeof(TChunk)), keepData);
+ }
+
+ Y_FORCE_INLINE void Sanitize() {
+ }
+ };
+
+ template <size_t num>
+ struct TDivCount {
+ static constexpr size_t Value = 1 + TDivCount<(num >> 1)>::Value;
+ };
+
+ template <>
+ struct TDivCount<0> {
+ static constexpr size_t Value = 0;
+ };
+
+}
+
+template <size_t BitCount, typename TChunkType>
+struct TFixedBitMapTraits {
+ using TChunk = TChunkType;
+ using TStorage = NBitMapPrivate::TFixedStorage<BitCount, TChunkType>;
+};
+
+template <typename TChunkType>
+struct TDynamicBitMapTraits {
+ using TChunk = TChunkType;
+ using TStorage = NBitMapPrivate::TDynamicStorage<TChunkType>;
+};
+
+template <class TTraits>
+class TBitMapOps {
+public:
+ using TChunk = typename TTraits::TChunk;
+ using TThis = TBitMapOps<TTraits>;
+
+private:
+ static_assert(std::is_unsigned<TChunk>::value, "expect std::is_unsigned<TChunk>::value");
+
+ static constexpr size_t BitsPerChunk = 8 * sizeof(TChunk);
+ static constexpr TChunk ModMask = static_cast<TChunk>(BitsPerChunk - 1);
+ static constexpr size_t DivCount = NBitMapPrivate::TDivCount<BitsPerChunk>::Value - 1;
+ static constexpr TChunk FullChunk = (TChunk)~TChunk(0);
+
+ template <class>
+ friend class TBitMapOps;
+
+ using TStorage = typename TTraits::TStorage;
+
+ // The smallest unsigned type, which can be used in bit ops
+ using TIntType = std::conditional_t<sizeof(TChunk) < sizeof(unsigned int), unsigned int, TChunk>;
+
+ TStorage Mask;
+
+public:
+ class TReference {
+ private:
+ friend class TBitMapOps<TTraits>;
+
+ TChunk* Chunk;
+ size_t Offset;
+
+ TReference(TChunk* c, size_t offset)
+ : Chunk(c)
+ , Offset(offset)
+ {
+ }
+
+ public:
+ ~TReference() = default;
+
+ Y_FORCE_INLINE TReference& operator=(bool val) {
+ if (val)
+ *Chunk |= static_cast<TChunk>(1) << Offset;
+ else
+ *Chunk &= ~(static_cast<TChunk>(1) << Offset);
+
+ return *this;
+ }
+
+ Y_FORCE_INLINE TReference& operator=(const TReference& ref) {
+ if (ref)
+ *Chunk |= static_cast<TChunk>(1) << Offset;
+ else
+ *Chunk &= ~(static_cast<TChunk>(1) << Offset);
+
+ return *this;
+ }
+
+ Y_FORCE_INLINE bool operator~() const {
+ return 0 == (*Chunk & (static_cast<TChunk>(1) << Offset));
+ }
+
+ Y_FORCE_INLINE operator bool() const {
+ return 0 != (*Chunk & (static_cast<TChunk>(1) << Offset));
+ }
+
+ Y_FORCE_INLINE TReference& Flip() {
+ *Chunk ^= static_cast<TChunk>(1) << Offset;
+ return *this;
+ }
+ };
+
+private:
+ struct TSetOp {
+ static constexpr TChunk Op(const TChunk src, const TChunk mask) noexcept {
+ return src | mask;
+ }
+ };
+
+ struct TResetOp {
+ static constexpr TChunk Op(const TChunk src, const TChunk mask) noexcept {
+ return src & ~mask;
+ }
+ };
+
+ template <class TUpdateOp>
+ void UpdateRange(size_t start, size_t end) {
+ const size_t startChunk = start >> DivCount;
+ const size_t startBitOffset = start & ModMask;
+
+ const size_t endChunk = end >> DivCount;
+ const size_t endBitOffset = end & ModMask;
+
+ size_t bitOffset = startBitOffset;
+ for (size_t chunk = startChunk; chunk <= endChunk; ++chunk) {
+ TChunk updateMask = FullChunk << bitOffset;
+ if (chunk == endChunk) {
+ updateMask ^= FullChunk << endBitOffset;
+ if (!updateMask)
+ break;
+ }
+ Mask.Data[chunk] = TUpdateOp::Op(Mask.Data[chunk], updateMask);
+ bitOffset = 0;
+ }
+ }
+
+public:
+ TBitMapOps() = default;
+
+ TBitMapOps(TChunk val) {
+ Mask.Data[0] = val;
+ Mask.Sanitize();
+ }
+
+ TBitMapOps(const TThis&) = default;
+
+ template <class T>
+ TBitMapOps(const TBitMapOps<T>& bitmap)
+ : Mask(bitmap.Mask.Data, bitmap.Mask.GetChunkCapacity())
+ {
+ Mask.Sanitize();
+ }
+
+ template <class T>
+ Y_FORCE_INLINE bool operator==(const TBitMapOps<T>& bitmap) const {
+ return Equal(bitmap);
+ }
+
+ Y_FORCE_INLINE TThis& operator=(const TThis& bitmap) {
+ if (this != &bitmap) {
+ TThis bm(bitmap);
+ Swap(bm);
+ }
+ return *this;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& operator=(const TBitMapOps<T>& bitmap) {
+ TThis bm(bitmap);
+ Swap(bm);
+ return *this;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& operator&=(const TBitMapOps<T>& bitmap) {
+ return And(bitmap);
+ }
+
+ Y_FORCE_INLINE TThis& operator&=(const TChunk& val) {
+ return And(val);
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& operator|=(const TBitMapOps<T>& bitmap) {
+ return Or(bitmap);
+ }
+
+ Y_FORCE_INLINE TThis& operator|=(const TChunk& val) {
+ return Or(val);
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& operator^=(const TBitMapOps<T>& bitmap) {
+ return Xor(bitmap);
+ }
+
+ Y_FORCE_INLINE TThis& operator^=(const TChunk& val) {
+ return Xor(val);
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& operator-=(const TBitMapOps<T>& bitmap) {
+ return SetDifference(bitmap);
+ }
+
+ Y_FORCE_INLINE TThis& operator-=(const TChunk& val) {
+ return SetDifference(val);
+ }
+
+ Y_FORCE_INLINE TThis& operator<<=(size_t pos) {
+ return LShift(pos);
+ }
+
+ Y_FORCE_INLINE TThis& operator>>=(size_t pos) {
+ return RShift(pos);
+ }
+
+ Y_FORCE_INLINE TThis operator<<(size_t pos) const {
+ return TThis(*this).LShift(pos);
+ }
+
+ Y_FORCE_INLINE TThis operator>>(size_t pos) const {
+ return TThis(*this).RShift(pos);
+ }
+
+ Y_FORCE_INLINE bool operator[](size_t pos) const {
+ return Get(pos);
+ }
+
+ Y_FORCE_INLINE TReference operator[](size_t pos) {
+ const bool fitStorage = Mask.ExpandBitSize(pos + 1);
+ Y_ASSERT(fitStorage);
+ return TReference(&Mask.Data[pos >> DivCount], ModMask & pos);
+ }
+
+ Y_FORCE_INLINE void Swap(TThis& bitmap) {
+ DoSwap(Mask, bitmap.Mask);
+ }
+
+ Y_FORCE_INLINE TThis& Set(size_t pos) {
+ const bool fitStorage = Mask.ExpandBitSize(pos + 1);
+ Y_ASSERT(fitStorage);
+ Mask.Data[pos >> DivCount] |= static_cast<TChunk>(1) << (pos & ModMask);
+ return *this;
+ }
+
+ // Fills the specified [start, end) bit range by the 1. Other bits are kept unchanged
+ TThis& Set(size_t start, size_t end) {
+ Y_ASSERT(start <= end);
+ if (start < end) {
+ Reserve(end);
+ UpdateRange<TSetOp>(start, end);
+ }
+ return *this;
+ }
+
+ Y_FORCE_INLINE TThis& Reset(size_t pos) {
+ if ((pos >> DivCount) < Mask.GetChunkCapacity()) {
+ Mask.Data[pos >> DivCount] &= ~(static_cast<TChunk>(1) << (pos & ModMask));
+ }
+ return *this;
+ }
+
+ // Clears the specified [start, end) bit range. Other bits are kept unchanged
+ TThis& Reset(size_t start, size_t end) {
+ Y_ASSERT(start <= end);
+ if (start < end && (start >> DivCount) < Mask.GetChunkCapacity()) {
+ UpdateRange<TResetOp>(start, Min(end, Mask.GetBitCapacity()));
+ }
+ return *this;
+ }
+
+ Y_FORCE_INLINE TThis& Flip(size_t pos) {
+ const bool fitStorage = Mask.ExpandBitSize(pos + 1);
+ Y_ASSERT(fitStorage);
+ Mask.Data[pos >> DivCount] ^= static_cast<TChunk>(1) << (pos & ModMask);
+ return *this;
+ }
+
+ Y_FORCE_INLINE bool Get(size_t pos) const {
+ if ((pos >> DivCount) < Mask.GetChunkCapacity()) {
+ return Mask.Data[pos >> DivCount] & (static_cast<TChunk>(1) << (pos & ModMask));
+ }
+ return false;
+ }
+
+ template <class TTo>
+ void Export(size_t pos, TTo& to) const {
+ static_assert(std::is_unsigned<TTo>::value, "expect std::is_unsigned<TTo>::value");
+ to = 0;
+ size_t chunkpos = pos >> DivCount;
+ if (chunkpos >= Mask.GetChunkCapacity())
+ return;
+ if ((pos & ModMask) == 0) {
+ if (sizeof(TChunk) >= sizeof(TTo))
+ to = (TTo)Mask.Data[chunkpos];
+ else //if (sizeof(TChunk) < sizeof(TTo))
+ NBitMapPrivate::CopyData(&to, 1, Mask.Data + chunkpos, Min(((sizeof(TTo) * 8) >> DivCount), Mask.GetChunkCapacity() - chunkpos));
+ } else if ((pos & (sizeof(TTo) * 8 - 1)) == 0 && sizeof(TChunk) >= 2 * sizeof(TTo))
+ to = (TTo)(Mask.Data[chunkpos] >> (pos & ModMask));
+ else {
+ static constexpr size_t copyToSize = (sizeof(TChunk) >= sizeof(TTo)) ? (sizeof(TChunk) / sizeof(TTo)) + 2 : 3;
+ TTo temp[copyToSize] = {0, 0};
+ //or use non defined by now TBitmap<copyToSize, TTo>::CopyData,RShift(pos & ModMask),Export(0,to)
+ NBitMapPrivate::CopyData(temp, copyToSize, Mask.Data + chunkpos, Min((sizeof(TTo) / sizeof(TChunk)) + 1, Mask.GetChunkCapacity() - chunkpos));
+ to = (temp[0] >> (pos & ModMask)) | (temp[1] << (8 * sizeof(TTo) - (pos & ModMask)));
+ }
+ }
+
+ Y_FORCE_INLINE bool Test(size_t n) const {
+ return Get(n);
+ }
+
+ Y_FORCE_INLINE TThis& Push(bool val) {
+ LShift(1);
+ return val ? Set(0) : *this;
+ }
+
+ Y_FORCE_INLINE bool Pop() {
+ bool val = Get(0);
+ return RShift(1), val;
+ }
+
+ // Clear entire bitmap. Current capacity is kept unchanged
+ Y_FORCE_INLINE TThis& Clear() {
+ for (size_t i = 0; i < Mask.GetChunkCapacity(); ++i) {
+ Mask.Data[i] = 0;
+ }
+ return *this;
+ }
+
+ // Returns bits capacity
+ Y_FORCE_INLINE constexpr size_t Size() const noexcept {
+ return Mask.GetBitCapacity();
+ }
+
+ Y_FORCE_INLINE void Reserve(size_t bitCount) {
+ Y_VERIFY(Mask.ExpandBitSize(bitCount), "Exceeding bitmap storage capacity");
+ }
+
+ Y_FORCE_INLINE size_t ValueBitCount() const {
+ size_t nonZeroChunk = Mask.GetChunkCapacity() - 1;
+ while (nonZeroChunk != 0 && !Mask.Data[nonZeroChunk])
+ --nonZeroChunk;
+ return nonZeroChunk || Mask.Data[nonZeroChunk]
+ ? nonZeroChunk * BitsPerChunk + GetValueBitCount(TIntType(Mask.Data[nonZeroChunk]))
+ : 0;
+ }
+
+ Y_PURE_FUNCTION Y_FORCE_INLINE bool Empty() const {
+ for (size_t i = 0; i < Mask.GetChunkCapacity(); ++i)
+ if (Mask.Data[i])
+ return false;
+ return true;
+ }
+
+ bool HasAny(const TThis& bitmap) const {
+ for (size_t i = 0; i < Min(Mask.GetChunkCapacity(), bitmap.Mask.GetChunkCapacity()); ++i) {
+ if (0 != (Mask.Data[i] & bitmap.Mask.Data[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE bool HasAny(const TBitMapOps<T>& bitmap) const {
+ return HasAny(TThis(bitmap));
+ }
+
+ Y_FORCE_INLINE bool HasAny(const TChunk& val) const {
+ return 0 != (Mask.Data[0] & val);
+ }
+
+ bool HasAll(const TThis& bitmap) const {
+ for (size_t i = 0; i < Min(Mask.GetChunkCapacity(), bitmap.Mask.GetChunkCapacity()); ++i) {
+ if (bitmap.Mask.Data[i] != (Mask.Data[i] & bitmap.Mask.Data[i])) {
+ return false;
+ }
+ }
+ for (size_t i = Mask.GetChunkCapacity(); i < bitmap.Mask.GetChunkCapacity(); ++i) {
+ if (bitmap.Mask.Data[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE bool HasAll(const TBitMapOps<T>& bitmap) const {
+ return HasAll(TThis(bitmap));
+ }
+
+ Y_FORCE_INLINE bool HasAll(const TChunk& val) const {
+ return (Mask.Data[0] & val) == val;
+ }
+
+ TThis& And(const TThis& bitmap) {
+ // Don't expand capacity here, because resulting bits in positions,
+ // which are greater then size of one of these bitmaps, will be zero
+ for (size_t i = 0; i < Min(bitmap.Mask.GetChunkCapacity(), Mask.GetChunkCapacity()); ++i)
+ Mask.Data[i] &= bitmap.Mask.Data[i];
+ // Clear bits if current bitmap size is greater than AND-ed one
+ for (size_t i = bitmap.Mask.GetChunkCapacity(); i < Mask.GetChunkCapacity(); ++i)
+ Mask.Data[i] = 0;
+ return *this;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& And(const TBitMapOps<T>& bitmap) {
+ return And(TThis(bitmap));
+ }
+
+ Y_FORCE_INLINE TThis& And(const TChunk& val) {
+ Mask.Data[0] &= val;
+ for (size_t i = 1; i < Mask.GetChunkCapacity(); ++i)
+ Mask.Data[i] = 0;
+ return *this;
+ }
+
+ TThis& Or(const TThis& bitmap) {
+ const size_t valueBitCount = bitmap.ValueBitCount();
+ if (valueBitCount) {
+ // Memory optimization: expand size only for non-zero bits
+ Reserve(valueBitCount);
+ for (size_t i = 0; i < Min(bitmap.Mask.GetChunkCapacity(), Mask.GetChunkCapacity()); ++i)
+ Mask.Data[i] |= bitmap.Mask.Data[i];
+ }
+ return *this;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& Or(const TBitMapOps<T>& bitmap) {
+ return Or(TThis(bitmap));
+ }
+
+ Y_FORCE_INLINE TThis& Or(const TChunk& val) {
+ Mask.Data[0] |= val;
+ Mask.Sanitize();
+ return *this;
+ }
+
+ TThis& Xor(const TThis& bitmap) {
+ Reserve(bitmap.Size());
+ for (size_t i = 0; i < bitmap.Mask.GetChunkCapacity(); ++i)
+ Mask.Data[i] ^= bitmap.Mask.Data[i];
+ return *this;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& Xor(const TBitMapOps<T>& bitmap) {
+ return Xor(TThis(bitmap));
+ }
+
+ Y_FORCE_INLINE TThis& Xor(const TChunk& val) {
+ Mask.Data[0] ^= val;
+ Mask.Sanitize();
+ return *this;
+ }
+
+ TThis& SetDifference(const TThis& bitmap) {
+ for (size_t i = 0; i < Min(bitmap.Mask.GetChunkCapacity(), Mask.GetChunkCapacity()); ++i)
+ Mask.Data[i] &= ~bitmap.Mask.Data[i];
+ return *this;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE TThis& SetDifference(const TBitMapOps<T>& bitmap) {
+ return SetDifference(TThis(bitmap));
+ }
+
+ Y_FORCE_INLINE TThis& SetDifference(const TChunk& val) {
+ Mask.Data[0] &= ~val;
+ return *this;
+ }
+
+ Y_FORCE_INLINE TThis& Flip() {
+ for (size_t i = 0; i < Mask.GetChunkCapacity(); ++i)
+ Mask.Data[i] = ~Mask.Data[i];
+ Mask.Sanitize();
+ return *this;
+ }
+
+ TThis& LShift(size_t shift) {
+ if (shift != 0) {
+ const size_t valueBitCount = ValueBitCount();
+ // Do nothing for empty bitmap
+ if (valueBitCount != 0) {
+ const size_t eshift = shift / BitsPerChunk;
+ const size_t offset = shift % BitsPerChunk;
+ const size_t subOffset = BitsPerChunk - offset;
+
+ // Don't verify expand result, so l-shift of fixed bitmap will work in the same way as for unsigned integer.
+ Mask.ExpandBitSize(valueBitCount + shift);
+
+ if (offset == 0) {
+ for (size_t i = Mask.GetChunkCapacity() - 1; i >= eshift; --i) {
+ Mask.Data[i] = Mask.Data[i - eshift];
+ }
+ } else {
+ for (size_t i = Mask.GetChunkCapacity() - 1; i > eshift; --i)
+ Mask.Data[i] = (Mask.Data[i - eshift] << offset) | (Mask.Data[i - eshift - 1] >> subOffset);
+ if (eshift < Mask.GetChunkCapacity())
+ Mask.Data[eshift] = Mask.Data[0] << offset;
+ }
+ for (size_t i = 0; i < Min(eshift, Mask.GetChunkCapacity()); ++i)
+ Mask.Data[i] = 0;
+
+ // Cleanup extra high bits in the storage
+ Mask.Sanitize();
+ }
+ }
+ return *this;
+ }
+
+ TThis& RShift(size_t shift) {
+ if (shift != 0) {
+ const size_t eshift = shift / BitsPerChunk;
+ const size_t offset = shift % BitsPerChunk;
+ if (eshift >= Mask.GetChunkCapacity()) {
+ Clear();
+
+ } else {
+ const size_t limit = Mask.GetChunkCapacity() - eshift - 1;
+
+ if (offset == 0) {
+ for (size_t i = 0; i <= limit; ++i) {
+ Mask.Data[i] = Mask.Data[i + eshift];
+ }
+ } else {
+ const size_t subOffset = BitsPerChunk - offset;
+ for (size_t i = 0; i < limit; ++i)
+ Mask.Data[i] = (Mask.Data[i + eshift] >> offset) | (Mask.Data[i + eshift + 1] << subOffset);
+ Mask.Data[limit] = Mask.Data[Mask.GetChunkCapacity() - 1] >> offset;
+ }
+
+ for (size_t i = limit + 1; i < Mask.GetChunkCapacity(); ++i)
+ Mask.Data[i] = 0;
+ }
+ }
+ return *this;
+ }
+
+ // Applies bitmap at the specified offset using OR operator.
+ // This method is optimized combination of Or() and LShift(), which allows reducing memory allocation
+ // when combining long dynamic bitmaps.
+ TThis& Or(const TThis& bitmap, size_t offset) {
+ if (0 == offset)
+ return Or(bitmap);
+
+ const size_t otherValueBitCount = bitmap.ValueBitCount();
+ // Continue only if OR-ed bitmap have non-zero bits
+ if (otherValueBitCount) {
+ const size_t chunkShift = offset / BitsPerChunk;
+ const size_t subShift = offset % BitsPerChunk;
+ const size_t subOffset = BitsPerChunk - subShift;
+
+ Reserve(otherValueBitCount + offset);
+
+ if (subShift == 0) {
+ for (size_t i = chunkShift; i < Min(bitmap.Mask.GetChunkCapacity() + chunkShift, Mask.GetChunkCapacity()); ++i) {
+ Mask.Data[i] |= bitmap.Mask.Data[i - chunkShift];
+ }
+ } else {
+ Mask.Data[chunkShift] |= bitmap.Mask.Data[0] << subShift;
+ size_t i = chunkShift + 1;
+ for (; i < Min(bitmap.Mask.GetChunkCapacity() + chunkShift, Mask.GetChunkCapacity()); ++i) {
+ Mask.Data[i] |= (bitmap.Mask.Data[i - chunkShift] << subShift) | (bitmap.Mask.Data[i - chunkShift - 1] >> subOffset);
+ }
+ if (i < Mask.GetChunkCapacity())
+ Mask.Data[i] |= bitmap.Mask.Data[i - chunkShift - 1] >> subOffset;
+ }
+ }
+
+ return *this;
+ }
+
+ bool Equal(const TThis& bitmap) const {
+ if (Mask.GetChunkCapacity() > bitmap.Mask.GetChunkCapacity()) {
+ for (size_t i = bitmap.Mask.GetChunkCapacity(); i < Mask.GetChunkCapacity(); ++i) {
+ if (0 != Mask.Data[i])
+ return false;
+ }
+ } else if (Mask.GetChunkCapacity() < bitmap.Mask.GetChunkCapacity()) {
+ for (size_t i = Mask.GetChunkCapacity(); i < bitmap.Mask.GetChunkCapacity(); ++i) {
+ if (0 != bitmap.Mask.Data[i])
+ return false;
+ }
+ }
+ size_t size = Min(Mask.GetChunkCapacity(), bitmap.Mask.GetChunkCapacity());
+ for (size_t i = 0; i < size; ++i) {
+ if (Mask.Data[i] != bitmap.Mask.Data[i])
+ return false;
+ }
+ return true;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE bool Equal(const TBitMapOps<T>& bitmap) const {
+ return Equal(TThis(bitmap));
+ }
+
+ int Compare(const TThis& bitmap) const {
+ size_t size = Min(Mask.GetChunkCapacity(), bitmap.Mask.GetChunkCapacity());
+ int res = ::memcmp(Mask.Data, bitmap.Mask.Data, size * sizeof(TChunk));
+ if (0 != res || Mask.GetChunkCapacity() == bitmap.Mask.GetChunkCapacity())
+ return res;
+
+ if (Mask.GetChunkCapacity() > bitmap.Mask.GetChunkCapacity()) {
+ for (size_t i = bitmap.Mask.GetChunkCapacity(); i < Mask.GetChunkCapacity(); ++i) {
+ if (0 != Mask.Data[i])
+ return 1;
+ }
+ } else {
+ for (size_t i = Mask.GetChunkCapacity(); i < bitmap.Mask.GetChunkCapacity(); ++i) {
+ if (0 != bitmap.Mask.Data[i])
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ template <class T>
+ Y_FORCE_INLINE int Compare(const TBitMapOps<T>& bitmap) const {
+ return Compare(TThis(bitmap));
+ }
+
+ // For backward compatibility
+ Y_FORCE_INLINE static int Compare(const TThis& l, const TThis& r) {
+ return l.Compare(r);
+ }
+
+ size_t FirstNonZeroBit() const {
+ for (size_t i = 0; i < Mask.GetChunkCapacity(); ++i) {
+ if (Mask.Data[i]) {
+ // CountTrailingZeroBits() expects unsigned types not smaller than unsigned int. So, convert before calling
+ return BitsPerChunk * i + CountTrailingZeroBits(TIntType(Mask.Data[i]));
+ }
+ }
+ return Size();
+ }
+
+ // Returns position of the next non-zero bit, which offset is greater than specified pos
+ // Typical loop for iterating bits:
+ // for (size_t pos = bits.FirstNonZeroBit(); pos != bits.Size(); pos = bits.NextNonZeroBit(pos)) {
+ // ...
+ // }
+ // See Y_FOR_EACH_BIT macro definition at the bottom
+ size_t NextNonZeroBit(size_t pos) const {
+ size_t i = (pos + 1) >> DivCount;
+ if (i < Mask.GetChunkCapacity()) {
+ const size_t offset = (pos + 1) & ModMask;
+ // Process the current chunk
+ if (offset) {
+ // Zero already iterated trailing bits using mask
+ const TChunk val = Mask.Data[i] & ((~TChunk(0)) << offset);
+ if (val) {
+ return BitsPerChunk * i + CountTrailingZeroBits(TIntType(val));
+ }
+ // Continue with other chunks
+ ++i;
+ }
+
+ for (; i < Mask.GetChunkCapacity(); ++i) {
+ if (Mask.Data[i]) {
+ return BitsPerChunk * i + CountTrailingZeroBits(TIntType(Mask.Data[i]));
+ }
+ }
+ }
+ return Size();
+ }
+
+ Y_FORCE_INLINE size_t Count() const {
+ size_t count = 0;
+ for (size_t i = 0; i < Mask.GetChunkCapacity(); ++i)
+ count += ::NBitMapPrivate::CountBitsPrivate(Mask.Data[i]);
+ return count;
+ }
+
+ void Save(IOutputStream* out) const {
+ ::Save(out, ui8(sizeof(TChunk)));
+ ::Save(out, ui64(Size()));
+ ::SavePodArray(out, Mask.Data, Mask.GetChunkCapacity());
+ }
+
+ void Load(IInputStream* inp) {
+ ui8 chunkSize = 0;
+ ::Load(inp, chunkSize);
+ Y_VERIFY(size_t(chunkSize) == sizeof(TChunk), "Chunk size is not the same");
+
+ ui64 bitCount64 = 0;
+ ::Load(inp, bitCount64);
+ size_t bitCount = size_t(bitCount64);
+ Reserve(bitCount);
+
+ size_t chunkCount = 0;
+ if (bitCount > 0) {
+ chunkCount = ((bitCount - 1) >> DivCount) + 1;
+ ::LoadPodArray(inp, Mask.Data, chunkCount);
+ }
+
+ if (chunkCount < Mask.GetChunkCapacity()) {
+ ::memset(Mask.Data + chunkCount, 0, (Mask.GetChunkCapacity() - chunkCount) * sizeof(TChunk));
+ }
+ Mask.Sanitize();
+ }
+
+ inline size_t Hash() const {
+ THash<TChunk> chunkHasher;
+
+ size_t hash = chunkHasher(0);
+ bool tailSkipped = false;
+ for (size_t i = Mask.GetChunkCapacity(); i > 0; --i) {
+ if (tailSkipped || Mask.Data[i - 1]) {
+ hash = ::CombineHashes(hash, chunkHasher(Mask.Data[i - 1]));
+ tailSkipped = true;
+ }
+ }
+
+ return hash;
+ }
+
+ inline const TChunk* GetChunks() const {
+ return Mask.Data;
+ }
+
+ constexpr size_t GetChunkCount() const noexcept {
+ return Mask.GetChunkCapacity();
+ }
+};
+
+template <class X, class Y>
+inline TBitMapOps<X> operator&(const TBitMapOps<X>& x, const TBitMapOps<Y>& y) {
+ return TBitMapOps<X>(x).And(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator&(const TBitMapOps<X>& x, const typename TBitMapOps<X>::TChunk& y) {
+ return TBitMapOps<X>(x).And(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator&(const typename TBitMapOps<X>::TChunk& x, const TBitMapOps<X>& y) {
+ return TBitMapOps<X>(x).And(y);
+}
+
+template <class X, class Y>
+inline TBitMapOps<X> operator|(const TBitMapOps<X>& x, const TBitMapOps<Y>& y) {
+ return TBitMapOps<X>(x).Or(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator|(const TBitMapOps<X>& x, const typename TBitMapOps<X>::TChunk& y) {
+ return TBitMapOps<X>(x).Or(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator|(const typename TBitMapOps<X>::TChunk& x, const TBitMapOps<X>& y) {
+ return TBitMapOps<X>(x).Or(y);
+}
+
+template <class X, class Y>
+inline TBitMapOps<X> operator^(const TBitMapOps<X>& x, const TBitMapOps<Y>& y) {
+ return TBitMapOps<X>(x).Xor(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator^(const TBitMapOps<X>& x, const typename TBitMapOps<X>::TChunk& y) {
+ return TBitMapOps<X>(x).Xor(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator^(const typename TBitMapOps<X>::TChunk& x, const TBitMapOps<X>& y) {
+ return TBitMapOps<X>(x).Xor(y);
+}
+
+template <class X, class Y>
+inline TBitMapOps<X> operator-(const TBitMapOps<X>& x, const TBitMapOps<Y>& y) {
+ return TBitMapOps<X>(x).SetDifference(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator-(const TBitMapOps<X>& x, const typename TBitMapOps<X>::TChunk& y) {
+ return TBitMapOps<X>(x).SetDifference(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator-(const typename TBitMapOps<X>::TChunk& x, const TBitMapOps<X>& y) {
+ return TBitMapOps<X>(x).SetDifference(y);
+}
+
+template <class X>
+inline TBitMapOps<X> operator~(const TBitMapOps<X>& x) {
+ return TBitMapOps<X>(x).Flip();
+}
+
+/////////////////// Specialization ///////////////////////////
+
+template <size_t BitCount, typename TChunkType /*= ui64*/>
+class TBitMap: public TBitMapOps<TFixedBitMapTraits<BitCount, TChunkType>> {
+private:
+ using TBase = TBitMapOps<TFixedBitMapTraits<BitCount, TChunkType>>;
+
+public:
+ TBitMap()
+ : TBase()
+ {
+ }
+
+ TBitMap(typename TBase::TChunk val)
+ : TBase(val)
+ {
+ }
+
+ TBitMap(const TBitMap<BitCount, TChunkType>&) = default;
+
+ template <class T>
+ TBitMap(const TBitMapOps<T>& bitmap)
+ : TBase(bitmap)
+ {
+ }
+};
+
+using TDynBitMap = TBitMapOps<TDynamicBitMapTraits<ui64>>;
+
+#define Y_FOR_EACH_BIT(var, bitmap) for (size_t var = (bitmap).FirstNonZeroBit(); var != (bitmap).Size(); var = (bitmap).NextNonZeroBit(var))
+
+template <typename TTraits>
+struct THash<TBitMapOps<TTraits>> {
+ size_t operator()(const TBitMapOps<TTraits>& elem) const {
+ return elem.Hash();
+ }
+};
diff --git a/util/generic/bitmap_ut.cpp b/util/generic/bitmap_ut.cpp
new file mode 100644
index 0000000000..087d34a8dc
--- /dev/null
+++ b/util/generic/bitmap_ut.cpp
@@ -0,0 +1,597 @@
+#include "bitmap.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#define INIT_BITMAP(bitmap, bits) \
+ for (size_t i = 0; i < sizeof(bits) / sizeof(size_t); ++i) { \
+ bitmap.Set(bits[i]); \
+ }
+
+#define CHECK_BITMAP(bitmap, bits) \
+ { \
+ size_t cur = 0, end = sizeof(bits) / sizeof(size_t); \
+ for (size_t i = 0; i < bitmap.Size(); ++i) { \
+ if (cur < end && bits[cur] == i) { \
+ UNIT_ASSERT_EQUAL_C(bitmap.Get(i), true, "pos=" << i); \
+ ++cur; \
+ } else { \
+ UNIT_ASSERT_EQUAL_C(bitmap.Get(i), false, "pos=" << i); \
+ } \
+ } \
+ }
+
+#define CHECK_BITMAP_WITH_TAIL(bitmap, bits) \
+ { \
+ size_t cur = 0, end = sizeof(bits) / sizeof(size_t); \
+ for (size_t i = 0; i < bitmap.Size(); ++i) { \
+ if (cur < end) { \
+ if (bits[cur] == i) { \
+ UNIT_ASSERT_EQUAL_C(bitmap.Get(i), true, "pos=" << i); \
+ ++cur; \
+ } else { \
+ UNIT_ASSERT_EQUAL_C(bitmap.Get(i), false, "pos=" << i); \
+ } \
+ } else { \
+ UNIT_ASSERT_EQUAL_C(bitmap.Get(i), true, "pos=" << i); \
+ } \
+ } \
+ }
+
+Y_UNIT_TEST_SUITE(TBitMapTest) {
+ Y_UNIT_TEST(TestBitMap) {
+ TBitMap<101> bitmap;
+
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 101);
+ UNIT_ASSERT_EQUAL(bitmap.Count(), 0);
+ UNIT_ASSERT_EQUAL(bitmap.FirstNonZeroBit(), 101);
+
+ size_t initBits[] = {0, 50, 100, 45};
+ INIT_BITMAP(bitmap, initBits);
+ bitmap.Reset(45);
+
+ UNIT_ASSERT_EQUAL(bitmap.FirstNonZeroBit(), 0);
+ size_t setBits[] = {0, 50, 100};
+ CHECK_BITMAP(bitmap, setBits);
+
+ for (size_t i = 0; i < bitmap.Size(); ++i) {
+ UNIT_ASSERT_EQUAL(bitmap.Get(i), bitmap.Test(i));
+ }
+
+ UNIT_ASSERT_EQUAL(bitmap.Count(), 3);
+
+ bitmap.Reset(0);
+
+ UNIT_ASSERT_EQUAL(bitmap.FirstNonZeroBit(), 50);
+
+ bitmap.Clear();
+
+ UNIT_ASSERT_EQUAL(bitmap.Count(), 0);
+ UNIT_ASSERT_EQUAL(bitmap.Empty(), true);
+ }
+
+ Y_UNIT_TEST(TestDynBitMap) {
+ TDynBitMap bitmap;
+
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 64); // Initial capacity
+ UNIT_ASSERT_EQUAL(bitmap.Count(), 0);
+ UNIT_ASSERT_EQUAL(bitmap.FirstNonZeroBit(), 64);
+
+ size_t initBits[] = {0, 50, 100, 45};
+ INIT_BITMAP(bitmap, initBits);
+ bitmap.Reset(45);
+
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 128);
+
+ UNIT_ASSERT_EQUAL(bitmap.FirstNonZeroBit(), 0);
+ size_t setBits[] = {0, 50, 100};
+ CHECK_BITMAP(bitmap, setBits);
+
+ for (size_t i = 0; i < bitmap.Size(); ++i) {
+ UNIT_ASSERT_EQUAL(bitmap.Get(i), bitmap.Test(i));
+ }
+
+ UNIT_ASSERT_EQUAL(bitmap.Count(), 3);
+
+ bitmap.Reset(0);
+
+ UNIT_ASSERT_EQUAL(bitmap.FirstNonZeroBit(), 50);
+
+ bitmap.Clear();
+
+ UNIT_ASSERT_EQUAL(bitmap.Count(), 0);
+ UNIT_ASSERT_EQUAL(bitmap.Empty(), true);
+ }
+
+ template <class TBitMapImpl>
+ void TestAndImpl() {
+ TBitMapImpl bitmap1;
+ TBitMapImpl bitmap2;
+
+ size_t initBits1[] = {10, 20, 50, 100};
+ size_t initBits2[] = {10, 11, 22, 50};
+
+ INIT_BITMAP(bitmap1, initBits1);
+ INIT_BITMAP(bitmap2, initBits2);
+
+ bitmap1.And(bitmap2);
+
+ UNIT_ASSERT_EQUAL(bitmap1.Count(), 2);
+ UNIT_ASSERT_EQUAL(bitmap2.Count(), 4);
+
+ size_t resBits[] = {10, 50};
+
+ CHECK_BITMAP(bitmap1, resBits);
+ CHECK_BITMAP(bitmap2, initBits2);
+ }
+
+ Y_UNIT_TEST(TestAndFixed) {
+ TestAndImpl<TBitMap<101>>();
+ }
+
+ Y_UNIT_TEST(TestAndDyn) {
+ TestAndImpl<TDynBitMap>();
+ }
+
+ template <class TBitMapImpl>
+ void TestOrImpl() {
+ TBitMapImpl bitmap1;
+ TBitMapImpl bitmap2;
+
+ size_t initBits1[] = {0, 10, 11, 76};
+ size_t initBits2[] = {1, 11, 22, 50};
+
+ INIT_BITMAP(bitmap1, initBits1);
+ INIT_BITMAP(bitmap2, initBits2);
+
+ bitmap1.Or(bitmap2);
+
+ UNIT_ASSERT_EQUAL(bitmap1.Count(), 7);
+ UNIT_ASSERT_EQUAL(bitmap2.Count(), 4);
+
+ size_t resBits[] = {0, 1, 10, 11, 22, 50, 76};
+
+ CHECK_BITMAP(bitmap1, resBits);
+ CHECK_BITMAP(bitmap2, initBits2);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits1);
+
+ UNIT_ASSERT_EQUAL(bitmap1 | (bitmap2 << 3), TBitMapImpl(bitmap1).Or(bitmap2, 3));
+ UNIT_ASSERT_EQUAL(bitmap1 | (bitmap2 << 64), TBitMapImpl(bitmap1).Or(bitmap2, 64));
+ UNIT_ASSERT_EQUAL(bitmap1 | (bitmap2 << 66), TBitMapImpl(bitmap1).Or(bitmap2, 66));
+
+ UNIT_ASSERT_EQUAL(bitmap2 | (bitmap1 << 3), TBitMapImpl(bitmap2).Or(bitmap1, 3));
+ UNIT_ASSERT_EQUAL(bitmap2 | (bitmap1 << 64), TBitMapImpl(bitmap2).Or(bitmap1, 64));
+ UNIT_ASSERT_EQUAL(bitmap2 | (bitmap1 << 66), TBitMapImpl(bitmap2).Or(bitmap1, 66));
+ }
+
+ Y_UNIT_TEST(TestOrFixed) {
+ TestOrImpl<TBitMap<145>>();
+ }
+
+ Y_UNIT_TEST(TestOrDyn) {
+ TestOrImpl<TDynBitMap>();
+ }
+
+ Y_UNIT_TEST(TestCopy) {
+ TBitMap<101> bitmap1;
+ size_t initBits[] = {0, 10, 11, 76, 100};
+
+ INIT_BITMAP(bitmap1, initBits);
+
+ TDynBitMap bitmap2(bitmap1);
+ CHECK_BITMAP(bitmap2, initBits);
+
+ TBitMap<101> bitmap3(bitmap1);
+ CHECK_BITMAP(bitmap3, initBits);
+
+ TBitMap<127> bitmap4(bitmap1);
+ CHECK_BITMAP(bitmap4, initBits);
+
+ TDynBitMap bitmap5;
+ bitmap5 = bitmap1;
+ CHECK_BITMAP(bitmap5, initBits);
+
+ TBitMap<101> bitmap6;
+ bitmap6 = bitmap1;
+ CHECK_BITMAP(bitmap6, initBits);
+
+ TBitMap<127> bitmap7;
+ bitmap7 = bitmap1;
+ CHECK_BITMAP(bitmap7, initBits);
+
+ TBitMap<101> bitmap8;
+ DoSwap(bitmap8, bitmap6);
+ CHECK_BITMAP(bitmap8, initBits);
+ UNIT_ASSERT_EQUAL(bitmap6.Empty(), true);
+
+ TDynBitMap bitmap9;
+ DoSwap(bitmap9, bitmap5);
+ CHECK_BITMAP(bitmap9, initBits);
+ UNIT_ASSERT_EQUAL(bitmap5.Empty(), true);
+
+ // 64->32
+ TBitMap<160, ui32> bitmap10(bitmap1);
+ CHECK_BITMAP(bitmap10, initBits);
+
+ // 64->16
+ TBitMap<160, ui16> bitmap11(bitmap1);
+ CHECK_BITMAP(bitmap11, initBits);
+
+ // 64->8
+ TBitMap<160, ui8> bitmap12(bitmap1);
+ CHECK_BITMAP(bitmap12, initBits);
+
+ // 32->16
+ TBitMap<160, ui16> bitmap13(bitmap10);
+ CHECK_BITMAP(bitmap13, initBits);
+
+ // 32->64
+ TBitMap<160, ui64> bitmap14(bitmap10);
+ CHECK_BITMAP(bitmap14, initBits);
+
+ // 16->64
+ TBitMap<160, ui64> bitmap15(bitmap11);
+ CHECK_BITMAP(bitmap15, initBits);
+
+ // 8->64
+ TBitMap<160, ui64> bitmap16(bitmap12);
+ CHECK_BITMAP(bitmap16, initBits);
+ }
+
+ Y_UNIT_TEST(TestOps) {
+ TBitMap<16> bitmap1;
+ TBitMap<12> bitmap2;
+ size_t initBits1[] = {0, 3, 7, 11};
+ size_t initBits2[] = {1, 3, 6, 7, 11};
+ INIT_BITMAP(bitmap1, initBits1);
+ INIT_BITMAP(bitmap2, initBits2);
+
+ bitmap1.Or(3).And(bitmap2).Flip();
+
+ size_t resBits[] = {0, 2, 4, 5, 6, 8, 9, 10, 12};
+ CHECK_BITMAP_WITH_TAIL(bitmap1, resBits);
+
+ TDynBitMap bitmap3;
+ INIT_BITMAP(bitmap3, initBits1);
+
+ bitmap3.Or(3).And(bitmap2).Flip();
+
+ CHECK_BITMAP_WITH_TAIL(bitmap3, resBits);
+
+ bitmap3.Clear();
+ INIT_BITMAP(bitmap3, initBits1);
+
+ TDynBitMap bitmap4 = ~((bitmap3 | 3) & bitmap2);
+ CHECK_BITMAP_WITH_TAIL(bitmap4, resBits);
+
+ TBitMap<128, ui32> expmap;
+ expmap.Set(47);
+ expmap.Set(90);
+ ui64 tst1 = 0;
+ ui32 tst2 = 0;
+ ui16 tst3 = 0;
+ expmap.Export(32, tst1);
+ UNIT_ASSERT_EQUAL(tst1, (1 << 15) | (((ui64)1) << 58));
+ expmap.Export(32, tst2);
+ UNIT_ASSERT_EQUAL(tst2, (1 << 15));
+ expmap.Export(32, tst3);
+ UNIT_ASSERT_EQUAL(tst3, (1 << 15));
+
+ expmap.Export(33, tst1);
+ UNIT_ASSERT_EQUAL(tst1, (1 << 14) | (((ui64)1) << 57));
+ expmap.Export(33, tst2);
+ UNIT_ASSERT_EQUAL(tst2, (1 << 14));
+ expmap.Export(33, tst3);
+ UNIT_ASSERT_EQUAL(tst3, (1 << 14));
+ }
+
+ Y_UNIT_TEST(TestShiftFixed) {
+ size_t initBits[] = {0, 3, 7, 11};
+
+ TBitMap<128> bitmap1;
+
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 62;
+ size_t resBits1[] = {62, 65, 69, 73};
+ CHECK_BITMAP(bitmap1, resBits1);
+ bitmap1 >>= 62;
+ CHECK_BITMAP(bitmap1, initBits);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 120;
+ size_t resBits2[] = {120, 123, 127};
+ CHECK_BITMAP(bitmap1, resBits2);
+ bitmap1 >>= 120;
+ size_t resBits3[] = {0, 3, 7};
+ CHECK_BITMAP(bitmap1, resBits3);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 128;
+ UNIT_ASSERT_EQUAL(bitmap1.Empty(), true);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 120;
+ bitmap1 >>= 128;
+ UNIT_ASSERT_EQUAL(bitmap1.Empty(), true);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 140;
+ UNIT_ASSERT_EQUAL(bitmap1.Empty(), true);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 62;
+ bitmap1 >>= 140;
+ UNIT_ASSERT_EQUAL(bitmap1.Empty(), true);
+ }
+
+ Y_UNIT_TEST(TestShiftDyn) {
+ size_t initBits[] = {0, 3, 7, 11};
+
+ TDynBitMap bitmap1;
+
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 62;
+ size_t resBits1[] = {62, 65, 69, 73};
+ CHECK_BITMAP(bitmap1, resBits1);
+ bitmap1 >>= 62;
+ CHECK_BITMAP(bitmap1, initBits);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 120;
+ size_t resBits2[] = {120, 123, 127, 131};
+ CHECK_BITMAP(bitmap1, resBits2);
+ bitmap1 >>= 120;
+ CHECK_BITMAP(bitmap1, initBits);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 128;
+ size_t resBits3[] = {128, 131, 135, 139};
+ CHECK_BITMAP(bitmap1, resBits3);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 120;
+ bitmap1 >>= 128;
+ size_t resBits4[] = {3};
+ CHECK_BITMAP(bitmap1, resBits4);
+
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits);
+ bitmap1 <<= 62;
+ bitmap1 >>= 140;
+ UNIT_ASSERT_EQUAL(bitmap1.Empty(), true);
+ }
+
+ // Test that we don't expand bitmap in LShift when high-order bits are zero
+ Y_UNIT_TEST(TestShiftExpansion) {
+ UNIT_ASSERT_EQUAL(TDynBitMap().LShift(1).Size(), 64);
+ UNIT_ASSERT_EQUAL(TDynBitMap().LShift(65).Size(), 64);
+ UNIT_ASSERT_EQUAL(TDynBitMap().LShift(128).Size(), 64);
+
+ TDynBitMap bitmap;
+ bitmap.Set(62).LShift(1);
+ UNIT_ASSERT_EQUAL(bitmap, TDynBitMap().Set(63));
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 64);
+
+ // Expand explicitly
+ bitmap.Set(65);
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 128);
+
+ bitmap.Clear().Set(0).LShift(1);
+ UNIT_ASSERT_EQUAL(bitmap, TDynBitMap().Set(1));
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 128);
+
+ bitmap.Clear().Set(63).LShift(1);
+ UNIT_ASSERT_EQUAL(bitmap, TDynBitMap().Set(64));
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 128);
+
+ bitmap.Clear().Set(63).LShift(64);
+ UNIT_ASSERT_EQUAL(bitmap, TDynBitMap().Set(127));
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 128);
+
+ bitmap.Clear().Set(62).LShift(129);
+ UNIT_ASSERT_EQUAL(bitmap, TDynBitMap().Set(191));
+ UNIT_ASSERT_EQUAL(bitmap.Size(), 256);
+ }
+
+ Y_UNIT_TEST(TestFixedSanity) {
+ // test extra-bit cleanup
+ UNIT_ASSERT_EQUAL(TBitMap<33>().Set(0).LShift(34).RShift(34).Empty(), true);
+ UNIT_ASSERT_EQUAL(TBitMap<88>().Set(0).Set(1).Set(2).LShift(90).RShift(90).Empty(), true);
+ UNIT_ASSERT_EQUAL(TBitMap<88>().Flip().RShift(88).Empty(), true);
+ UNIT_ASSERT_EQUAL(TBitMap<64>().Flip().LShift(2).RShift(2).Count(), 62);
+ UNIT_ASSERT_EQUAL(TBitMap<67>().Flip().LShift(2).RShift(2).Count(), 65);
+ UNIT_ASSERT_EQUAL(TBitMap<128>().Flip().LShift(2).RShift(2).Count(), 126);
+ UNIT_ASSERT_EQUAL(TBitMap<130>().Flip().LShift(2).RShift(2).Count(), 128);
+ UNIT_ASSERT_EQUAL(TBitMap<130>(TDynBitMap().Set(131)).Empty(), true);
+ UNIT_ASSERT_EQUAL(TBitMap<33>().Or(TBitMap<40>().Set(39)).Empty(), true);
+ UNIT_ASSERT_EQUAL(TBitMap<33>().Xor(TBitMap<40>().Set(39)).Empty(), true);
+ }
+
+ Y_UNIT_TEST(TestIterate) {
+ TDynBitMap bitmap1;
+ TDynBitMap bitmap2;
+
+ size_t initBits1[] = {0, 3, 7, 8, 11, 33, 34, 35, 36, 62, 63, 100, 127};
+ INIT_BITMAP(bitmap1, initBits1);
+ for (size_t i = bitmap1.FirstNonZeroBit(); i != bitmap1.Size(); i = bitmap1.NextNonZeroBit(i)) {
+ bitmap2.Set(i);
+ }
+ CHECK_BITMAP(bitmap2, initBits1);
+ UNIT_ASSERT_EQUAL(bitmap1, bitmap2);
+
+ size_t initBits2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 33, 34, 35, 36, 62};
+ bitmap1.Clear();
+ bitmap2.Clear();
+ INIT_BITMAP(bitmap1, initBits2);
+ for (size_t i = bitmap1.FirstNonZeroBit(); i != bitmap1.Size(); i = bitmap1.NextNonZeroBit(i)) {
+ bitmap2.Set(i);
+ }
+ CHECK_BITMAP(bitmap2, initBits2);
+ UNIT_ASSERT_EQUAL(bitmap1, bitmap2);
+
+ UNIT_ASSERT_EQUAL(bitmap1.NextNonZeroBit(63), bitmap1.Size());
+ UNIT_ASSERT_EQUAL(bitmap1.NextNonZeroBit(64), bitmap1.Size());
+ UNIT_ASSERT_EQUAL(bitmap1.NextNonZeroBit(65), bitmap1.Size());
+ UNIT_ASSERT_EQUAL(bitmap1.NextNonZeroBit(127), bitmap1.Size());
+ UNIT_ASSERT_EQUAL(bitmap1.NextNonZeroBit(533), bitmap1.Size());
+
+ TBitMap<128, ui8> bitmap3;
+ bitmap1.Clear();
+ INIT_BITMAP(bitmap1, initBits1);
+ for (size_t i = bitmap1.FirstNonZeroBit(); i != bitmap1.Size(); i = bitmap1.NextNonZeroBit(i)) {
+ bitmap3.Set(i);
+ }
+ CHECK_BITMAP(bitmap3, initBits1);
+ UNIT_ASSERT_EQUAL(bitmap3, bitmap1);
+
+ TBitMap<18> bitmap4;
+ bitmap4.Set(15);
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(0), 15);
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(15), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(63), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(64), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(65), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(127), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(533), bitmap4.Size());
+
+ bitmap4.Clear().Flip();
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(0), 1);
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(15), 16);
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(17), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(18), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(63), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(64), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(65), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(127), bitmap4.Size());
+ UNIT_ASSERT_EQUAL(bitmap4.NextNonZeroBit(533), bitmap4.Size());
+ }
+
+ Y_UNIT_TEST(TestHashFixed) {
+ TBitMap<32, ui8> bitmap32;
+ TBitMap<32, ui8> bitmap322;
+ TBitMap<64, ui8> bitmap64;
+
+ bitmap32.Clear();
+ bitmap322.Clear();
+ UNIT_ASSERT_EQUAL(bitmap32.Hash(), bitmap322.Hash());
+ bitmap32.Set(0);
+ UNIT_ASSERT_UNEQUAL(bitmap32.Hash(), bitmap322.Hash());
+ bitmap322.Set(0);
+ UNIT_ASSERT_EQUAL(bitmap32.Hash(), bitmap322.Hash());
+ bitmap32.Set(8).Set(31);
+ UNIT_ASSERT_UNEQUAL(bitmap32.Hash(), bitmap322.Hash());
+ bitmap322.Set(8).Set(31);
+ UNIT_ASSERT_EQUAL(bitmap32.Hash(), bitmap322.Hash());
+
+ bitmap32.Clear();
+ bitmap64.Clear();
+ UNIT_ASSERT_EQUAL(bitmap32.Hash(), bitmap64.Hash());
+ bitmap32.Set(0);
+ UNIT_ASSERT_UNEQUAL(bitmap32.Hash(), bitmap64.Hash());
+ bitmap64.Set(0);
+ UNIT_ASSERT_EQUAL(bitmap32.Hash(), bitmap64.Hash());
+ bitmap32.Set(8).Set(31);
+ UNIT_ASSERT_UNEQUAL(bitmap32.Hash(), bitmap64.Hash());
+ bitmap64.Set(8).Set(31);
+ UNIT_ASSERT_EQUAL(bitmap32.Hash(), bitmap64.Hash());
+ bitmap64.Set(32);
+ UNIT_ASSERT_UNEQUAL(bitmap32.Hash(), bitmap64.Hash());
+ }
+
+ Y_UNIT_TEST(TestHashDynamic) {
+ TDynBitMap bitmap1;
+ TDynBitMap bitmap2;
+
+ bitmap1.Clear();
+ bitmap2.Clear();
+ UNIT_ASSERT_EQUAL(bitmap1.Hash(), bitmap2.Hash());
+ bitmap1.Set(0);
+ UNIT_ASSERT_UNEQUAL(bitmap1.Hash(), bitmap2.Hash());
+ bitmap2.Set(0);
+ UNIT_ASSERT_EQUAL(bitmap1.Hash(), bitmap2.Hash());
+ bitmap1.Set(8).Set(31);
+ UNIT_ASSERT_UNEQUAL(bitmap1.Hash(), bitmap2.Hash());
+ bitmap2.Set(8).Set(31);
+ UNIT_ASSERT_EQUAL(bitmap1.Hash(), bitmap2.Hash());
+ bitmap1.Set(64);
+ UNIT_ASSERT_UNEQUAL(bitmap1.Hash(), bitmap2.Hash());
+ bitmap2.Set(64);
+ UNIT_ASSERT_EQUAL(bitmap1.Hash(), bitmap2.Hash());
+ }
+
+ Y_UNIT_TEST(TestHashMixed) {
+ static_assert((std::is_same<TDynBitMap::TChunk, ui64>::value), "expect (TSameType<TDynBitMap::TChunk, ui64>::Result)");
+
+ TBitMap<sizeof(ui64) * 16, ui64> bitmapFixed;
+ TDynBitMap bitmapDynamic;
+
+ bitmapFixed.Clear();
+ bitmapDynamic.Clear();
+ UNIT_ASSERT_EQUAL(bitmapFixed.Hash(), bitmapDynamic.Hash());
+ bitmapFixed.Set(0);
+ UNIT_ASSERT_UNEQUAL(bitmapFixed.Hash(), bitmapDynamic.Hash());
+ bitmapDynamic.Set(0);
+ UNIT_ASSERT_EQUAL(bitmapFixed.Hash(), bitmapDynamic.Hash());
+ bitmapFixed.Set(8).Set(127);
+ UNIT_ASSERT_UNEQUAL(bitmapFixed.Hash(), bitmapDynamic.Hash());
+ bitmapDynamic.Set(8).Set(127);
+ UNIT_ASSERT_EQUAL(bitmapFixed.Hash(), bitmapDynamic.Hash());
+ bitmapDynamic.Set(128);
+ UNIT_ASSERT_UNEQUAL(bitmapFixed.Hash(), bitmapDynamic.Hash());
+ }
+
+ Y_UNIT_TEST(TestSetResetRange) {
+ // Single chunk
+ using TBitMap1Chunk = TBitMap<64>;
+ UNIT_ASSERT_EQUAL(TBitMap1Chunk().Flip().Reset(10, 50), TBitMap1Chunk().Set(0, 10).Set(50, 64));
+ UNIT_ASSERT_EQUAL(TBitMap1Chunk().Flip().Reset(0, 10), TBitMap1Chunk().Set(10, 64));
+ UNIT_ASSERT_EQUAL(TBitMap1Chunk().Flip().Reset(50, 64), TBitMap1Chunk().Set(0, 50));
+ UNIT_ASSERT_EQUAL(TBitMap1Chunk().Flip().Reset(0, 10).Reset(50, 64), TBitMap1Chunk().Set(10, 50));
+
+ // Two chunks
+ using TBitMap2Chunks = TBitMap<64, ui32>;
+ UNIT_ASSERT_EQUAL(TBitMap2Chunks().Flip().Reset(10, 50), TBitMap2Chunks().Set(0, 10).Set(50, 64));
+ UNIT_ASSERT_EQUAL(TBitMap2Chunks().Flip().Reset(0, 10), TBitMap2Chunks().Set(10, 64));
+ UNIT_ASSERT_EQUAL(TBitMap2Chunks().Flip().Reset(50, 64), TBitMap2Chunks().Set(0, 50));
+ UNIT_ASSERT_EQUAL(TBitMap2Chunks().Flip().Reset(0, 10).Reset(50, 64), TBitMap2Chunks().Set(10, 50));
+
+ // Many chunks
+ using TBitMap4Chunks = TBitMap<64, ui16>;
+ UNIT_ASSERT_EQUAL(TBitMap4Chunks().Flip().Reset(10, 50), TBitMap4Chunks().Set(0, 10).Set(50, 64));
+ UNIT_ASSERT_EQUAL(TBitMap4Chunks().Flip().Reset(0, 10), TBitMap4Chunks().Set(10, 64));
+ UNIT_ASSERT_EQUAL(TBitMap4Chunks().Flip().Reset(50, 64), TBitMap4Chunks().Set(0, 50));
+ UNIT_ASSERT_EQUAL(TBitMap4Chunks().Flip().Reset(0, 10).Reset(50, 64), TBitMap4Chunks().Set(10, 50));
+ }
+
+ Y_UNIT_TEST(TestSetRangeDyn) {
+ for (size_t start = 0; start < 192; ++start) {
+ for (size_t end = start; end < 192; ++end) {
+ TDynBitMap bm;
+ bm.Reserve(192);
+ bm.Set(start, end);
+ for (size_t k = 0; k < 192; ++k) {
+ UNIT_ASSERT_VALUES_EQUAL(bm.Get(k), k >= start && k < end ? 1 : 0);
+ }
+ }
+ }
+ }
+
+ Y_UNIT_TEST(TestResetLargeRangeDyn) {
+ TDynBitMap bm;
+ bm.Set(0);
+ bm.Reset(1, 2048);
+ bm.Set(2048);
+ for (size_t k = 0; k <= 2048; ++k) {
+ UNIT_ASSERT_VALUES_EQUAL(bm.Get(k), k >= 1 && k < 2048 ? 0 : 1);
+ }
+ }
+}
diff --git a/util/generic/bitops.cpp b/util/generic/bitops.cpp
new file mode 100644
index 0000000000..db5667a21f
--- /dev/null
+++ b/util/generic/bitops.cpp
@@ -0,0 +1,141 @@
+#include "bitops.h"
+
+namespace NBitOps {
+ namespace NPrivate {
+ const ui64 WORD_MASK[] = {
+ 0x0000000000000000ULL,
+ 0x0000000000000001ULL,
+ 0x0000000000000003ULL,
+ 0x0000000000000007ULL,
+ 0x000000000000000FULL,
+ 0x000000000000001FULL,
+ 0x000000000000003FULL,
+ 0x000000000000007FULL,
+ 0x00000000000000FFULL,
+ 0x00000000000001FFULL,
+ 0x00000000000003FFULL,
+ 0x00000000000007FFULL,
+ 0x0000000000000FFFULL,
+ 0x0000000000001FFFULL,
+ 0x0000000000003FFFULL,
+ 0x0000000000007FFFULL,
+ 0x000000000000FFFFULL,
+ 0x000000000001FFFFULL,
+ 0x000000000003FFFFULL,
+ 0x000000000007FFFFULL,
+ 0x00000000000FFFFFULL,
+ 0x00000000001FFFFFULL,
+ 0x00000000003FFFFFULL,
+ 0x00000000007FFFFFULL,
+ 0x0000000000FFFFFFULL,
+ 0x0000000001FFFFFFULL,
+ 0x0000000003FFFFFFULL,
+ 0x0000000007FFFFFFULL,
+ 0x000000000FFFFFFFULL,
+ 0x000000001FFFFFFFULL,
+ 0x000000003FFFFFFFULL,
+ 0x000000007FFFFFFFULL,
+ 0x00000000FFFFFFFFULL,
+ 0x00000001FFFFFFFFULL,
+ 0x00000003FFFFFFFFULL,
+ 0x00000007FFFFFFFFULL,
+ 0x0000000FFFFFFFFFULL,
+ 0x0000001FFFFFFFFFULL,
+ 0x0000003FFFFFFFFFULL,
+ 0x0000007FFFFFFFFFULL,
+ 0x000000FFFFFFFFFFULL,
+ 0x000001FFFFFFFFFFULL,
+ 0x000003FFFFFFFFFFULL,
+ 0x000007FFFFFFFFFFULL,
+ 0x00000FFFFFFFFFFFULL,
+ 0x00001FFFFFFFFFFFULL,
+ 0x00003FFFFFFFFFFFULL,
+ 0x00007FFFFFFFFFFFULL,
+ 0x0000FFFFFFFFFFFFULL,
+ 0x0001FFFFFFFFFFFFULL,
+ 0x0003FFFFFFFFFFFFULL,
+ 0x0007FFFFFFFFFFFFULL,
+ 0x000FFFFFFFFFFFFFULL,
+ 0x001FFFFFFFFFFFFFULL,
+ 0x003FFFFFFFFFFFFFULL,
+ 0x007FFFFFFFFFFFFFULL,
+ 0x00FFFFFFFFFFFFFFULL,
+ 0x01FFFFFFFFFFFFFFULL,
+ 0x03FFFFFFFFFFFFFFULL,
+ 0x07FFFFFFFFFFFFFFULL,
+ 0x0FFFFFFFFFFFFFFFULL,
+ 0x1FFFFFFFFFFFFFFFULL,
+ 0x3FFFFFFFFFFFFFFFULL,
+ 0x7FFFFFFFFFFFFFFFULL,
+ 0xFFFFFFFFFFFFFFFFULL,
+ };
+
+ const ui64 INVERSE_WORD_MASK[] = {
+ ~0x0000000000000000ULL,
+ ~0x0000000000000001ULL,
+ ~0x0000000000000003ULL,
+ ~0x0000000000000007ULL,
+ ~0x000000000000000FULL,
+ ~0x000000000000001FULL,
+ ~0x000000000000003FULL,
+ ~0x000000000000007FULL,
+ ~0x00000000000000FFULL,
+ ~0x00000000000001FFULL,
+ ~0x00000000000003FFULL,
+ ~0x00000000000007FFULL,
+ ~0x0000000000000FFFULL,
+ ~0x0000000000001FFFULL,
+ ~0x0000000000003FFFULL,
+ ~0x0000000000007FFFULL,
+ ~0x000000000000FFFFULL,
+ ~0x000000000001FFFFULL,
+ ~0x000000000003FFFFULL,
+ ~0x000000000007FFFFULL,
+ ~0x00000000000FFFFFULL,
+ ~0x00000000001FFFFFULL,
+ ~0x00000000003FFFFFULL,
+ ~0x00000000007FFFFFULL,
+ ~0x0000000000FFFFFFULL,
+ ~0x0000000001FFFFFFULL,
+ ~0x0000000003FFFFFFULL,
+ ~0x0000000007FFFFFFULL,
+ ~0x000000000FFFFFFFULL,
+ ~0x000000001FFFFFFFULL,
+ ~0x000000003FFFFFFFULL,
+ ~0x000000007FFFFFFFULL,
+ ~0x00000000FFFFFFFFULL,
+ ~0x00000001FFFFFFFFULL,
+ ~0x00000003FFFFFFFFULL,
+ ~0x00000007FFFFFFFFULL,
+ ~0x0000000FFFFFFFFFULL,
+ ~0x0000001FFFFFFFFFULL,
+ ~0x0000003FFFFFFFFFULL,
+ ~0x0000007FFFFFFFFFULL,
+ ~0x000000FFFFFFFFFFULL,
+ ~0x000001FFFFFFFFFFULL,
+ ~0x000003FFFFFFFFFFULL,
+ ~0x000007FFFFFFFFFFULL,
+ ~0x00000FFFFFFFFFFFULL,
+ ~0x00001FFFFFFFFFFFULL,
+ ~0x00003FFFFFFFFFFFULL,
+ ~0x00007FFFFFFFFFFFULL,
+ ~0x0000FFFFFFFFFFFFULL,
+ ~0x0001FFFFFFFFFFFFULL,
+ ~0x0003FFFFFFFFFFFFULL,
+ ~0x0007FFFFFFFFFFFFULL,
+ ~0x000FFFFFFFFFFFFFULL,
+ ~0x001FFFFFFFFFFFFFULL,
+ ~0x003FFFFFFFFFFFFFULL,
+ ~0x007FFFFFFFFFFFFFULL,
+ ~0x00FFFFFFFFFFFFFFULL,
+ ~0x01FFFFFFFFFFFFFFULL,
+ ~0x03FFFFFFFFFFFFFFULL,
+ ~0x07FFFFFFFFFFFFFFULL,
+ ~0x0FFFFFFFFFFFFFFFULL,
+ ~0x1FFFFFFFFFFFFFFFULL,
+ ~0x3FFFFFFFFFFFFFFFULL,
+ ~0x7FFFFFFFFFFFFFFFULL,
+ ~0xFFFFFFFFFFFFFFFFULL,
+ };
+ }
+}
diff --git a/util/generic/bitops.h b/util/generic/bitops.h
new file mode 100644
index 0000000000..2db15fc59b
--- /dev/null
+++ b/util/generic/bitops.h
@@ -0,0 +1,459 @@
+#pragma once
+
+#include "ylimits.h"
+#include "typelist.h"
+
+#include <util/system/compiler.h>
+#include <util/system/yassert.h>
+
+#ifdef _MSC_VER
+ #include <intrin.h>
+#endif
+
+namespace NBitOps {
+ namespace NPrivate {
+ template <unsigned N, typename T>
+ struct TClp2Helper {
+ static Y_FORCE_INLINE T Calc(T t) noexcept {
+ const T prev = TClp2Helper<N / 2, T>::Calc(t);
+
+ return prev | (prev >> N);
+ }
+ };
+
+ template <typename T>
+ struct TClp2Helper<0u, T> {
+ static Y_FORCE_INLINE T Calc(T t) noexcept {
+ return t - 1;
+ }
+ };
+
+ extern const ui64 WORD_MASK[];
+ extern const ui64 INVERSE_WORD_MASK[];
+
+ // see http://www-graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
+
+ Y_FORCE_INLINE ui64 SwapOddEvenBits(ui64 v) {
+ return ((v >> 1ULL) & 0x5555555555555555ULL) | ((v & 0x5555555555555555ULL) << 1ULL);
+ }
+
+ Y_FORCE_INLINE ui64 SwapBitPairs(ui64 v) {
+ return ((v >> 2ULL) & 0x3333333333333333ULL) | ((v & 0x3333333333333333ULL) << 2ULL);
+ }
+
+ Y_FORCE_INLINE ui64 SwapNibbles(ui64 v) {
+ return ((v >> 4ULL) & 0x0F0F0F0F0F0F0F0FULL) | ((v & 0x0F0F0F0F0F0F0F0FULL) << 4ULL);
+ }
+
+ Y_FORCE_INLINE ui64 SwapOddEvenBytes(ui64 v) {
+ return ((v >> 8ULL) & 0x00FF00FF00FF00FFULL) | ((v & 0x00FF00FF00FF00FFULL) << 8ULL);
+ }
+
+ Y_FORCE_INLINE ui64 SwapBytePairs(ui64 v) {
+ return ((v >> 16ULL) & 0x0000FFFF0000FFFFULL) | ((v & 0x0000FFFF0000FFFFULL) << 16ULL);
+ }
+
+ Y_FORCE_INLINE ui64 SwapByteQuads(ui64 v) {
+ return (v >> 32ULL) | (v << 32ULL);
+ }
+
+#if defined(__GNUC__)
+ inline unsigned GetValueBitCountImpl(unsigned int value) noexcept {
+ Y_ASSERT(value); // because __builtin_clz* have undefined result for zero.
+ return std::numeric_limits<unsigned int>::digits - __builtin_clz(value);
+ }
+
+ inline unsigned GetValueBitCountImpl(unsigned long value) noexcept {
+ Y_ASSERT(value); // because __builtin_clz* have undefined result for zero.
+ return std::numeric_limits<unsigned long>::digits - __builtin_clzl(value);
+ }
+
+ inline unsigned GetValueBitCountImpl(unsigned long long value) noexcept {
+ Y_ASSERT(value); // because __builtin_clz* have undefined result for zero.
+ return std::numeric_limits<unsigned long long>::digits - __builtin_clzll(value);
+ }
+#else
+ /// Stupid realization for non-GCC. Can use BSR from x86 instructions set.
+ template <typename T>
+ inline unsigned GetValueBitCountImpl(T value) noexcept {
+ Y_ASSERT(value); // because __builtin_clz* have undefined result for zero.
+ unsigned result = 1; // result == 0 - impossible value, see Y_ASSERT().
+ value >>= 1;
+ while (value) {
+ value >>= 1;
+ ++result;
+ }
+
+ return result;
+ }
+#endif
+
+#if defined(__GNUC__)
+ inline unsigned CountTrailingZeroBitsImpl(unsigned int value) noexcept {
+ Y_ASSERT(value); // because __builtin_ctz* have undefined result for zero.
+ return __builtin_ctz(value);
+ }
+
+ inline unsigned CountTrailingZeroBitsImpl(unsigned long value) noexcept {
+ Y_ASSERT(value); // because __builtin_ctz* have undefined result for zero.
+ return __builtin_ctzl(value);
+ }
+
+ inline unsigned CountTrailingZeroBitsImpl(unsigned long long value) noexcept {
+ Y_ASSERT(value); // because __builtin_ctz* have undefined result for zero.
+ return __builtin_ctzll(value);
+ }
+#else
+ /// Stupid realization for non-GCC. Can use BSF from x86 instructions set.
+ template <typename T>
+ inline unsigned CountTrailingZeroBitsImpl(T value) noexcept {
+ Y_ASSERT(value); // because __builtin_ctz* have undefined result for zero.
+ unsigned result = 0;
+ while (!(value & 1)) {
+ value >>= 1;
+ ++result;
+ }
+
+ return result;
+ }
+#endif
+
+ template <typename T>
+ Y_FORCE_INLINE T RotateBitsLeftImpl(T value, const ui8 shift) noexcept {
+ constexpr ui8 bits = sizeof(T) * 8;
+ constexpr ui8 mask = bits - 1;
+ Y_ASSERT(shift <= mask);
+
+ // do trick with mask to avoid undefined behaviour
+ return (value << shift) | (value >> ((-shift) & mask));
+ }
+
+ template <typename T>
+ Y_FORCE_INLINE T RotateBitsRightImpl(T value, const ui8 shift) noexcept {
+ constexpr ui8 bits = sizeof(T) * 8;
+ constexpr ui8 mask = bits - 1;
+ Y_ASSERT(shift <= mask);
+
+ // do trick with mask to avoid undefined behaviour
+ return (value >> shift) | (value << ((-shift) & mask));
+ }
+
+#if defined(_x86_) && defined(__GNUC__)
+ Y_FORCE_INLINE ui8 RotateBitsRightImpl(ui8 value, ui8 shift) noexcept {
+ __asm__("rorb %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ Y_FORCE_INLINE ui16 RotateBitsRightImpl(ui16 value, ui8 shift) noexcept {
+ __asm__("rorw %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ Y_FORCE_INLINE ui32 RotateBitsRightImpl(ui32 value, ui8 shift) noexcept {
+ __asm__("rorl %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ Y_FORCE_INLINE ui8 RotateBitsLeftImpl(ui8 value, ui8 shift) noexcept {
+ __asm__("rolb %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ Y_FORCE_INLINE ui16 RotateBitsLeftImpl(ui16 value, ui8 shift) noexcept {
+ __asm__("rolw %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ Y_FORCE_INLINE ui32 RotateBitsLeftImpl(ui32 value, ui8 shift) noexcept {
+ __asm__("roll %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ #if defined(_x86_64_)
+ Y_FORCE_INLINE ui64 RotateBitsRightImpl(ui64 value, ui8 shift) noexcept {
+ __asm__("rorq %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+
+ Y_FORCE_INLINE ui64 RotateBitsLeftImpl(ui64 value, ui8 shift) noexcept {
+ __asm__("rolq %%cl, %0"
+ : "=r"(value)
+ : "0"(value), "c"(shift));
+ return value;
+ }
+ #endif
+#endif
+ }
+}
+
+/**
+ * Computes the next power of 2 higher or equal to the integer parameter `t`.
+ * If `t` is a power of 2 will return `t`.
+ * Result is undefined for `t == 0`.
+ */
+template <typename T>
+static inline T FastClp2(T t) noexcept {
+ Y_ASSERT(t > 0);
+ using TCvt = typename ::TUnsignedInts::template TSelectBy<TSizeOfPredicate<sizeof(T)>::template TResult>::type;
+ return 1 + ::NBitOps::NPrivate::TClp2Helper<sizeof(TCvt) * 4, T>::Calc(static_cast<TCvt>(t));
+}
+
+/**
+ * Check if integer is a power of 2.
+ */
+template <typename T>
+Y_CONST_FUNCTION constexpr bool IsPowerOf2(T v) noexcept {
+ return v > 0 && (v & (v - 1)) == 0;
+}
+
+/**
+ * Returns the number of leading 0-bits in `value`, starting at the most significant bit position.
+ */
+template <typename T>
+static inline unsigned GetValueBitCount(T value) noexcept {
+ Y_ASSERT(value > 0);
+ using TCvt = typename ::TUnsignedInts::template TSelectBy<TSizeOfPredicate<sizeof(T)>::template TResult>::type;
+ return ::NBitOps::NPrivate::GetValueBitCountImpl(static_cast<TCvt>(value));
+}
+
+/**
+ * Returns the number of trailing 0-bits in `value`, starting at the least significant bit position
+ */
+template <typename T>
+static inline unsigned CountTrailingZeroBits(T value) noexcept {
+ Y_ASSERT(value > 0);
+ using TCvt = typename ::TUnsignedInts::template TSelectBy<TSizeOfPredicate<sizeof(T)>::template TResult>::type;
+ return ::NBitOps::NPrivate::CountTrailingZeroBitsImpl(static_cast<TCvt>(value));
+}
+
+/*
+ * Returns 64-bit mask with `bits` lower bits set.
+ */
+Y_FORCE_INLINE ui64 MaskLowerBits(ui64 bits) {
+ return ::NBitOps::NPrivate::WORD_MASK[bits];
+}
+
+/*
+ * Return 64-bit mask with `bits` set starting from `skipbits`.
+ */
+Y_FORCE_INLINE ui64 MaskLowerBits(ui64 bits, ui64 skipbits) {
+ return MaskLowerBits(bits) << skipbits;
+}
+
+/*
+ * Return 64-bit mask with all bits set except for `bits` lower bits.
+ */
+Y_FORCE_INLINE ui64 InverseMaskLowerBits(ui64 bits) {
+ return ::NBitOps::NPrivate::INVERSE_WORD_MASK[bits];
+}
+
+/*
+ * Return 64-bit mask with all bits set except for `bits` bitst starting from `skipbits`.
+ */
+Y_FORCE_INLINE ui64 InverseMaskLowerBits(ui64 bits, ui64 skipbits) {
+ return ~MaskLowerBits(bits, skipbits);
+}
+
+/*
+ * Returns 0-based position of the most significant bit that is set. 0 for 0.
+ */
+Y_FORCE_INLINE ui64 MostSignificantBit(ui64 v) {
+#ifdef __GNUC__
+ ui64 res = v ? (63 - __builtin_clzll(v)) : 0;
+#elif defined(_MSC_VER) && defined(_64_)
+ unsigned long res = 0;
+ if (v)
+ _BitScanReverse64(&res, v);
+#else
+ ui64 res = 0;
+ if (v)
+ while (v >>= 1)
+ ++res;
+#endif
+ return res;
+}
+
+/**
+ * Returns 0-based position of the least significant bit that is set. 0 for 0.
+ */
+Y_FORCE_INLINE ui64 LeastSignificantBit(ui64 v) {
+#ifdef __GNUC__
+ ui64 res = v ? __builtin_ffsll(v) - 1 : 0;
+#elif defined(_MSC_VER) && defined(_64_)
+ unsigned long res = 0;
+ if (v)
+ _BitScanForward64(&res, v);
+#else
+ ui64 res = 0;
+ if (v) {
+ while (!(v & 1)) {
+ ++res;
+ v >>= 1;
+ }
+ }
+#endif
+ return res;
+}
+
+/*
+ * Returns 0 - based position of the most significant bit (compile time)
+ * 0 for 0.
+ */
+constexpr ui64 MostSignificantBitCT(ui64 x) {
+ return x > 1 ? 1 + MostSignificantBitCT(x >> 1) : 0;
+}
+
+/*
+ * Return rounded up binary logarithm of `x`.
+ */
+Y_FORCE_INLINE ui8 CeilLog2(ui64 x) {
+ return static_cast<ui8>(MostSignificantBit(x - 1)) + 1;
+}
+
+Y_FORCE_INLINE ui8 ReverseBytes(ui8 t) {
+ return t;
+}
+
+Y_FORCE_INLINE ui16 ReverseBytes(ui16 t) {
+ return static_cast<ui16>(::NBitOps::NPrivate::SwapOddEvenBytes(t));
+}
+
+Y_FORCE_INLINE ui32 ReverseBytes(ui32 t) {
+ return static_cast<ui32>(::NBitOps::NPrivate::SwapBytePairs(
+ ::NBitOps::NPrivate::SwapOddEvenBytes(t)));
+}
+
+Y_FORCE_INLINE ui64 ReverseBytes(ui64 t) {
+ return ::NBitOps::NPrivate::SwapByteQuads((::NBitOps::NPrivate::SwapOddEvenBytes(t)));
+}
+
+Y_FORCE_INLINE ui8 ReverseBits(ui8 t) {
+ return static_cast<ui8>(::NBitOps::NPrivate::SwapNibbles(
+ ::NBitOps::NPrivate::SwapBitPairs(
+ ::NBitOps::NPrivate::SwapOddEvenBits(t))));
+}
+
+Y_FORCE_INLINE ui16 ReverseBits(ui16 t) {
+ return static_cast<ui16>(::NBitOps::NPrivate::SwapOddEvenBytes(
+ ::NBitOps::NPrivate::SwapNibbles(
+ ::NBitOps::NPrivate::SwapBitPairs(
+ ::NBitOps::NPrivate::SwapOddEvenBits(t)))));
+}
+
+Y_FORCE_INLINE ui32 ReverseBits(ui32 t) {
+ return static_cast<ui32>(::NBitOps::NPrivate::SwapBytePairs(
+ ::NBitOps::NPrivate::SwapOddEvenBytes(
+ ::NBitOps::NPrivate::SwapNibbles(
+ ::NBitOps::NPrivate::SwapBitPairs(
+ ::NBitOps::NPrivate::SwapOddEvenBits(t))))));
+}
+
+Y_FORCE_INLINE ui64 ReverseBits(ui64 t) {
+ return ::NBitOps::NPrivate::SwapByteQuads(
+ ::NBitOps::NPrivate::SwapBytePairs(
+ ::NBitOps::NPrivate::SwapOddEvenBytes(
+ ::NBitOps::NPrivate::SwapNibbles(
+ ::NBitOps::NPrivate::SwapBitPairs(
+ ::NBitOps::NPrivate::SwapOddEvenBits(t))))));
+}
+
+/*
+ * Reverse first `bits` bits
+ * 1000111000111000 , bits = 6 => 1000111000000111
+ */
+template <typename T>
+Y_FORCE_INLINE T ReverseBits(T v, ui64 bits) {
+ return bits ? (T(v & ::InverseMaskLowerBits(bits)) | T(ReverseBits(T(v & ::MaskLowerBits(bits)))) >> ((ui64{sizeof(T)} << ui64{3}) - bits)) : v;
+}
+
+/*
+ * Referse first `bits` bits starting from `skipbits` bits
+ * 1000111000111000 , bits = 4, skipbits = 2 => 1000111000011100
+ */
+template <typename T>
+Y_FORCE_INLINE T ReverseBits(T v, ui64 bits, ui64 skipbits) {
+ return (T(ReverseBits((v >> skipbits), bits)) << skipbits) | T(v & MaskLowerBits(skipbits));
+}
+
+/* Rotate bits left. Also known as left circular shift.
+ */
+template <typename T>
+Y_FORCE_INLINE T RotateBitsLeft(T value, const ui8 shift) noexcept {
+ static_assert(std::is_unsigned<T>::value, "must be unsigned arithmetic type");
+ return ::NBitOps::NPrivate::RotateBitsLeftImpl((TFixedWidthUnsignedInt<T>)value, shift);
+}
+
+/* Rotate bits right. Also known as right circular shift.
+ */
+template <typename T>
+Y_FORCE_INLINE T RotateBitsRight(T value, const ui8 shift) noexcept {
+ static_assert(std::is_unsigned<T>::value, "must be unsigned arithmetic type");
+ return ::NBitOps::NPrivate::RotateBitsRightImpl((TFixedWidthUnsignedInt<T>)value, shift);
+}
+
+/* Rotate bits left. Also known as left circular shift.
+ */
+template <typename T>
+constexpr T RotateBitsLeftCT(T value, const ui8 shift) noexcept {
+ static_assert(std::is_unsigned<T>::value, "must be unsigned arithmetic type");
+
+ // do trick with mask to avoid undefined behaviour
+ return (value << shift) | (value >> ((-shift) & (sizeof(T) * 8 - 1)));
+}
+
+/* Rotate bits right. Also known as right circular shift.
+ */
+template <typename T>
+constexpr T RotateBitsRightCT(T value, const ui8 shift) noexcept {
+ static_assert(std::is_unsigned<T>::value, "must be unsigned arithmetic type");
+
+ // do trick with mask to avoid undefined behaviour
+ return (value >> shift) | (value << ((-shift) & (sizeof(T) * 8 - 1)));
+}
+
+/* Remain `size` bits to current `offset` of `value`
+ size, offset are less than number of bits in size_type
+ */
+template <size_t Offset, size_t Size, class T>
+Y_FORCE_INLINE T SelectBits(T value) {
+ static_assert(Size < sizeof(T) * 8, "violated: Size < sizeof(T) * 8");
+ static_assert(Offset < sizeof(T) * 8, "violated: Offset < sizeof(T) * 8");
+ T id = 1;
+ return (value >> Offset) & ((id << Size) - id);
+}
+
+/* Set `size` bits of `bits` to current offset of `value`. Requires that bits <= (1 << size) - 1
+ size, offset are less than number of bits in size_type
+ */
+template <size_t Offset, size_t Size, class T>
+void SetBits(T& value, T bits) {
+ static_assert(Size < sizeof(T) * 8, "violated: Size < sizeof(T) * 8");
+ static_assert(Offset < sizeof(T) * 8, "violated: Offset < sizeof(T) * 8");
+ T id = 1;
+ T maxValue = ((id << Size) - id);
+ Y_ASSERT(bits <= maxValue);
+ value &= ~(maxValue << Offset);
+ value |= bits << Offset;
+}
+
+inline constexpr ui64 NthBit64(int bit) {
+ return ui64(1) << bit;
+}
+
+inline constexpr ui64 Mask64(int bits) {
+ return NthBit64(bits) - 1;
+}
diff --git a/util/generic/bitops_ut.cpp b/util/generic/bitops_ut.cpp
new file mode 100644
index 0000000000..d23c2b5c27
--- /dev/null
+++ b/util/generic/bitops_ut.cpp
@@ -0,0 +1,348 @@
+#include "bitops.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/string/builder.h>
+
+template <typename T>
+static void TestCTZ() {
+ for (unsigned int i = 0; i < (sizeof(T) << 3); ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(CountTrailingZeroBits(T(1) << i), i);
+ }
+}
+
+template <typename T>
+static void TestFastClp2ForEachPowerOf2() {
+ for (size_t i = 0; i < sizeof(T) * 8 - 1; ++i) {
+ const auto current = T(1) << i;
+ UNIT_ASSERT_VALUES_EQUAL(FastClp2(current), current);
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(FastClp2(T(1)), T(1));
+ for (size_t i = 1; i < sizeof(T) * 8 - 1; ++i) {
+ for (size_t j = 0; j < i; ++j) {
+ const auto value = (T(1) << i) | (T(1) << j);
+ const auto next = T(1) << (i + 1);
+ UNIT_ASSERT_VALUES_EQUAL(FastClp2(value), next);
+ }
+ }
+}
+
+template <typename T>
+static T ReverseBitsSlow(T v) {
+ T r = v; // r will be reversed bits of v; first get LSB of v
+ ui32 s = sizeof(v) * 8 - 1; // extra shift needed at end
+
+ for (v >>= 1; v; v >>= 1) {
+ r <<= 1;
+ r |= v & 1;
+ --s;
+ }
+
+ r <<= s; // shift when v's highest bits are zero
+ return r;
+}
+
+// DO_NOT_STYLE
+Y_UNIT_TEST_SUITE(TBitOpsTest) {
+ Y_UNIT_TEST(TestCountTrailingZeroBits) {
+ TestCTZ<unsigned int>();
+ TestCTZ<unsigned long>();
+ TestCTZ<unsigned long long>();
+ }
+
+ Y_UNIT_TEST(TestIsPowerOf2) {
+ UNIT_ASSERT(!IsPowerOf2(-2));
+ UNIT_ASSERT(!IsPowerOf2(-1));
+ UNIT_ASSERT(!IsPowerOf2(0));
+ UNIT_ASSERT(IsPowerOf2(1));
+ UNIT_ASSERT(IsPowerOf2(2));
+ UNIT_ASSERT(!IsPowerOf2(3));
+ UNIT_ASSERT(IsPowerOf2(4));
+ UNIT_ASSERT(!IsPowerOf2(5));
+ UNIT_ASSERT(IsPowerOf2(0x10000000u));
+ UNIT_ASSERT(!IsPowerOf2(0x10000001u));
+ UNIT_ASSERT(IsPowerOf2(0x1000000000000000ull));
+ UNIT_ASSERT(!IsPowerOf2(0x1000000000000001ull));
+ }
+
+ Y_UNIT_TEST(TestFastClp2) {
+ TestFastClp2ForEachPowerOf2<unsigned>();
+ TestFastClp2ForEachPowerOf2<unsigned long>();
+ TestFastClp2ForEachPowerOf2<unsigned long long>();
+ }
+
+ Y_UNIT_TEST(TestMask) {
+ for (ui32 i = 0; i < 64; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(MaskLowerBits(i), (ui64{1} << i) - 1);
+ UNIT_ASSERT_VALUES_EQUAL(InverseMaskLowerBits(i), ~MaskLowerBits(i));
+ UNIT_ASSERT_VALUES_EQUAL(MaskLowerBits(i, i / 2), (ui64{1} << i) - 1 << (i / 2));
+ UNIT_ASSERT_VALUES_EQUAL(InverseMaskLowerBits(i, i / 2), ~MaskLowerBits(i, i / 2));
+ }
+ }
+
+ Y_UNIT_TEST(TestMostSignificantBit) {
+ static_assert(MostSignificantBitCT(0) == 0, ".");
+ static_assert(MostSignificantBitCT(1) == 0, ".");
+ static_assert(MostSignificantBitCT(5) == 2, ".");
+
+ for (ui32 i = 0; i < 64; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(i, MostSignificantBit(ui64{1} << i));
+ }
+
+ for (ui32 i = 0; i < 63; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(i + 1, MostSignificantBit(ui64{3} << i));
+ }
+ }
+
+ Y_UNIT_TEST(TestLeastSignificantBit) {
+ for (ui32 i = 0; i < 64; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(i, LeastSignificantBit(ui64{1} << i));
+ }
+
+ for (ui32 i = 0; i < 63; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(i, LeastSignificantBit(ui64{3} << i));
+ }
+
+ for (ui32 i = 0; i < 64; ++i) {
+ ui64 value = (ui64(-1)) << i;
+ UNIT_ASSERT_VALUES_EQUAL(i, LeastSignificantBit(value));
+ }
+ }
+
+ Y_UNIT_TEST(TestCeilLog2) {
+ UNIT_ASSERT_VALUES_EQUAL(CeilLog2(ui64{1}), 1);
+
+ for (ui32 i = 2; i < 64; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(CeilLog2(ui64{1} << i), i);
+ UNIT_ASSERT_VALUES_EQUAL(CeilLog2((ui64{1} << i) | ui64{1}), i + 1);
+ }
+ }
+
+ Y_UNIT_TEST(TestReverse) {
+ for (ui64 i = 0; i < 0x100; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui8)i), ReverseBitsSlow((ui8)i));
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui16)i), ReverseBitsSlow((ui16)i));
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui32)i), ReverseBitsSlow((ui32)i));
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui64)i), ReverseBitsSlow((ui64)i));
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui16)~i), ReverseBitsSlow((ui16)~i));
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui32)~i), ReverseBitsSlow((ui32)~i));
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits((ui64)~i), ReverseBitsSlow((ui64)~i));
+ }
+
+ ui32 v = 0xF0F0F0F0; // 11110000111100001111000011110000
+ for (ui32 i = 0; i < 4; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits(v, i + 1), v);
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits(v, 4 + 2 * i, 4 - i), v);
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits(v, 8), 0xF0F0F00Fu);
+ UNIT_ASSERT_VALUES_EQUAL(ReverseBits(v, 8, 4), 0xF0F0FF00u);
+
+ for (ui32 i = 0; i < 0x10000; ++i) {
+ for (ui32 j = 0; j <= 32; ++j) {
+ UNIT_ASSERT_VALUES_EQUAL_C(i, ReverseBits(ReverseBits(i, j), j), (TString)(TStringBuilder() << i << " " << j));
+ }
+ }
+ }
+
+ Y_UNIT_TEST(TestRotateBitsLeft) {
+ static_assert(RotateBitsLeftCT<ui8>(0b00000000u, 0) == 0b00000000u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b00000001u, 0) == 0b00000001u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b10000000u, 0) == 0b10000000u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b00000001u, 1) == 0b00000010u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b10000000u, 1) == 0b00000001u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b00000101u, 1) == 0b00001010u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b10100000u, 1) == 0b01000001u, "");
+ static_assert(RotateBitsLeftCT<ui8>(0b10000000u, 7) == 0b01000000u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b00000000u, 0), 0b00000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b00000001u, 0), 0b00000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b10000000u, 0), 0b10000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b00000001u, 1), 0b00000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b10000000u, 1), 0b00000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b00000101u, 1), 0b00001010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b10100000u, 1), 0b01000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui8>(0b10000000u, 7), 0b01000000u);
+
+ static_assert(RotateBitsLeftCT<ui16>(0b0000000000000000u, 0) == 0b0000000000000000u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b0000000000000001u, 0) == 0b0000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b1000000000000000u, 0) == 0b1000000000000000u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b0000000000000001u, 1) == 0b0000000000000010u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b1000000000000000u, 1) == 0b0000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b0000000000000101u, 1) == 0b0000000000001010u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b1010000000000000u, 1) == 0b0100000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui16>(0b1000000000000000u, 15) == 0b0100000000000000u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b0000000000000000u, 0), 0b0000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b0000000000000001u, 0), 0b0000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b1000000000000000u, 0), 0b1000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b0000000000000001u, 1), 0b0000000000000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b1000000000000000u, 1), 0b0000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b0000000000000101u, 1), 0b0000000000001010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b1010000000000000u, 1), 0b0100000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui16>(0b1000000000000000u, 15), 0b0100000000000000u);
+
+ static_assert(RotateBitsLeftCT<ui32>(0b00000000000000000000000000000000u, 0) == 0b00000000000000000000000000000000u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b00000000000000000000000000000001u, 0) == 0b00000000000000000000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b10000000000000000000000000000000u, 0) == 0b10000000000000000000000000000000u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b00000000000000000000000000000001u, 1) == 0b00000000000000000000000000000010u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b10000000000000000000000000000000u, 1) == 0b00000000000000000000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b00000000000000000000000000000101u, 1) == 0b00000000000000000000000000001010u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b10100000000000000000000000000000u, 1) == 0b01000000000000000000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui32>(0b10000000000000000000000000000000u, 31) == 0b01000000000000000000000000000000u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b00000000000000000000000000000000u, 0), 0b00000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b00000000000000000000000000000001u, 0), 0b00000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b10000000000000000000000000000000u, 0), 0b10000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b00000000000000000000000000000001u, 1), 0b00000000000000000000000000000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b10000000000000000000000000000000u, 1), 0b00000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b00000000000000000000000000000101u, 1), 0b00000000000000000000000000001010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b10100000000000000000000000000000u, 1), 0b01000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui32>(0b10000000000000000000000000000000u, 31), 0b01000000000000000000000000000000u);
+
+ static_assert(RotateBitsLeftCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000000u, 0) == 0b0000000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 0) == 0b0000000000000000000000000000000000000000000000000000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 0) == 0b1000000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 1) == 0b0000000000000000000000000000000000000000000000000000000000000010u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 1) == 0b0000000000000000000000000000000000000000000000000000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000101u, 1) == 0b0000000000000000000000000000000000000000000000000000000000001010u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b1010000000000000000000000000000000000000000000000000000000000000u, 1) == 0b0100000000000000000000000000000000000000000000000000000000000001u, "");
+ static_assert(RotateBitsLeftCT<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 63) == 0b0100000000000000000000000000000000000000000000000000000000000000u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b0000000000000000000000000000000000000000000000000000000000000000u, 0), 0b0000000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 0), 0b0000000000000000000000000000000000000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 0), 0b1000000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 1), 0b0000000000000000000000000000000000000000000000000000000000000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 1), 0b0000000000000000000000000000000000000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b0000000000000000000000000000000000000000000000000000000000000101u, 1), 0b0000000000000000000000000000000000000000000000000000000000001010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b1010000000000000000000000000000000000000000000000000000000000000u, 1), 0b0100000000000000000000000000000000000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsLeft<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 63), 0b0100000000000000000000000000000000000000000000000000000000000000u);
+ }
+
+ Y_UNIT_TEST(TestRotateBitsRight) {
+ static_assert(RotateBitsRightCT<ui8>(0b00000000u, 0) == 0b00000000u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b00000001u, 0) == 0b00000001u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b10000000u, 0) == 0b10000000u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b00000001u, 1) == 0b10000000u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b10000000u, 1) == 0b01000000u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b00000101u, 1) == 0b10000010u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b10100000u, 1) == 0b01010000u, "");
+ static_assert(RotateBitsRightCT<ui8>(0b00000001u, 7) == 0b00000010u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b00000000u, 0), 0b00000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b00000001u, 0), 0b00000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b10000000u, 0), 0b10000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b00000001u, 1), 0b10000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b10000000u, 1), 0b01000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b00000101u, 1), 0b10000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b10100000u, 1), 0b01010000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui8>(0b00000001u, 7), 0b00000010u);
+
+ static_assert(RotateBitsRightCT<ui16>(0b0000000000000000u, 0) == 0b0000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b0000000000000001u, 0) == 0b0000000000000001u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b1000000000000000u, 0) == 0b1000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b0000000000000001u, 1) == 0b1000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b1000000000000000u, 1) == 0b0100000000000000u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b0000000000000101u, 1) == 0b1000000000000010u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b1010000000000000u, 1) == 0b0101000000000000u, "");
+ static_assert(RotateBitsRightCT<ui16>(0b0000000000000001u, 15) == 0b0000000000000010u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b0000000000000000u, 0), 0b0000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b0000000000000001u, 0), 0b0000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b1000000000000000u, 0), 0b1000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b0000000000000001u, 1), 0b1000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b1000000000000000u, 1), 0b0100000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b0000000000000101u, 1), 0b1000000000000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b1010000000000000u, 1), 0b0101000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui16>(0b0000000000000001u, 15), 0b0000000000000010u);
+
+ static_assert(RotateBitsRightCT<ui32>(0b00000000000000000000000000000000u, 0) == 0b00000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b00000000000000000000000000000001u, 0) == 0b00000000000000000000000000000001u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b10000000000000000000000000000000u, 0) == 0b10000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b00000000000000000000000000000001u, 1) == 0b10000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b10000000000000000000000000000000u, 1) == 0b01000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b00000000000000000000000000000101u, 1) == 0b10000000000000000000000000000010u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b10100000000000000000000000000000u, 1) == 0b01010000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui32>(0b00000000000000000000000000000001u, 31) == 0b00000000000000000000000000000010u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b00000000000000000000000000000000u, 0), 0b00000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b00000000000000000000000000000001u, 0), 0b00000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b10000000000000000000000000000000u, 0), 0b10000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b00000000000000000000000000000001u, 1), 0b10000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b10000000000000000000000000000000u, 1), 0b01000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b00000000000000000000000000000101u, 1), 0b10000000000000000000000000000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b10100000000000000000000000000000u, 1), 0b01010000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui32>(0b00000000000000000000000000000001u, 31), 0b00000000000000000000000000000010u);
+
+ static_assert(RotateBitsRightCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000000u, 0) == 0b0000000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 0) == 0b0000000000000000000000000000000000000000000000000000000000000001u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 0) == 0b1000000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 1) == 0b1000000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 1) == 0b0100000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000101u, 1) == 0b1000000000000000000000000000000000000000000000000000000000000010u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b1010000000000000000000000000000000000000000000000000000000000000u, 1) == 0b0101000000000000000000000000000000000000000000000000000000000000u, "");
+ static_assert(RotateBitsRightCT<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 63) == 0b0000000000000000000000000000000000000000000000000000000000000010u, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b0000000000000000000000000000000000000000000000000000000000000000u, 0), 0b0000000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 0), 0b0000000000000000000000000000000000000000000000000000000000000001u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 0), 0b1000000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 1), 0b1000000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b1000000000000000000000000000000000000000000000000000000000000000u, 1), 0b0100000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b0000000000000000000000000000000000000000000000000000000000000101u, 1), 0b1000000000000000000000000000000000000000000000000000000000000010u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b1010000000000000000000000000000000000000000000000000000000000000u, 1), 0b0101000000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(RotateBitsRight<ui64>(0b0000000000000000000000000000000000000000000000000000000000000001u, 63), 0b0000000000000000000000000000000000000000000000000000000000000010u);
+ }
+
+ Y_UNIT_TEST(TestSelectBits) {
+ ui8 firstui8Test = SelectBits<3, 4, ui8>(0b11111111u);
+ ui8 secondui8Test = SelectBits<2, 5, ui8>(0b11101101u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui8Test, 0b00001111u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui8Test, 0b00011011u);
+
+ ui16 firstui16Test = SelectBits<9, 2, ui16>(0b1111111111111111u);
+ ui16 secondui16Test = SelectBits<3, 6, ui16>(0b1010011111010001u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui16Test, 0b0000000000000011u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui16Test, 0b0000000000111010u);
+
+ ui32 firstui32Test = SelectBits<23, 31, ui32>(0b11111111111111111111111111111111u);
+ ui32 secondui32Test = SelectBits<0, 31, ui32>(0b10001011101010011111010000111111u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui32Test, 0b00000000000000000000000111111111u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui32Test, 0b00001011101010011111010000111111);
+
+ ui64 firstui64Test = SelectBits<1, 62, ui64>(0b1111000000000000000000000000000000000000000000000000000000000000u);
+ ui64 secondui64Test = SelectBits<32, 43, ui64>(0b1111111111111111111111111111111111111111111111111111111111111111u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui64Test, 0b0011100000000000000000000000000000000000000000000000000000000000u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui64Test, 0b0000000000000000000000000000000011111111111111111111111111111111u);
+ }
+
+ Y_UNIT_TEST(TestSetBits) {
+ ui8 firstui8Test = 0b11111111u;
+ SetBits<3, 4, ui8>(firstui8Test, 0b00001111u);
+ ui8 secondui8Test = 0b11101101u;
+ SetBits<2, 7, ui8>(secondui8Test, 0b01110111u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui8Test, 0b11111111u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui8Test, 0b11011101u);
+
+ ui16 firstui16Test = 0b1111111111111111u;
+ SetBits<9, 4, ui16>(firstui16Test, 0b000000000000111u);
+ ui16 secondui16Test = 0b1010011111010001u;
+ SetBits<3, 15, ui16>(secondui16Test, 0b0010011111010001u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui16Test, 0b1110111111111111u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui16Test, 0b0011111010001001u);
+
+ ui32 firstui32Test = 0b11111111111111111111111111111111u;
+ SetBits<23, 31, ui32>(firstui32Test, 0b01100001111111111001111101111111u);
+ ui32 secondui32Test = 0b10001011101010011111010000111111u;
+ SetBits<0, 31, ui32>(secondui32Test, 0b01111111111111111111111111111111u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui32Test, 0b10111111111111111111111111111111u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui32Test, 0b11111111111111111111111111111111u);
+
+ ui64 firstui64Test = 0b1111000000000000000000000000000000000000000000000000000000000000u;
+ SetBits<1, 62, ui64>(firstui64Test, 0b0001000000000000000000000000000000000000000000000000000001010101u);
+ ui64 secondui64Test = 0b1111111111111111111111111111111111111111111111111111111111111111u;
+ SetBits<32, 43, ui64>(secondui64Test, 0b0000000000000000000000000000000000000111111111111111111111111111u);
+ UNIT_ASSERT_VALUES_EQUAL(firstui64Test, 0b1010000000000000000000000000000000000000000000000000000010101010u);
+ UNIT_ASSERT_VALUES_EQUAL(secondui64Test, 0b0000011111111111111111111111111111111111111111111111111111111111u);
+ }
+};
diff --git a/util/generic/bt_exception.cpp b/util/generic/bt_exception.cpp
new file mode 100644
index 0000000000..609870c4c0
--- /dev/null
+++ b/util/generic/bt_exception.cpp
@@ -0,0 +1 @@
+#include "bt_exception.h"
diff --git a/util/generic/bt_exception.h b/util/generic/bt_exception.h
new file mode 100644
index 0000000000..018d2bc89a
--- /dev/null
+++ b/util/generic/bt_exception.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <utility>
+#include "yexception.h"
+
+#include <util/system/backtrace.h>
+
+template <class T>
+class TWithBackTrace: public T {
+public:
+ template <typename... Args>
+ inline TWithBackTrace(Args&&... args)
+ : T(std::forward<Args>(args)...)
+ {
+ BT_.Capture();
+ }
+
+ const TBackTrace* BackTrace() const noexcept override {
+ return &BT_;
+ }
+
+private:
+ TBackTrace BT_;
+};
diff --git a/util/generic/buffer.cpp b/util/generic/buffer.cpp
new file mode 100644
index 0000000000..b92697e1d0
--- /dev/null
+++ b/util/generic/buffer.cpp
@@ -0,0 +1,99 @@
+#include "buffer.h"
+#include "mem_copy.h"
+#include "string.h"
+#include "ymath.h"
+
+#include <util/system/sys_alloc.h>
+#include <util/system/sanitizers.h>
+
+TBuffer::TBuffer(size_t len)
+ : Data_(nullptr)
+ , Len_(0)
+ , Pos_(0)
+{
+ Reserve(len);
+}
+
+TBuffer::TBuffer(TBuffer&& b) noexcept
+ : Data_(nullptr)
+ , Len_(0)
+ , Pos_(0)
+{
+ Swap(b);
+}
+
+TBuffer::TBuffer(const char* buf, size_t len)
+ : Data_(nullptr)
+ , Len_(0)
+ , Pos_(0)
+{
+ Append(buf, len);
+}
+
+TBuffer& TBuffer::operator=(TBuffer&& b) noexcept {
+ y_deallocate(Data_);
+
+ Data_ = b.Data_;
+ Len_ = b.Len_;
+ Pos_ = b.Pos_;
+
+ b.Data_ = nullptr;
+ b.Len_ = 0;
+ b.Pos_ = 0;
+
+ return *this;
+}
+
+void TBuffer::Append(const char* buf, size_t len) {
+ if (len > Avail()) {
+ Reserve(Pos_ + len);
+ }
+
+ Y_ASSERT(len <= Avail());
+
+ MemCopy(Data() + Pos_, buf, len);
+ NSan::Unpoison(Data() + Pos_, len);
+ Pos_ += len;
+
+ Y_ASSERT(Pos_ <= Len_);
+}
+
+void TBuffer::Fill(char ch, size_t len) {
+ if (len > Avail()) {
+ Reserve(Pos_ + len);
+ }
+
+ Y_ASSERT(len <= Avail());
+
+ memset(Data() + Pos_, ch, len);
+ NSan::Unpoison(Data() + Pos_, len);
+ Pos_ += len;
+
+ Y_ASSERT(Pos_ <= Len_);
+}
+
+void TBuffer::DoReserve(size_t realLen) {
+ // FastClp2<T>(x) returns 0 on x from [Max<T>/2 + 2, Max<T>]
+ const size_t len = Max<size_t>(FastClp2(realLen), realLen);
+
+ Y_ASSERT(realLen > Len_);
+ Y_ASSERT(len >= realLen);
+
+ Realloc(len);
+}
+
+void TBuffer::Realloc(size_t len) {
+ Y_ASSERT(Pos_ <= len);
+
+ Data_ = (char*)y_reallocate(Data_, len);
+ Len_ = len;
+}
+
+TBuffer::~TBuffer() {
+ y_deallocate(Data_);
+}
+
+void TBuffer::AsString(TString& s) {
+ s.assign(Data(), Size());
+ Clear();
+}
diff --git a/util/generic/buffer.h b/util/generic/buffer.h
new file mode 100644
index 0000000000..9576467404
--- /dev/null
+++ b/util/generic/buffer.h
@@ -0,0 +1,245 @@
+#pragma once
+
+#include "utility.h"
+
+#include <util/generic/fwd.h>
+#include <util/system/align.h>
+#include <util/system/yassert.h>
+
+#include <cstring>
+
+class TBuffer {
+public:
+ using TIterator = char*;
+ using TConstIterator = const char*;
+
+ TBuffer(size_t len = 0);
+ TBuffer(const char* buf, size_t len);
+
+ TBuffer(const TBuffer& b)
+ : Data_(nullptr)
+ , Len_(0)
+ , Pos_(0)
+ {
+ *this = b;
+ }
+
+ TBuffer(TBuffer&& b) noexcept;
+
+ TBuffer& operator=(TBuffer&& b) noexcept;
+
+ TBuffer& operator=(const TBuffer& b) {
+ if (this != &b) {
+ Assign(b.Data(), b.Size());
+ }
+ return *this;
+ }
+
+ ~TBuffer();
+
+ inline void Clear() noexcept {
+ Pos_ = 0;
+ }
+
+ inline void EraseBack(size_t n) noexcept {
+ Y_ASSERT(n <= Pos_);
+ Pos_ -= n;
+ }
+
+ inline void Reset() noexcept {
+ TBuffer().Swap(*this);
+ }
+
+ inline void Assign(const char* data, size_t len) {
+ Clear();
+ Append(data, len);
+ }
+
+ inline void Assign(const char* b, const char* e) {
+ Assign(b, e - b);
+ }
+
+ inline char* Data() noexcept {
+ return Data_;
+ }
+
+ inline const char* Data() const noexcept {
+ return Data_;
+ }
+
+ inline char* Pos() noexcept {
+ return Data_ + Pos_;
+ }
+
+ inline const char* Pos() const noexcept {
+ return Data_ + Pos_;
+ }
+
+ /// Used space in bytes (do not mix with Capacity!)
+ inline size_t Size() const noexcept {
+ return Pos_;
+ }
+
+ Y_PURE_FUNCTION inline bool Empty() const noexcept {
+ return !Size();
+ }
+
+ inline explicit operator bool() const noexcept {
+ return Size();
+ }
+
+ inline size_t Avail() const noexcept {
+ return Len_ - Pos_;
+ }
+
+ void Append(const char* buf, size_t len);
+
+ inline void Append(const char* b, const char* e) {
+ Append(b, e - b);
+ }
+
+ inline void Append(char ch) {
+ if (Len_ == Pos_) {
+ Reserve(Len_ + 1);
+ }
+
+ *(Data() + Pos_++) = ch;
+ }
+
+ void Fill(char ch, size_t len);
+
+ // Method is useful when first messages from buffer are processed, and
+ // the last message in buffer is incomplete, so we need to move partial
+ // message to the begin of the buffer and continue filling the buffer
+ // from the network.
+ inline void Chop(size_t pos, size_t count) {
+ const auto end = pos + count;
+ Y_ASSERT(end <= Pos_);
+
+ if (count == 0) {
+ return;
+ } else if (count == Pos_) {
+ Pos_ = 0;
+ } else {
+ memmove(Data_ + pos, Data_ + end, Pos_ - end);
+ Pos_ -= count;
+ }
+ }
+
+ inline void ChopHead(size_t count) {
+ Chop(0U, count);
+ }
+
+ inline void Proceed(size_t pos) {
+ //Y_ASSERT(pos <= Len_); // see discussion in REVIEW:29021
+ Resize(pos);
+ }
+
+ inline void Advance(size_t len) {
+ Resize(Pos_ + len);
+ }
+
+ inline void Reserve(size_t len) {
+ if (len > Len_) {
+ DoReserve(len);
+ }
+ }
+
+ inline void ReserveExactNeverCallMeInSaneCode(size_t len) {
+ if (len > Len_) {
+ Realloc(len);
+ }
+ }
+
+ inline void ShrinkToFit() {
+ if (Pos_ < Len_) {
+ Realloc(Pos_);
+ }
+ }
+
+ inline void Resize(size_t len) {
+ Reserve(len);
+ Pos_ = len;
+ }
+
+ // Method works like Resize, but allocates exact specified number of bytes
+ // rather than rounded up to next power of 2
+ // Use with care
+ inline void ResizeExactNeverCallMeInSaneCode(size_t len) {
+ ReserveExactNeverCallMeInSaneCode(len);
+ Pos_ = len;
+ }
+
+ inline size_t Capacity() const noexcept {
+ return Len_;
+ }
+
+ inline void AlignUp(size_t align, char fillChar = '\0') {
+ size_t diff = ::AlignUp(Pos_, align) - Pos_;
+ while (diff-- > 0) {
+ Append(fillChar);
+ }
+ }
+
+ inline char* data() noexcept {
+ return Data();
+ }
+
+ inline const char* data() const noexcept {
+ return Data();
+ }
+
+ inline size_t size() const noexcept {
+ return Size();
+ }
+
+ inline void Swap(TBuffer& r) noexcept {
+ DoSwap(Data_, r.Data_);
+ DoSwap(Len_, r.Len_);
+ DoSwap(Pos_, r.Pos_);
+ }
+
+ /*
+ * after this call buffer becomes empty
+ */
+ void AsString(TString& s);
+
+ /*
+ * iterator-like interface
+ */
+ inline TIterator Begin() noexcept {
+ return Data();
+ }
+
+ inline TIterator End() noexcept {
+ return Begin() + Size();
+ }
+
+ inline TConstIterator Begin() const noexcept {
+ return Data();
+ }
+
+ inline TConstIterator End() const noexcept {
+ return Begin() + Size();
+ }
+
+ bool operator==(const TBuffer& other) const noexcept {
+ if (Empty()) {
+ return other.Empty();
+ }
+ return Size() == other.Size() && 0 == std::memcmp(Data(), other.Data(), Size());
+ }
+
+ bool operator!=(const TBuffer& other) const noexcept {
+ return !(*this == other);
+ }
+
+private:
+ void DoReserve(size_t len);
+ void Realloc(size_t len);
+
+private:
+ char* Data_;
+ size_t Len_;
+ size_t Pos_;
+};
diff --git a/util/generic/buffer_ut.cpp b/util/generic/buffer_ut.cpp
new file mode 100644
index 0000000000..437d7122ec
--- /dev/null
+++ b/util/generic/buffer_ut.cpp
@@ -0,0 +1,205 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/system/datetime.h>
+#include "string.h"
+#include "vector.h"
+#include "buffer.h"
+
+Y_UNIT_TEST_SUITE(TBufferTest) {
+ Y_UNIT_TEST(TestEraseBack) {
+ TBuffer buf;
+
+ buf.Append("1234567", 7);
+ buf.Reserve(1000);
+ buf.Resize(6);
+ buf.EraseBack(2);
+
+ UNIT_ASSERT_EQUAL(TString(buf.data(), buf.size()), "1234");
+ }
+
+ Y_UNIT_TEST(TestAppend) {
+ const char data[] = "1234567890qwertyuiop";
+
+ TBuffer buf(13);
+ TString str;
+
+ for (size_t i = 0; i < 10; ++i) {
+ for (size_t j = 0; j < sizeof(data) - 1; ++j) {
+ buf.Append(data, j);
+ buf.Append('q');
+ str.append(data, j);
+ str.append('q');
+ }
+ }
+
+ UNIT_ASSERT_EQUAL(TString(buf.data(), buf.size()), str);
+ }
+
+ Y_UNIT_TEST(TestReset) {
+ char content[] = "some text";
+ TBuffer buf;
+
+ buf.Append(content, sizeof(content));
+ buf.Clear();
+
+ UNIT_ASSERT(buf.Capacity() != 0);
+
+ buf.Append(content, sizeof(content));
+ buf.Reset();
+
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 0);
+ }
+
+ Y_UNIT_TEST(TestResize) {
+ char content[] = "some text";
+ TBuffer buf;
+
+ buf.Resize(10);
+ UNIT_ASSERT_VALUES_EQUAL(buf.size(), 10u);
+
+ buf.Resize(0);
+ UNIT_ASSERT_VALUES_EQUAL(buf.size(), 0u);
+
+ buf.Resize(9);
+ memcpy(buf.data(), content, 9);
+ UNIT_ASSERT_VALUES_EQUAL(TString(buf.data(), buf.size()), "some text");
+
+ buf.Resize(4);
+ UNIT_ASSERT_VALUES_EQUAL(TString(buf.data(), buf.size()), "some");
+ }
+
+ Y_UNIT_TEST(TestReserve) {
+ TBuffer buf;
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 0);
+
+ buf.Reserve(4);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 4);
+
+ buf.Reserve(6);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 8);
+
+ buf.Reserve(32);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 32);
+
+ buf.Reserve(33);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 64);
+ buf.Reserve(64);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 64);
+
+ buf.Resize(128);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 128);
+
+ buf.Append('a');
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 256);
+ TString tmp1 = "abcdef";
+ buf.Append(tmp1.data(), tmp1.size());
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 256);
+
+ TString tmp2 = "30498290sfokdsflj2308w";
+ buf.Resize(1020);
+ buf.Append(tmp2.data(), tmp2.size());
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 2048);
+ }
+
+ Y_UNIT_TEST(TestShrinkToFit) {
+ TBuffer buf;
+
+ TString content = "some text";
+ buf.Append(content.data(), content.size());
+ UNIT_ASSERT_EQUAL(buf.Size(), 9);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 16);
+
+ buf.ShrinkToFit();
+ UNIT_ASSERT_EQUAL(buf.Size(), 9);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 9);
+ UNIT_ASSERT_EQUAL(TString(buf.data(), buf.size()), content);
+
+ const size_t MB = 1024 * 1024;
+ buf.Resize(MB);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), MB);
+ buf.ShrinkToFit();
+ UNIT_ASSERT_EQUAL(buf.Capacity(), MB);
+ buf.Resize(MB + 100);
+ UNIT_ASSERT_EQUAL(buf.Capacity(), 2 * MB);
+ buf.ShrinkToFit();
+ UNIT_ASSERT_EQUAL(buf.Capacity(), MB + 100);
+ }
+
+#if 0
+Y_UNIT_TEST(TestAlignUp) {
+ char content[] = "some text";
+ TBuffer buf;
+
+ buf.Append(content, sizeof(content));
+ buf.AlignUp(4, '!');
+
+ UNIT_ASSERT(buf.Size() % 4 == 0);
+ UNIT_ASSERT_VALUES_EQUAL(TString(~buf, +buf), "some text!!!");
+
+ char addContent[] = "1234";
+ buf.Append(addContent, sizeof(addContent));
+ buf.AlignUp(4, 'X');
+ UNIT_ASSERT(buf.Size() % 4 == 0);
+ UNIT_ASSERT_VALUES_EQUAL(TString(~buf, +buf), "some text!!!1234");
+}
+#endif
+
+#if 0
+Y_UNIT_TEST(TestSpeed) {
+ const char data[] = "1234567890qwertyuiop";
+ const size_t c = 100000;
+ ui64 t1 = 0;
+ ui64 t2 = 0;
+
+ {
+ TBuffer buf;
+
+ t1 = MicroSeconds();
+
+ for (size_t i = 0; i < c; ++i) {
+ buf.Append(data, sizeof(data));
+ }
+
+ t1 = MicroSeconds() - t1;
+ }
+
+ {
+ TVector<char> buf;
+
+ t2 = MicroSeconds();
+
+ for (size_t i = 0; i < c; ++i) {
+ buf.insert(buf.end(), data, data + sizeof(data));
+ }
+
+ t2 = MicroSeconds() - t2;
+ }
+
+ UNIT_ASSERT(t1 < t2);
+}
+#endif
+
+ Y_UNIT_TEST(TestFillAndChop) {
+ TBuffer buf;
+ buf.Append("Some ", 5);
+ buf.Fill('!', 5);
+ buf.Append(" text.", 6);
+ UNIT_ASSERT_VALUES_EQUAL(TString(buf.data(), buf.size()), "Some !!!!! text.");
+
+ buf.Chop(5, 6);
+ UNIT_ASSERT_VALUES_EQUAL(TString(buf.data(), buf.size()), "Some text.");
+ }
+
+ Y_UNIT_TEST(TestComparison) {
+ TBuffer buf1("abcd", 4);
+ TBuffer buf2("abcde", 5);
+ TBuffer empty;
+ UNIT_ASSERT(empty == empty);
+ UNIT_ASSERT(!(empty != empty));
+ UNIT_ASSERT(buf1 != buf2);
+ UNIT_ASSERT(buf1 == buf1);
+ buf2.EraseBack(1);
+ UNIT_ASSERT(buf2 == buf1);
+ }
+
+}
diff --git a/util/generic/cast.cpp b/util/generic/cast.cpp
new file mode 100644
index 0000000000..11f82364d3
--- /dev/null
+++ b/util/generic/cast.cpp
@@ -0,0 +1 @@
+#include "cast.h"
diff --git a/util/generic/cast.h b/util/generic/cast.h
new file mode 100644
index 0000000000..0d4a41f385
--- /dev/null
+++ b/util/generic/cast.h
@@ -0,0 +1,176 @@
+#pragma once
+
+#include "typetraits.h"
+#include "yexception.h"
+
+#include <util/system/compat.h>
+#include <util/system/type_name.h>
+#include <util/system/unaligned_mem.h>
+#include <util/system/yassert.h>
+
+#include <cstdlib>
+
+template <class T, class F>
+static inline T VerifyDynamicCast(F f) {
+ if (!f) {
+ return nullptr;
+ }
+
+ T ret = dynamic_cast<T>(f);
+
+ Y_VERIFY(ret, "verify cast failed");
+
+ return ret;
+}
+
+#if !defined(NDEBUG)
+ #define USE_DEBUG_CHECKED_CAST
+#endif
+
+namespace NPrivate {
+ template <typename T, typename F>
+ static T DynamicCast(F f) {
+ return dynamic_cast<T>(f);
+ }
+}
+
+/*
+ * replacement for dynamic_cast(dynamic_cast in debug mode, else static_cast)
+ */
+template <class T, class F>
+static inline T CheckedCast(F f) {
+#if defined(USE_DEBUG_CHECKED_CAST)
+ return VerifyDynamicCast<T>(f);
+#else
+ /* Make sure F is polymorphic.
+ * Without this cast, CheckedCast with non-polymorphic F
+ * incorrectly compiled without error in release mode.
+ */
+ {
+ auto&& x = &::NPrivate::DynamicCast<T, F>;
+
+ (void)x;
+ }
+
+ return static_cast<T>(f);
+#endif // USE_DEBUG_CHECKED_CAST
+}
+
+/*
+ * be polite
+ */
+#undef USE_DEBUG_CHECKED_CAST
+
+template <bool isUnsigned>
+class TInteger;
+
+template <>
+class TInteger<true> {
+public:
+ template <class TUnsigned>
+ static constexpr bool IsNegative(TUnsigned) noexcept {
+ return false;
+ }
+};
+
+template <>
+class TInteger<false> {
+public:
+ template <class TSigned>
+ static constexpr bool IsNegative(const TSigned value) noexcept {
+ return value < 0;
+ }
+};
+
+template <class TType>
+constexpr bool IsNegative(const TType value) noexcept {
+ return TInteger<std::is_unsigned<TType>::value>::IsNegative(value);
+}
+
+namespace NPrivate {
+ template <class T>
+ using TUnderlyingTypeOrSelf = typename std::conditional<
+ std::is_enum<T>::value,
+ std::underlying_type<T>, // Lazy evaluatuion: do not call ::type here, because underlying_type<T> is undefined if T is not an enum.
+ std::enable_if<true, T> // Wrapping T in a class, that has member ::type typedef.
+ >::type::type; // Left ::type is for std::conditional, right ::type is for underlying_type/enable_if
+
+ template <class TSmall, class TLarge>
+ struct TSafelyConvertible {
+ using TSmallInt = TUnderlyingTypeOrSelf<TSmall>;
+ using TLargeInt = TUnderlyingTypeOrSelf<TLarge>;
+
+ static constexpr bool Result = std::is_integral<TSmallInt>::value && std::is_integral<TLargeInt>::value &&
+ ((std::is_signed<TSmallInt>::value == std::is_signed<TLargeInt>::value && sizeof(TSmallInt) >= sizeof(TLargeInt)) ||
+ (std::is_signed<TSmallInt>::value && sizeof(TSmallInt) > sizeof(TLargeInt)));
+ };
+}
+
+template <class TSmallInt, class TLargeInt>
+constexpr std::enable_if_t<::NPrivate::TSafelyConvertible<TSmallInt, TLargeInt>::Result, TSmallInt> SafeIntegerCast(TLargeInt largeInt) noexcept {
+ return static_cast<TSmallInt>(largeInt);
+}
+
+template <class TSmall, class TLarge>
+inline std::enable_if_t<!::NPrivate::TSafelyConvertible<TSmall, TLarge>::Result, TSmall> SafeIntegerCast(TLarge largeInt) {
+ using TSmallInt = ::NPrivate::TUnderlyingTypeOrSelf<TSmall>;
+ using TLargeInt = ::NPrivate::TUnderlyingTypeOrSelf<TLarge>;
+
+ if (std::is_unsigned<TSmallInt>::value && std::is_signed<TLargeInt>::value) {
+ if (IsNegative(largeInt)) {
+ ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
+ << TypeName<TSmallInt>()
+ << "', negative value converted to unsigned";
+ }
+ }
+
+ TSmallInt smallInt = TSmallInt(largeInt);
+
+ if (std::is_signed<TSmallInt>::value && std::is_unsigned<TLargeInt>::value) {
+ if (IsNegative(smallInt)) {
+ ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
+ << TypeName<TSmallInt>()
+ << "', positive value converted to negative";
+ }
+ }
+
+ if (TLargeInt(smallInt) != largeInt) {
+ ythrow TBadCastException() << "Conversion '" << TypeName<TLarge>() << '{' << TLargeInt(largeInt) << "}' to '"
+ << TypeName<TSmallInt>() << "', loss of data";
+ }
+
+ return static_cast<TSmall>(smallInt);
+}
+
+template <class TSmallInt, class TLargeInt>
+inline TSmallInt IntegerCast(TLargeInt largeInt) noexcept {
+ try {
+ return SafeIntegerCast<TSmallInt>(largeInt);
+ } catch (const yexception& exc) {
+ Y_FAIL("IntegerCast: %s", exc.what());
+ }
+}
+
+/* Convert given enum value to its underlying type. This is just a shortcut for
+ * `static_cast<std::underlying_type_t<EEnum>>(enum_)`.
+ */
+template <typename T>
+constexpr std::underlying_type_t<T> ToUnderlying(const T enum_) noexcept {
+ return static_cast<std::underlying_type_t<T>>(enum_);
+}
+
+// std::bit_cast from c++20
+template <class TTarget, class TSource>
+TTarget BitCast(const TSource& source) {
+ static_assert(sizeof(TSource) == sizeof(TTarget), "Size mismatch");
+ static_assert(std::is_trivially_copyable<TSource>::value, "TSource is not trivially copyable");
+ static_assert(std::is_trivial<TTarget>::value, "TTarget is not trivial");
+
+ // Support volatile qualifiers.
+ // ReadUnaligned does not work with volatile pointers, so cast away
+ // volatileness beforehand.
+ using TNonvolatileSource = std::remove_volatile_t<TSource>;
+ using TNonvolatileTarget = std::remove_volatile_t<TTarget>;
+
+ return ReadUnaligned<TNonvolatileTarget>(&const_cast<const TNonvolatileSource&>(source));
+}
diff --git a/util/generic/cast_ut.cpp b/util/generic/cast_ut.cpp
new file mode 100644
index 0000000000..718a8de79d
--- /dev/null
+++ b/util/generic/cast_ut.cpp
@@ -0,0 +1,112 @@
+#include "cast.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+class TGenericCastsTest: public TTestBase {
+ UNIT_TEST_SUITE(TGenericCastsTest);
+ UNIT_TEST(TestVerifyDynamicCast)
+ UNIT_TEST(TestIntegralCast)
+ UNIT_TEST(TestEnumCast)
+ UNIT_TEST(TestToUnderlying)
+ UNIT_TEST(TestBitCast)
+ UNIT_TEST_SUITE_END();
+
+private:
+ struct TAaa {
+ virtual ~TAaa() = default;
+ };
+ struct TBbb: public TAaa {};
+
+ inline void TestVerifyDynamicCast() {
+ TBbb bbb;
+ TAaa* aaa = &bbb;
+ TAaa* aaa2 = VerifyDynamicCast<TBbb*>(aaa);
+ UNIT_ASSERT(aaa == aaa2);
+ }
+
+ void TestIntegralCast() {
+ UNIT_ASSERT_EXCEPTION(SafeIntegerCast<ui32>(-5), TBadCastException);
+ UNIT_ASSERT_EXCEPTION(SafeIntegerCast<ui16>(static_cast<i32>(Max<ui16>() + 10)), TBadCastException);
+ UNIT_ASSERT_EXCEPTION(SafeIntegerCast<ui16>(static_cast<ui32>(Max<ui16>() + 10)), TBadCastException);
+ }
+
+ inline void TestEnumCast() {
+ enum A {
+ AM1 = -1
+ };
+
+ enum B: int {
+ BM1 = -1
+ };
+
+ enum class C: unsigned short {
+ CM1 = 1
+ };
+
+ UNIT_ASSERT_EXCEPTION(SafeIntegerCast<unsigned int>(AM1), TBadCastException);
+ UNIT_ASSERT_EXCEPTION(SafeIntegerCast<unsigned int>(BM1), TBadCastException);
+ UNIT_ASSERT_EXCEPTION(SafeIntegerCast<C>(AM1), TBadCastException);
+ UNIT_ASSERT_EXCEPTION(static_cast<int>(SafeIntegerCast<C>(BM1)), TBadCastException);
+ UNIT_ASSERT(SafeIntegerCast<A>(BM1) == AM1);
+ UNIT_ASSERT(SafeIntegerCast<B>(AM1) == BM1);
+ UNIT_ASSERT(SafeIntegerCast<A>(C::CM1) == 1);
+ UNIT_ASSERT(SafeIntegerCast<B>(C::CM1) == 1);
+ UNIT_ASSERT(SafeIntegerCast<A>(-1) == AM1);
+ UNIT_ASSERT(SafeIntegerCast<B>(-1) == BM1);
+ UNIT_ASSERT(SafeIntegerCast<C>(1) == C::CM1);
+ }
+
+ void TestToUnderlying() {
+ enum A {
+ AM1 = -1
+ };
+
+ enum B: int {
+ BM1 = -1
+ };
+
+ enum class C: unsigned short {
+ CM1 = 1
+ };
+
+ static_assert(static_cast<std::underlying_type_t<A>>(AM1) == ToUnderlying(AM1), "");
+ static_assert(static_cast<std::underlying_type_t<B>>(BM1) == ToUnderlying(BM1), "");
+ static_assert(static_cast<std::underlying_type_t<C>>(C::CM1) == ToUnderlying(C::CM1), "");
+
+ static_assert(std::is_same<std::underlying_type_t<A>, decltype(ToUnderlying(AM1))>::value, "");
+ static_assert(std::is_same<std::underlying_type_t<B>, decltype(ToUnderlying(BM1))>::value, "");
+ static_assert(std::is_same<std::underlying_type_t<C>, decltype(ToUnderlying(C::CM1))>::value, "");
+
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<std::underlying_type_t<A>>(AM1), ToUnderlying(AM1));
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<std::underlying_type_t<B>>(BM1), ToUnderlying(BM1));
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<std::underlying_type_t<C>>(C::CM1), ToUnderlying(C::CM1));
+ }
+
+ void TestBitCast() {
+ // Change sign of float
+ {
+ const float floatValue = 17.33f;
+ ui32 ui32Value = BitCast<ui32>(floatValue);
+ ui32Value ^= (ui32)1 << 31;
+ UNIT_ASSERT_VALUES_EQUAL(-floatValue, BitCast<float>(ui32Value));
+ }
+
+ // Unpack ui64 into a struct
+ {
+ const ui64 value = 0x1122334455667788;
+ struct TStruct {
+ ui32 a;
+ ui16 b;
+ ui8 c;
+ ui8 d;
+ };
+ auto structValue = BitCast<TStruct>(value);
+ UNIT_ASSERT_VALUES_EQUAL(structValue.a, 0x55667788);
+ UNIT_ASSERT_VALUES_EQUAL(structValue.b, 0x3344);
+ UNIT_ASSERT_VALUES_EQUAL(structValue.c, 0x22);
+ UNIT_ASSERT_VALUES_EQUAL(structValue.d, 0x11);
+ }
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TGenericCastsTest);
diff --git a/util/generic/deque.cpp b/util/generic/deque.cpp
new file mode 100644
index 0000000000..3c055780ab
--- /dev/null
+++ b/util/generic/deque.cpp
@@ -0,0 +1 @@
+#include "deque.h"
diff --git a/util/generic/deque.h b/util/generic/deque.h
new file mode 100644
index 0000000000..2dabaf3177
--- /dev/null
+++ b/util/generic/deque.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "fwd.h"
+
+#include <util/memory/alloc.h>
+
+#include <deque>
+#include <memory>
+#include <initializer_list>
+
+template <class T, class A>
+class TDeque: public std::deque<T, TReboundAllocator<A, T>> {
+ using TBase = std::deque<T, TReboundAllocator<A, T>>;
+
+public:
+ using TBase::TBase;
+
+ inline yssize_t ysize() const noexcept {
+ return (yssize_t)this->size();
+ }
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+};
diff --git a/util/generic/deque.pxd b/util/generic/deque.pxd
new file mode 100644
index 0000000000..62834ac2ad
--- /dev/null
+++ b/util/generic/deque.pxd
@@ -0,0 +1,9 @@
+from libcpp.deque cimport deque
+
+
+cdef extern from "<util/generic/deque.h>" nogil:
+ cdef cppclass TDeque[T](deque):
+ TDeque() except +
+ TDeque(size_t) except +
+ TDeque(size_t, const T&) except +
+ TDeque(const TDeque&) except +
diff --git a/util/generic/deque_ut.cpp b/util/generic/deque_ut.cpp
new file mode 100644
index 0000000000..93bf50fa92
--- /dev/null
+++ b/util/generic/deque_ut.cpp
@@ -0,0 +1,253 @@
+#include "deque.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <utility>
+#include "yexception.h"
+
+class TDequeTest: public TTestBase {
+ UNIT_TEST_SUITE(TDequeTest);
+ UNIT_TEST(TestConstructorsAndAssignments);
+ UNIT_TEST(TestDeque1);
+ UNIT_TEST(TestAt);
+ UNIT_TEST(TestInsert);
+ UNIT_TEST(TestErase);
+ UNIT_TEST(TestAutoRef);
+ UNIT_TEST_SUITE_END();
+
+protected:
+ void TestConstructorsAndAssignments();
+ void TestDeque1();
+ void TestInsert();
+ void TestErase();
+ void TestAt();
+ void TestAutoRef();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TDequeTest);
+
+void TDequeTest::TestConstructorsAndAssignments() {
+ using container = TDeque<int>;
+
+ container c1;
+ c1.push_back(100);
+ c1.push_back(200);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(100, c1.at(0));
+ UNIT_ASSERT_VALUES_EQUAL(200, c2.at(1));
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(100, c3.at(0));
+
+ c2.push_back(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(300, c3.at(2));
+
+ c2.push_back(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(400, c3.at(3));
+
+ int array[] = {2, 3, 4};
+ container c4 = {2, 3, 4};
+ UNIT_ASSERT_VALUES_EQUAL(c4, container(std::begin(array), std::end(array)));
+}
+
+void TDequeTest::TestDeque1() {
+ TDeque<int> d;
+ UNIT_ASSERT(!d);
+
+ d.push_back(4);
+ d.push_back(9);
+ d.push_back(16);
+ d.push_front(1);
+
+ UNIT_ASSERT(d);
+
+ UNIT_ASSERT(d[0] == 1);
+ UNIT_ASSERT(d[1] == 4);
+ UNIT_ASSERT(d[2] == 9);
+ UNIT_ASSERT(d[3] == 16);
+
+ d.pop_front();
+ d[2] = 25;
+
+ UNIT_ASSERT(d[0] == 4);
+ UNIT_ASSERT(d[1] == 9);
+ UNIT_ASSERT(d[2] == 25);
+
+ //Some compile time tests:
+ TDeque<int>::iterator dit = d.begin();
+ TDeque<int>::const_iterator cdit(d.begin());
+
+ UNIT_ASSERT((dit - cdit) == 0);
+ UNIT_ASSERT((cdit - dit) == 0);
+ UNIT_ASSERT((dit - dit) == 0);
+ UNIT_ASSERT((cdit - cdit) == 0);
+ UNIT_ASSERT(!((dit < cdit) || (dit > cdit) || (dit != cdit) || !(dit <= cdit) || !(dit >= cdit)));
+}
+
+void TDequeTest::TestInsert() {
+ TDeque<int> d;
+ d.push_back(0);
+ d.push_back(1);
+ d.push_back(2);
+
+ UNIT_ASSERT(d.size() == 3);
+
+ TDeque<int>::iterator dit;
+
+ //Insertion before begin:
+ dit = d.insert(d.begin(), 3);
+ UNIT_ASSERT(dit != d.end());
+ UNIT_ASSERT(*dit == 3);
+ UNIT_ASSERT(d.size() == 4);
+ UNIT_ASSERT(d[0] == 3);
+
+ //Insertion after begin:
+ dit = d.insert(d.begin() + 1, 4);
+ UNIT_ASSERT(dit != d.end());
+ UNIT_ASSERT(*dit == 4);
+ UNIT_ASSERT(d.size() == 5);
+ UNIT_ASSERT(d[1] == 4);
+
+ //Insertion at end:
+ dit = d.insert(d.end(), 5);
+ UNIT_ASSERT(dit != d.end());
+ UNIT_ASSERT(*dit == 5);
+ UNIT_ASSERT(d.size() == 6);
+ UNIT_ASSERT(d[5] == 5);
+
+ //Insertion before last element:
+ dit = d.insert(d.end() - 1, 6);
+ UNIT_ASSERT(dit != d.end());
+ UNIT_ASSERT(*dit == 6);
+ UNIT_ASSERT(d.size() == 7);
+ UNIT_ASSERT(d[5] == 6);
+
+ //Insertion of several elements before begin
+ d.insert(d.begin(), 2, 7);
+ UNIT_ASSERT(d.size() == 9);
+ UNIT_ASSERT(d[0] == 7);
+ UNIT_ASSERT(d[1] == 7);
+
+ //Insertion of several elements after begin
+ //There is more elements to insert than elements before insertion position
+ d.insert(d.begin() + 1, 2, 8);
+ UNIT_ASSERT(d.size() == 11);
+ UNIT_ASSERT(d[1] == 8);
+ UNIT_ASSERT(d[2] == 8);
+
+ //There is less elements to insert than elements before insertion position
+ d.insert(d.begin() + 3, 2, 9);
+ UNIT_ASSERT(d.size() == 13);
+ UNIT_ASSERT(d[3] == 9);
+ UNIT_ASSERT(d[4] == 9);
+
+ //Insertion of several elements at end:
+ d.insert(d.end(), 2, 10);
+ UNIT_ASSERT(d.size() == 15);
+ UNIT_ASSERT(d[14] == 10);
+ UNIT_ASSERT(d[13] == 10);
+
+ //Insertion of several elements before last:
+ //There is more elements to insert than elements after insertion position
+ d.insert(d.end() - 1, 2, 11);
+ UNIT_ASSERT(d.size() == 17);
+ UNIT_ASSERT(d[15] == 11);
+ UNIT_ASSERT(d[14] == 11);
+
+ //There is less elements to insert than elements after insertion position
+ d.insert(d.end() - 3, 2, 12);
+ UNIT_ASSERT(d.size() == 19);
+ UNIT_ASSERT(d[15] == 12);
+ UNIT_ASSERT(d[14] == 12);
+}
+
+void TDequeTest::TestAt() {
+ TDeque<int> d;
+ TDeque<int> const& cd = d;
+
+ d.push_back(10);
+ UNIT_ASSERT(d.at(0) == 10);
+ d.at(0) = 20;
+ UNIT_ASSERT(cd.at(0) == 20);
+
+ for (;;) {
+ try {
+ d.at(1) = 20;
+ UNIT_ASSERT(false);
+ } catch (...) {
+ return;
+ }
+ }
+}
+
+void TDequeTest::TestAutoRef() {
+ int i;
+ TDeque<int> ref;
+ for (i = 0; i < 5; ++i) {
+ ref.push_back(i);
+ }
+
+ TDeque<TDeque<int>> d_d_int(1, ref);
+ d_d_int.push_back(d_d_int[0]);
+ d_d_int.push_back(ref);
+ d_d_int.push_back(d_d_int[0]);
+ d_d_int.push_back(d_d_int[0]);
+ d_d_int.push_back(ref);
+
+ for (i = 0; i < 5; ++i) {
+ UNIT_ASSERT(d_d_int[i] == ref);
+ }
+}
+
+void TDequeTest::TestErase() {
+ TDeque<int> dint;
+ dint.push_back(3);
+ dint.push_front(2);
+ dint.push_back(4);
+ dint.push_front(1);
+ dint.push_back(5);
+ dint.push_front(0);
+ dint.push_back(6);
+
+ TDeque<int>::iterator it(dint.begin() + 1);
+ UNIT_ASSERT(*it == 1);
+
+ dint.erase(dint.begin());
+ UNIT_ASSERT(*it == 1);
+
+ it = dint.end() - 2;
+ UNIT_ASSERT(*it == 5);
+
+ dint.erase(dint.end() - 1);
+ UNIT_ASSERT(*it == 5);
+
+ dint.push_back(6);
+ dint.push_front(0);
+
+ it = dint.begin() + 2;
+ UNIT_ASSERT(*it == 2);
+
+ dint.erase(dint.begin(), dint.begin() + 2);
+ UNIT_ASSERT(*it == 2);
+
+ it = dint.end() - 3;
+ UNIT_ASSERT(*it == 4);
+
+ dint.erase(dint.end() - 2, dint.end());
+ UNIT_ASSERT(*it == 4);
+}
diff --git a/util/generic/deque_ut.pyx b/util/generic/deque_ut.pyx
new file mode 100644
index 0000000000..42cec42647
--- /dev/null
+++ b/util/generic/deque_ut.pyx
@@ -0,0 +1,66 @@
+from libcpp.deque cimport deque
+from util.generic.deque cimport TDeque
+
+import pytest
+import unittest
+
+
+class TestDeque(unittest.TestCase):
+ def test_ctor1(self):
+ cdef TDeque[int] tmp = TDeque[int]()
+ self.assertEqual(tmp.size(), 0)
+
+ def test_ctor2(self):
+ cdef TDeque[int] tmp = TDeque[int](10)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp[0], 0)
+
+ def test_ctor3(self):
+ cdef TDeque[int] tmp = TDeque[int](10, 42)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp[0], 42)
+
+ def test_ctor4(self):
+ cdef TDeque[int] tmp = TDeque[int](10, 42)
+ cdef TDeque[int] tmp2 = TDeque[int](tmp)
+ self.assertEqual(tmp2.size(), 10)
+ self.assertEqual(tmp2[0], 42)
+
+ def test_operator_assign(self):
+ cdef TDeque[int] tmp2
+ tmp2.push_back(1)
+ tmp2.push_back(2)
+
+ cdef TDeque[int] tmp3
+ tmp3.push_back(1)
+ tmp3.push_back(3)
+
+ self.assertEqual(tmp2[1], 2)
+ self.assertEqual(tmp3[1], 3)
+
+ tmp3 = tmp2
+
+ self.assertEqual(tmp2[1], 2)
+ self.assertEqual(tmp3[1], 2)
+
+ def test_compare(self):
+ cdef TDeque[int] tmp1
+ tmp1.push_back(1)
+ tmp1.push_back(2)
+
+ cdef TDeque[int] tmp2
+ tmp2.push_back(1)
+ tmp2.push_back(2)
+
+ cdef TDeque[int] tmp3
+ tmp3.push_back(1)
+ tmp3.push_back(3)
+
+ self.assertTrue(tmp1 == tmp2)
+ self.assertTrue(tmp1 != tmp3)
+
+ self.assertTrue(tmp1 < tmp3)
+ self.assertTrue(tmp1 <= tmp3)
+
+ self.assertTrue(tmp3 > tmp1)
+ self.assertTrue(tmp3 >= tmp1) \ No newline at end of file
diff --git a/util/generic/explicit_type.cpp b/util/generic/explicit_type.cpp
new file mode 100644
index 0000000000..65bc80093a
--- /dev/null
+++ b/util/generic/explicit_type.cpp
@@ -0,0 +1 @@
+#include "explicit_type.h"
diff --git a/util/generic/explicit_type.h b/util/generic/explicit_type.h
new file mode 100644
index 0000000000..34e269f155
--- /dev/null
+++ b/util/generic/explicit_type.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "typetraits.h"
+
+/**
+ * Helper type that can be used as one of the parameters in function declaration
+ * to limit the number of types this function can be called with.
+ *
+ * Example usage:
+ * @code
+ * void CharOnlyFunction(TExplicitType<char> value);
+ * void AnythingFunction(char value);
+ *
+ * CharOnlyFunction('c'); // Works.
+ * CharOnlyFunction(1); // Compilation error.
+ * CharOnlyFunction(1ull); // Compilation error.
+ *
+ * AnythingFunction('c'); // Works.
+ * AnythingFunction(1); // Works.
+ * AnythingFunction(1ull); // Works.
+ * @endcode
+ */
+template <class T>
+class TExplicitType {
+public:
+ template <class OtherT>
+ TExplicitType(const OtherT& value, std::enable_if_t<std::is_same<OtherT, T>::value>* = nullptr) noexcept
+ : Value_(value)
+ {
+ }
+
+ const T& Value() const noexcept {
+ return Value_;
+ }
+
+ operator const T&() const noexcept {
+ return Value_;
+ }
+
+private:
+ const T& Value_;
+};
diff --git a/util/generic/explicit_type_ut.cpp b/util/generic/explicit_type_ut.cpp
new file mode 100644
index 0000000000..50a745f090
--- /dev/null
+++ b/util/generic/explicit_type_ut.cpp
@@ -0,0 +1,52 @@
+#include "explicit_type.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+struct TCallableBase {
+public:
+ using TYes = char;
+ using TNo = struct { TYes dummy[32]; };
+
+ template <class T, class Arg>
+ static TNo Test(const T&, const Arg&, ...);
+
+ template <class T, class Arg>
+ static TYes Test(const T&, const Arg&, int, decltype(std::declval<T>()(std::declval<Arg>()))* = nullptr);
+};
+
+template <class T, class Arg>
+struct TCallable: public TCallableBase {
+ enum {
+ Result = sizeof(Test(std::declval<T>(), std::declval<Arg>(), 1)) == sizeof(TYes)
+ };
+};
+
+template <class T>
+struct TExplicitlyCallable {
+ void operator()(TExplicitType<T>) {
+ }
+};
+
+struct IntConvertible {
+ operator int() {
+ return 1;
+ }
+};
+
+struct IntConstructible {
+ IntConstructible(const int&) {
+ }
+};
+
+Y_UNIT_TEST_SUITE(TestExplicitType) {
+ Y_UNIT_TEST(Test1) {
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<char>, char>::Result), true);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<char>, int>::Result), false);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<char>, wchar_t>::Result), false);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<int>, int>::Result), true);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<int>, IntConvertible>::Result), false);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<IntConstructible>, IntConstructible>::Result), true);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<IntConstructible>, IntConvertible>::Result), false);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<bool>(TCallable<TExplicitlyCallable<IntConstructible>, int>::Result), false);
+ }
+}
diff --git a/util/generic/fastqueue.cpp b/util/generic/fastqueue.cpp
new file mode 100644
index 0000000000..140b3ef63e
--- /dev/null
+++ b/util/generic/fastqueue.cpp
@@ -0,0 +1 @@
+#include "fastqueue.h"
diff --git a/util/generic/fastqueue.h b/util/generic/fastqueue.h
new file mode 100644
index 0000000000..1fee5b86f6
--- /dev/null
+++ b/util/generic/fastqueue.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <util/memory/smallobj.h>
+#include "ptr.h"
+
+template <class T>
+class TFastQueue {
+ struct THelper: public TObjectFromPool<THelper>, public TIntrusiveListItem<THelper> {
+ inline THelper(const T& t)
+ : Obj(t)
+ {
+ }
+
+ T Obj;
+ };
+
+public:
+ inline TFastQueue()
+ : Pool_(TDefaultAllocator::Instance())
+ , Size_(0)
+ {
+ }
+
+ inline void Push(const T& t) {
+ Queue_.PushFront(new (&Pool_) THelper(t));
+ ++Size_;
+ }
+
+ inline T Pop() {
+ Y_ASSERT(!this->Empty());
+
+ THolder<THelper> tmp(Queue_.PopBack());
+ --Size_;
+
+ return tmp->Obj;
+ }
+
+ inline size_t Size() const noexcept {
+ return Size_;
+ }
+
+ Y_PURE_FUNCTION inline bool Empty() const noexcept {
+ return !this->Size();
+ }
+
+ inline explicit operator bool() const noexcept {
+ return !this->Empty();
+ }
+
+private:
+ typename THelper::TPool Pool_;
+ TIntrusiveListWithAutoDelete<THelper, TDelete> Queue_;
+ size_t Size_;
+};
diff --git a/util/generic/flags.cpp b/util/generic/flags.cpp
new file mode 100644
index 0000000000..2ee52188a0
--- /dev/null
+++ b/util/generic/flags.cpp
@@ -0,0 +1,29 @@
+#include "flags.h"
+
+#include <util/stream/format.h>
+#include <util/system/yassert.h>
+
+void ::NPrivate::PrintFlags(IOutputStream& stream, ui64 value, size_t size) {
+ /* Note that this function is in cpp because we need to break circular
+ * dependency between TFlags and ENumberFormat. */
+ stream << "TFlags(";
+
+ switch (size) {
+ case 1:
+ stream << Bin(static_cast<ui8>(value), HF_FULL);
+ break;
+ case 2:
+ stream << Bin(static_cast<ui16>(value), HF_FULL);
+ break;
+ case 4:
+ stream << Bin(static_cast<ui32>(value), HF_FULL);
+ break;
+ case 8:
+ stream << Bin(static_cast<ui64>(value), HF_FULL);
+ break;
+ default:
+ Y_VERIFY(false);
+ }
+
+ stream << ")";
+}
diff --git a/util/generic/flags.h b/util/generic/flags.h
new file mode 100644
index 0000000000..a1f5921d42
--- /dev/null
+++ b/util/generic/flags.h
@@ -0,0 +1,244 @@
+#pragma once
+
+#include <type_traits>
+
+#include <util/system/types.h>
+#include <util/generic/typetraits.h>
+#include <util/generic/fwd.h>
+
+class IOutputStream;
+namespace NPrivate {
+ void PrintFlags(IOutputStream& stream, ui64 value, size_t size);
+}
+
+/**
+ * `TFlags` wrapper provides a type-safe mechanism for storing OR combinations
+ * of enumeration values.
+ *
+ * This class is intended to be used mainly via helper macros. For example:
+ * @code
+ * class TAligner {
+ * public:
+ * enum EOrientation {
+ * Vertical = 1,
+ * Horizontal = 2
+ * };
+ * Y_DECLARE_FLAGS(EOrientations, EOrientation)
+ *
+ * // ...
+ * };
+ *
+ * Y_DECLARE_OPERATORS_FOR_FLAGS(TAligner::EOrientations)
+ * @endcode
+ */
+template <class Enum>
+class TFlags {
+ static_assert(std::is_enum<Enum>::value, "Expecting an enumeration here.");
+
+public:
+ using TEnum = Enum;
+ using TInt = std::underlying_type_t<Enum>;
+
+ constexpr TFlags(std::nullptr_t = 0)
+ : Value_(0)
+ {
+ }
+
+ constexpr TFlags(Enum value)
+ : Value_(static_cast<TInt>(value))
+ {
+ }
+
+ /* Generated copy/move ctor/assignment are OK. */
+
+ constexpr operator TInt() const {
+ return Value_;
+ }
+
+ constexpr TInt ToBaseType() const {
+ return Value_;
+ }
+
+ constexpr static TFlags FromBaseType(TInt value) {
+ return TFlags(TFlag(value));
+ }
+
+ constexpr friend TFlags operator|(TFlags l, TFlags r) {
+ return TFlags(TFlag(l.Value_ | r.Value_));
+ }
+
+ constexpr friend TFlags operator|(TEnum l, TFlags r) {
+ return TFlags(TFlag(static_cast<TInt>(l) | r.Value_));
+ }
+
+ constexpr friend TFlags operator|(TFlags l, TEnum r) {
+ return TFlags(TFlag(l.Value_ | static_cast<TInt>(r)));
+ }
+
+ constexpr friend TFlags operator^(TFlags l, TFlags r) {
+ return TFlags(TFlag(l.Value_ ^ r.Value_));
+ }
+
+ constexpr friend TFlags
+ operator^(TEnum l, TFlags r) {
+ return TFlags(TFlag(static_cast<TInt>(l) ^ r.Value_));
+ }
+
+ constexpr friend TFlags
+ operator^(TFlags l, TEnum r) {
+ return TFlags(TFlag(l.Value_ ^ static_cast<TInt>(r)));
+ }
+
+ constexpr friend TFlags
+ operator&(TFlags l, TFlags r) {
+ return TFlags(TFlag(l.Value_ & r.Value_));
+ }
+
+ constexpr friend TFlags operator&(TEnum l, TFlags r) {
+ return TFlags(TFlag(static_cast<TInt>(l) & r.Value_));
+ }
+
+ constexpr friend TFlags operator&(TFlags l, TEnum r) {
+ return TFlags(TFlag(l.Value_ & static_cast<TInt>(r)));
+ }
+
+ constexpr friend bool operator==(TFlags l, TFlags r) {
+ return l.Value_ == r.Value_;
+ }
+
+ constexpr friend bool operator==(TEnum l, TFlags r) {
+ return static_cast<TInt>(l) == r.Value_;
+ }
+
+ constexpr friend bool operator==(TFlags l, TEnum r) {
+ return l.Value_ == static_cast<TInt>(r);
+ }
+
+ constexpr friend bool operator!=(TFlags l, TFlags r) {
+ return l.Value_ != r.Value_;
+ }
+
+ constexpr friend bool operator!=(TEnum l, TFlags r) {
+ return static_cast<TInt>(l) != r.Value_;
+ }
+
+ constexpr friend bool operator!=(TFlags l, TEnum r) {
+ return l.Value_ != static_cast<TInt>(r);
+ }
+
+ TFlags& operator&=(TFlags mask) {
+ *this = *this & mask;
+ return *this;
+ }
+
+ TFlags& operator&=(Enum mask) {
+ *this = *this & mask;
+ return *this;
+ }
+
+ TFlags& operator|=(TFlags flags) {
+ *this = *this | flags;
+ return *this;
+ }
+
+ TFlags& operator|=(Enum flags) {
+ *this = *this | flags;
+ return *this;
+ }
+
+ TFlags& operator^=(TFlags flags) {
+ *this = *this ^ flags;
+ return *this;
+ }
+
+ TFlags& operator^=(Enum flags) {
+ *this = *this ^ flags;
+ return *this;
+ }
+
+ constexpr TFlags operator~() const {
+ return TFlags(TFlag(~Value_));
+ }
+
+ constexpr bool operator!() const {
+ return !Value_;
+ }
+
+ constexpr explicit operator bool() const {
+ return Value_;
+ }
+
+ constexpr bool HasFlags(TFlags flags) const {
+ return (Value_ & flags.Value_) == flags.Value_;
+ }
+
+ TFlags RemoveFlags(TFlags flags) {
+ Value_ &= ~flags.Value_;
+ return *this;
+ }
+
+ friend IOutputStream& operator<<(IOutputStream& stream, const TFlags& flags) {
+ ::NPrivate::PrintFlags(stream, static_cast<ui64>(flags.Value_), sizeof(TInt));
+ return stream;
+ }
+
+private:
+ struct TFlag {
+ constexpr TFlag() {
+ }
+ constexpr explicit TFlag(TInt value)
+ : Value(value)
+ {
+ }
+
+ TInt Value = 0;
+ };
+
+ constexpr explicit TFlags(TFlag value)
+ : Value_(value.Value)
+ {
+ }
+
+private:
+ TInt Value_;
+};
+
+template <class T>
+struct TPodTraits<TFlags<T>> {
+ enum {
+ IsPod = TTypeTraits<T>::IsPod
+ };
+};
+
+template <class Enum>
+struct THash<TFlags<Enum>> {
+ size_t operator()(const TFlags<Enum>& flags) const noexcept {
+ return THash<typename TFlags<Enum>::TInt>()(flags);
+ }
+};
+
+/**
+ * This macro defines a flags type for the provided enum.
+ *
+ * @param FLAGS Name of the flags type to declare.
+ * @param ENUM Name of the base enum type to use.
+ */
+#define Y_DECLARE_FLAGS(FLAGS, ENUM) \
+ using FLAGS = TFlags<ENUM>;
+
+/**
+ * This macro declares global operator functions for enum base of `FLAGS` type.
+ * This way operations on individual enum values will provide a type-safe
+ * `TFlags` object.
+ *
+ * @param FLAGS Flags type to declare operator for.
+ */
+#define Y_DECLARE_OPERATORS_FOR_FLAGS(FLAGS) \
+ Y_DECLARE_UNUSED \
+ constexpr inline FLAGS operator|(FLAGS::TEnum l, FLAGS::TEnum r) { \
+ return FLAGS(l) | r; \
+ } \
+ Y_DECLARE_UNUSED \
+ constexpr inline FLAGS operator~(FLAGS::TEnum value) { \
+ return ~FLAGS(value); \
+ }
diff --git a/util/generic/flags_ut.cpp b/util/generic/flags_ut.cpp
new file mode 100644
index 0000000000..5377c6a058
--- /dev/null
+++ b/util/generic/flags_ut.cpp
@@ -0,0 +1,117 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+#include "flags.h"
+
+enum ETestFlag1: ui16 {
+ Test1 = 1,
+ Test2 = 2,
+ Test4 = 4,
+ Test8 = 8
+};
+Y_DECLARE_FLAGS(ETest1, ETestFlag1)
+Y_DECLARE_OPERATORS_FOR_FLAGS(ETest1)
+
+static_assert(TTypeTraits<ETest1>::IsPod, "flags should be POD type");
+
+enum class ETestFlag2 {
+ Test1 = 1,
+ Test2 = 2,
+ Test4 = 4,
+ Test8 = 8
+};
+Y_DECLARE_FLAGS(ETest2, ETestFlag2)
+Y_DECLARE_OPERATORS_FOR_FLAGS(ETest2)
+
+namespace {
+ // won't compile without Y_DECLARE_UNUSED
+ enum class ETestFlag3 { One = 1,
+ Two = 2,
+ Three = 3 };
+ Y_DECLARE_FLAGS(ETestFlags3, ETestFlag3)
+ Y_DECLARE_OPERATORS_FOR_FLAGS(ETestFlags3)
+}
+
+Y_UNIT_TEST_SUITE(TFlagsTest) {
+ template <class Enum>
+ void TestEnum() {
+ {
+ auto i = Enum::Test1 | Enum::Test2;
+
+ UNIT_ASSERT((std::is_same<decltype(i), TFlags<Enum>>::value));
+ UNIT_ASSERT((std::is_same<decltype(~i), TFlags<Enum>>::value));
+ UNIT_ASSERT(!(std::is_same<decltype(i), int>::value));
+ UNIT_ASSERT_VALUES_EQUAL(sizeof(Enum), sizeof(TFlags<Enum>));
+
+ UNIT_ASSERT(i.HasFlags(Enum::Test1));
+ UNIT_ASSERT(i.HasFlags(Enum::Test4) == false);
+ UNIT_ASSERT(i.HasFlags(Enum::Test1 | Enum::Test4) == false);
+
+ i |= Enum::Test4;
+ i ^= Enum::Test2;
+ UNIT_ASSERT_EQUAL(i, Enum::Test4 | Enum::Test1);
+ UNIT_ASSERT_EQUAL(i & Enum::Test1, i & ~Enum::Test4);
+ UNIT_ASSERT(i & Enum::Test4);
+ UNIT_ASSERT_UNEQUAL(i, ~i);
+ UNIT_ASSERT_EQUAL(i, ~~i);
+ }
+ {
+ auto i = Enum::Test1 | Enum::Test2;
+ i.RemoveFlags(Enum::Test1);
+ UNIT_ASSERT_EQUAL(i, TFlags<Enum>(Enum::Test2));
+ }
+ {
+ auto i = Enum::Test1 | Enum::Test2;
+ i.RemoveFlags(Enum::Test1 | Enum::Test2);
+ UNIT_ASSERT_EQUAL(i, TFlags<Enum>());
+ }
+ }
+
+ Y_UNIT_TEST(TestFlags) {
+ TestEnum<ETestFlag1>();
+ TestEnum<ETestFlag2>();
+ }
+
+ Y_UNIT_TEST(TestZero) {
+ /* This code should simply compile. */
+
+ ETest1 f = 0;
+ f = 0;
+ f = ETest1(0);
+
+ ETest1 ff(0);
+ ff = 0;
+ }
+
+ Y_UNIT_TEST(TestOutput) {
+ ETest1 value0 = nullptr, value1 = Test1, value7 = Test1 | Test2 | Test4;
+
+ UNIT_ASSERT_VALUES_EQUAL(ToString(value0), "TFlags(0000000000000000)");
+ UNIT_ASSERT_VALUES_EQUAL(ToString(value1), "TFlags(0000000000000001)");
+ UNIT_ASSERT_VALUES_EQUAL(ToString(value7), "TFlags(0000000000000111)");
+ }
+
+ Y_UNIT_TEST(TestHash) {
+ ETest1 value0 = nullptr, value1 = Test1;
+
+ THashMap<ETest1, int> hash;
+ hash[value0] = 0;
+ hash[value1] = 1;
+
+ UNIT_ASSERT_VALUES_EQUAL(hash[value0], 0);
+ UNIT_ASSERT_VALUES_EQUAL(hash[value1], 1);
+ }
+
+ Y_UNIT_TEST(TestBaseType) {
+ ui16 goodValue = 7;
+ auto goodFlags = ETest1::FromBaseType(goodValue);
+ UNIT_ASSERT(goodFlags& ETestFlag1::Test1);
+ UNIT_ASSERT(goodFlags& ETestFlag1::Test2);
+ UNIT_ASSERT(goodFlags& ETestFlag1::Test4);
+ UNIT_ASSERT_VALUES_EQUAL(goodValue, goodFlags.ToBaseType());
+
+ // Passed value is not checked, but preserved as is
+ ui16 badValue = 1024;
+ auto badFlags = ETest1::FromBaseType(badValue);
+ UNIT_ASSERT_VALUES_EQUAL(badValue, badFlags.ToBaseType());
+ }
+}
diff --git a/util/generic/function.cpp b/util/generic/function.cpp
new file mode 100644
index 0000000000..d4b693e3fa
--- /dev/null
+++ b/util/generic/function.cpp
@@ -0,0 +1 @@
+#include "function.h"
diff --git a/util/generic/function.h b/util/generic/function.h
new file mode 100644
index 0000000000..62fa84e0cb
--- /dev/null
+++ b/util/generic/function.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "typetraits.h"
+#include "typelist.h"
+
+#include <functional>
+
+namespace NPrivate {
+ template <class F>
+ struct TRemoveClassImpl {
+ using TSignature = F;
+ };
+
+ template <typename C, typename R, typename... Args>
+ struct TRemoveClassImpl<R (C::*)(Args...)> {
+ typedef R TSignature(Args...);
+ };
+
+ template <typename C, typename R, typename... Args>
+ struct TRemoveClassImpl<R (C::*)(Args...) const> {
+ typedef R TSignature(Args...);
+ };
+
+ template <class T>
+ struct TRemoveNoExceptImpl {
+ using Type = T;
+ };
+
+ template <typename R, typename... Args>
+ struct TRemoveNoExceptImpl<R(Args...) noexcept> {
+ using Type = R(Args...);
+ };
+
+ template <typename R, typename C, typename... Args>
+ struct TRemoveNoExceptImpl<R (C::*)(Args...) noexcept> {
+ using Type = R (C::*)(Args...);
+ };
+
+ template <class T>
+ using TRemoveNoExcept = typename TRemoveNoExceptImpl<T>::Type;
+
+ template <class F>
+ using TRemoveClass = typename TRemoveClassImpl<TRemoveNoExcept<F>>::TSignature;
+
+ template <class C>
+ struct TFuncInfo {
+ using TSignature = TRemoveClass<decltype(&C::operator())>;
+ };
+
+ template <class R, typename... Args>
+ struct TFuncInfo<R(Args...)> {
+ using TResult = R;
+ typedef R TSignature(Args...);
+ };
+}
+
+template <class C>
+using TFunctionSignature = typename ::NPrivate::TFuncInfo<::NPrivate::TRemoveClass<std::remove_reference_t<std::remove_pointer_t<C>>>>::TSignature;
+
+template <typename F>
+struct TCallableTraits: public TCallableTraits<TFunctionSignature<F>> {
+};
+
+template <typename R, typename... Args>
+struct TCallableTraits<R(Args...)> {
+ using TResult = R;
+ using TArgs = TTypeList<Args...>;
+ typedef R TSignature(Args...);
+};
+
+template <typename C>
+using TFunctionResult = typename TCallableTraits<C>::TResult;
+
+template <typename C>
+using TFunctionArgs = typename TCallableTraits<C>::TArgs;
+
+template <typename C, size_t N>
+struct TFunctionArgImpl {
+ using TArgs = TFunctionArgs<C>;
+ using TResult = typename TArgs::template TGet<N>;
+};
+
+template <typename C, size_t N>
+using TFunctionArg = typename TFunctionArgImpl<C, N>::TResult;
+
+// temporary before std::apply appearance
+
+template <typename F, typename Tuple, size_t... I>
+auto ApplyImpl(F&& f, Tuple&& t, std::index_sequence<I...>) {
+ return f(std::get<I>(std::forward<Tuple>(t))...);
+}
+
+// change to std::apply after c++ 17
+template <typename F, typename Tuple>
+auto Apply(F&& f, Tuple&& t) {
+ return ApplyImpl(f, t, std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
+}
+
+// change to std::apply after c++ 17
+template <typename F>
+auto Apply(F&& f, std::tuple<>) {
+ return f();
+}
diff --git a/util/generic/function_ut.cpp b/util/generic/function_ut.cpp
new file mode 100644
index 0000000000..3880295a9f
--- /dev/null
+++ b/util/generic/function_ut.cpp
@@ -0,0 +1,69 @@
+#include "function.h"
+#include "typetraits.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TestFunctionSignature) {
+ int FF(double x) {
+ return (int)x;
+ }
+
+ int FFF(double x, char xx) {
+ return (int)x + (int)xx;
+ }
+
+ struct A {
+ int F(double x) {
+ return FF(x);
+ }
+ };
+
+ Y_UNIT_TEST(TestPlainFunc) {
+ UNIT_ASSERT_TYPES_EQUAL(TFunctionSignature<decltype(FF)>, decltype(FF));
+ }
+
+ Y_UNIT_TEST(TestMethod) {
+ UNIT_ASSERT_TYPES_EQUAL(TFunctionSignature<decltype(&A::F)>, decltype(FF));
+ }
+
+ Y_UNIT_TEST(TestLambda) {
+ auto f = [](double x) -> int {
+ return FF(x);
+ };
+
+ UNIT_ASSERT_TYPES_EQUAL(TFunctionSignature<decltype(f)>, decltype(FF));
+ }
+
+ Y_UNIT_TEST(TestFunction) {
+ std::function<int(double)> f(FF);
+
+ UNIT_ASSERT_TYPES_EQUAL(TFunctionSignature<decltype(f)>, decltype(FF));
+ }
+
+ template <class F>
+ void TestCT() {
+#define FA(x) TFunctionArg<F, x>
+
+ UNIT_ASSERT_TYPES_EQUAL(FA(0), double);
+ UNIT_ASSERT_TYPES_EQUAL(FA(1), char);
+ UNIT_ASSERT_TYPES_EQUAL(TFunctionResult<F>, int);
+
+#undef FA
+ }
+
+ Y_UNIT_TEST(TestTypeErasureTraits) {
+ TestCT<std::function<int(double, char)>>();
+ }
+
+ Y_UNIT_TEST(TestPlainFunctionTraits) {
+ TestCT<decltype(FFF)>();
+ }
+
+ Y_UNIT_TEST(TestLambdaTraits) {
+ auto fff = [](double xx, char xxx) -> int {
+ return FFF(xx, xxx);
+ };
+
+ TestCT<decltype(fff)>();
+ }
+}
diff --git a/util/generic/fuzz/vector/main.cpp b/util/generic/fuzz/vector/main.cpp
new file mode 100644
index 0000000000..0a0293f795
--- /dev/null
+++ b/util/generic/fuzz/vector/main.cpp
@@ -0,0 +1,47 @@
+#include <util/generic/vector.h>
+#include <util/stream/mem.h>
+
+template <class T>
+static inline T Read(IInputStream& in) {
+ T t;
+
+ in.LoadOrFail(&t, sizeof(t));
+
+ return t;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const ui8* data, size_t size) {
+ TMemoryInput mi(data, size);
+
+ try {
+ TVector<ui16> v;
+
+ while (mi.Avail()) {
+ char cmd = Read<char>(mi);
+
+ switch (cmd % 2) {
+ case 0: {
+ const size_t cnt = 1 + Read<ui8>(mi) % 16;
+
+ for (size_t i = 0; i < cnt; ++i) {
+ v.push_back(i);
+ }
+
+ break;
+ }
+
+ case 1: {
+ if (v) {
+ v.pop_back();
+ }
+
+ break;
+ }
+ }
+ }
+ } catch (...) {
+ // ¯\_(ツ)_/¯
+ }
+
+ return 0; // Non-zero return values are reserved for future use.
+}
diff --git a/util/generic/fuzz/vector/ya.make b/util/generic/fuzz/vector/ya.make
new file mode 100644
index 0000000000..b8614f6411
--- /dev/null
+++ b/util/generic/fuzz/vector/ya.make
@@ -0,0 +1,13 @@
+FUZZ()
+
+OWNER(
+ pg
+ g:util
+)
+SUBSCRIBER(g:util-subscribers)
+
+SRCS(
+ main.cpp
+)
+
+END()
diff --git a/util/generic/fuzz/ya.make b/util/generic/fuzz/ya.make
new file mode 100644
index 0000000000..dc60b12a0c
--- /dev/null
+++ b/util/generic/fuzz/ya.make
@@ -0,0 +1,3 @@
+RECURSE(
+ vector
+)
diff --git a/util/generic/fwd.cpp b/util/generic/fwd.cpp
new file mode 100644
index 0000000000..4214b6df83
--- /dev/null
+++ b/util/generic/fwd.cpp
@@ -0,0 +1 @@
+#include "fwd.h"
diff --git a/util/generic/fwd.h b/util/generic/fwd.h
new file mode 100644
index 0000000000..5cc2da40e5
--- /dev/null
+++ b/util/generic/fwd.h
@@ -0,0 +1,168 @@
+#pragma once
+
+#include <util/system/defaults.h>
+
+#include <stlfwd>
+
+template <typename TCharType, typename TTraits = std::char_traits<TCharType>>
+class TBasicString;
+
+using TString = TBasicString<char>;
+using TUtf16String = TBasicString<wchar16>;
+using TUtf32String = TBasicString<wchar32>;
+
+template <typename TCharType, typename TTraits = std::char_traits<TCharType>>
+class TBasicStringBuf;
+
+using TStringBuf = TBasicStringBuf<char>;
+using TWtringBuf = TBasicStringBuf<wchar16>;
+using TUtf32StringBuf = TBasicStringBuf<wchar32>;
+
+//misc
+class TBuffer;
+
+//functors
+template <class T = void>
+struct TLess;
+
+template <class T = void>
+struct TGreater;
+
+template <class T = void>
+struct TEqualTo;
+
+template <class T>
+struct THash;
+
+//intrusive containers
+struct TIntrusiveListDefaultTag;
+template <class T, class Tag = TIntrusiveListDefaultTag>
+class TIntrusiveList;
+
+template <class T, class D, class Tag = TIntrusiveListDefaultTag>
+class TIntrusiveListWithAutoDelete;
+
+template <class T, class Tag = TIntrusiveListDefaultTag>
+class TIntrusiveSList;
+
+template <class T, class C>
+class TAvlTree;
+
+template <class TValue, class TCmp>
+class TRbTree;
+
+//containers
+template <class T, class A = std::allocator<T>>
+class TVector;
+
+template <class T, class A = std::allocator<T>>
+class TDeque;
+
+template <class T, class S = TDeque<T>>
+class TQueue;
+
+template <class T, class S = TVector<T>, class C = TLess<T>>
+class TPriorityQueue;
+
+template <class Key, class T, class HashFcn = THash<Key>, class EqualKey = TEqualTo<Key>, class Alloc = std::allocator<Key>>
+class THashMap;
+
+template <class Key, class T, class HashFcn = THash<Key>, class EqualKey = TEqualTo<Key>, class Alloc = std::allocator<Key>>
+class THashMultiMap;
+
+template <class Value, class HashFcn = THash<Value>, class EqualKey = TEqualTo<Value>, class Alloc = std::allocator<Value>>
+class THashSet;
+
+template <class Value, class HashFcn = THash<Value>, class EqualKey = TEqualTo<Value>, class Alloc = std::allocator<Value>>
+class THashMultiSet;
+
+template <class T, class A = std::allocator<T>>
+class TList;
+
+template <class K, class V, class Less = TLess<K>, class A = std::allocator<K>>
+class TMap;
+
+template <class K, class V, class Less = TLess<K>, class A = std::allocator<K>>
+class TMultiMap;
+
+template <class K, class L = TLess<K>, class A = std::allocator<K>>
+class TSet;
+
+template <class K, class L = TLess<K>, class A = std::allocator<K>>
+class TMultiSet;
+
+template <class T, class S = TDeque<T>>
+class TStack;
+
+template <size_t BitCount, typename TChunkType = ui64>
+class TBitMap;
+
+//autopointers
+class TDelete;
+class TDeleteArray;
+class TFree;
+class TCopyNew;
+
+template <class T, class D = TDelete>
+class TAutoPtr;
+
+template <class T, class D = TDelete>
+class THolder;
+
+template <class T, class C, class D = TDelete>
+class TRefCounted;
+
+template <class T>
+class TDefaultIntrusivePtrOps;
+
+template <class T, class Ops>
+class TSimpleIntrusiveOps;
+
+template <class T, class Ops = TDefaultIntrusivePtrOps<T>>
+class TIntrusivePtr;
+
+template <class T, class Ops = TDefaultIntrusivePtrOps<T>>
+class TIntrusiveConstPtr;
+
+template <class T, class Ops = TDefaultIntrusivePtrOps<T>>
+using TSimpleIntrusivePtr = TIntrusivePtr<T, TSimpleIntrusiveOps<T, Ops>>;
+
+template <class T, class C, class D = TDelete>
+class TSharedPtr;
+
+template <class T, class C = TCopyNew, class D = TDelete>
+class TCopyPtr;
+
+template <class TPtr, class TCopy = TCopyNew>
+class TCowPtr;
+
+template <typename T>
+class TPtrArg;
+
+template <typename T>
+using TArrayHolder = THolder<T, TDeleteArray>;
+
+template <typename T>
+using TMallocHolder = THolder<T, TFree>;
+
+template <typename T>
+using TArrayPtr = TAutoPtr<T, TDeleteArray>;
+
+template <typename T>
+using TMallocPtr = TAutoPtr<T, TFree>;
+
+//maybe
+namespace NMaybe {
+ struct TPolicyUndefinedExcept;
+}
+
+template <class T, class Policy = ::NMaybe::TPolicyUndefinedExcept>
+class TMaybe;
+
+struct TGUID;
+
+template <class T>
+class TArrayRef;
+
+template <class T>
+using TConstArrayRef = TArrayRef<const T>;
diff --git a/util/generic/guid.cpp b/util/generic/guid.cpp
new file mode 100644
index 0000000000..8b907457bc
--- /dev/null
+++ b/util/generic/guid.cpp
@@ -0,0 +1,186 @@
+#include "guid.h"
+#include "ylimits.h"
+#include "string.h"
+
+#include <util/string/ascii.h>
+#include <util/string/builder.h>
+#include <util/stream/format.h>
+#include <util/system/unaligned_mem.h>
+#include <util/random/easy.h>
+
+namespace {
+ inline void LowerCaseHex(TString& s) {
+ for (auto&& c : s) {
+ c = AsciiToLower(c);
+ }
+ }
+}
+
+TString TGUID::AsGuidString() const {
+ TStringBuilder s;
+ s.reserve(50);
+ s << Hex(dw[0], 0) << '-' << Hex(dw[1], 0) << '-' << Hex(dw[2], 0) << '-' << Hex(dw[3], 0);
+ LowerCaseHex(s);
+ return std::move(s);
+}
+
+TString TGUID::AsUuidString() const {
+ TStringBuilder s;
+ s.reserve(50);
+ s << Hex(dw[0], HF_FULL) << '-';
+ s << Hex(static_cast<ui16>(dw[1] >> 16), HF_FULL) << '-' << Hex(static_cast<ui16>(dw[1]), HF_FULL) << '-';
+ s << Hex(static_cast<ui16>(dw[2] >> 16), HF_FULL) << '-' << Hex(static_cast<ui16>(dw[2]), HF_FULL);
+ s << Hex(dw[3], HF_FULL);
+ LowerCaseHex(s);
+ return std::move(s);
+}
+
+TGUID TGUID::Create() {
+ TGUID result;
+ CreateGuid(&result);
+ return result;
+}
+
+void CreateGuid(TGUID* res) {
+ ui64* dw = reinterpret_cast<ui64*>(res->dw);
+
+ WriteUnaligned<ui64>(&dw[0], RandomNumber<ui64>());
+ WriteUnaligned<ui64>(&dw[1], RandomNumber<ui64>());
+}
+
+TGUID TGUID::CreateTimebased() {
+ TGUID result;
+ // GUID_EPOCH_OFFSET is the number of 100-ns intervals between the
+ // UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
+ constexpr ui64 GUID_EPOCH_OFFSET = 0x01b21dd213814000;
+ const ui64 timestamp = Now().NanoSeconds() / 100 + GUID_EPOCH_OFFSET;
+ result.dw[0] = ui32(timestamp & 0xffffffff); // time low
+ const ui32 timeMid = ui32((timestamp >> 32) & 0xffff);
+ constexpr ui32 UUID_VERSION = 1;
+ const ui32 timeHighAndVersion = ui16((timestamp >> 48) & 0x0fff) | (UUID_VERSION << 12);
+ result.dw[1] = (timeMid << 16) | timeHighAndVersion;
+ const ui32 clockSeq = RandomNumber<ui32>(0x3fff) | 0x8000;
+ result.dw[2] = (clockSeq << 16) | RandomNumber<ui16>();
+ result.dw[3] = RandomNumber<ui32>() | (1 << 24);
+ return result;
+}
+
+TString GetGuidAsString(const TGUID& g) {
+ return g.AsGuidString();
+}
+
+TString CreateGuidAsString() {
+ return TGUID::Create().AsGuidString();
+}
+
+static bool GetDigit(const char c, ui32& digit) {
+ digit = 0;
+ if ('0' <= c && c <= '9') {
+ digit = c - '0';
+ } else if ('a' <= c && c <= 'f') {
+ digit = c - 'a' + 10;
+ } else if ('A' <= c && c <= 'F') {
+ digit = c - 'A' + 10;
+ } else {
+ return false; // non-hex character
+ }
+ return true;
+}
+
+bool GetGuid(const TStringBuf s, TGUID& result) {
+ size_t partId = 0;
+ ui64 partValue = 0;
+ bool isEmptyPart = true;
+
+ for (size_t i = 0; i != s.size(); ++i) {
+ const char c = s[i];
+
+ if (c == '-') {
+ if (isEmptyPart || partId == 3) { // x-y--z, -x-y-z or x-y-z-m-...
+ return false;
+ }
+ result.dw[partId] = static_cast<ui32>(partValue);
+ ++partId;
+ partValue = 0;
+ isEmptyPart = true;
+ continue;
+ }
+
+ ui32 digit = 0;
+ if (!GetDigit(c, digit)) {
+ return false;
+ }
+
+ partValue = partValue * 16 + digit;
+ isEmptyPart = false;
+
+ // overflow check
+ if (partValue > Max<ui32>()) {
+ return false;
+ }
+ }
+
+ if (partId != 3 || isEmptyPart) { // x-y or x-y-z-
+ return false;
+ }
+ result.dw[partId] = static_cast<ui32>(partValue);
+ return true;
+}
+
+// Parses GUID from s and checks that it's valid.
+// In case of error returns TGUID().
+TGUID GetGuid(const TStringBuf s) {
+ TGUID result;
+
+ if (GetGuid(s, result)) {
+ return result;
+ }
+
+ return TGUID();
+}
+
+bool GetUuid(const TStringBuf s, TGUID& result) {
+ if (s.size() != 36) {
+ return false;
+ }
+
+ size_t partId = 0;
+ ui64 partValue = 0;
+ size_t digitCount = 0;
+
+ for (size_t i = 0; i < s.size(); ++i) {
+ const char c = s[i];
+
+ if (c == '-') {
+ if (i != 8 && i != 13 && i != 18 && i != 23) {
+ return false;
+ }
+ continue;
+ }
+
+ ui32 digit = 0;
+ if (!GetDigit(c, digit)) {
+ return false;
+ }
+
+ partValue = partValue * 16 + digit;
+
+ if (++digitCount == 8) {
+ result.dw[partId++] = partValue;
+ digitCount = 0;
+ }
+ }
+ return true;
+}
+
+// Parses GUID from uuid and checks that it's valid.
+// In case of error returns TGUID().
+TGUID GetUuid(const TStringBuf s) {
+ TGUID result;
+
+ if (GetUuid(s, result)) {
+ return result;
+ }
+
+ return TGUID();
+}
diff --git a/util/generic/guid.h b/util/generic/guid.h
new file mode 100644
index 0000000000..2bf6c8ad99
--- /dev/null
+++ b/util/generic/guid.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "fwd.h"
+
+#include <util/str_stl.h>
+
+/**
+ * UUID generation
+ *
+ * NOTE: It is not a real GUID (RFC 4122), as described in
+ * https://en.wikipedia.org/wiki/Universally_unique_identifier
+ * https://en.wikipedia.org/wiki/Globally_unique_identifier
+ *
+ * See https://clubs.at.yandex-team.ru/stackoverflow/10238/10240
+ * and https://st.yandex-team.ru/IGNIETFERRO-768 for details.
+ */
+struct TGUID {
+ ui32 dw[4] = {};
+
+ constexpr bool IsEmpty() const noexcept {
+ return (dw[0] | dw[1] | dw[2] | dw[3]) == 0;
+ }
+
+ constexpr explicit operator bool() const noexcept {
+ return !IsEmpty();
+ }
+
+ // xxxx-xxxx-xxxx-xxxx
+ TString AsGuidString() const;
+
+ /**
+ * RFC4122 GUID, which described in
+ * https://en.wikipedia.org/wiki/Universally_unique_identifier
+ * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ **/
+ TString AsUuidString() const;
+
+ static TGUID Create();
+
+ /**
+ * Generate time based UUID version 1 RFC4122 GUID
+ * https://datatracker.ietf.org/doc/html/rfc4122#section-4.1
+ **/
+ static TGUID CreateTimebased();
+};
+
+constexpr bool operator==(const TGUID& a, const TGUID& b) noexcept {
+ return a.dw[0] == b.dw[0] && a.dw[1] == b.dw[1] && a.dw[2] == b.dw[2] && a.dw[3] == b.dw[3];
+}
+
+constexpr bool operator!=(const TGUID& a, const TGUID& b) noexcept {
+ return !(a == b);
+}
+
+struct TGUIDHash {
+ constexpr int operator()(const TGUID& a) const noexcept {
+ return a.dw[0] + a.dw[1] + a.dw[2] + a.dw[3];
+ }
+};
+
+template <>
+struct THash<TGUID> {
+ constexpr size_t operator()(const TGUID& g) const noexcept {
+ return (unsigned int)TGUIDHash()(g);
+ }
+};
+
+void CreateGuid(TGUID* res);
+TString GetGuidAsString(const TGUID& g);
+TString CreateGuidAsString();
+TGUID GetGuid(TStringBuf s);
+bool GetGuid(TStringBuf s, TGUID& result);
+
+/**
+* Functions for correct parsing RFC4122 GUID, which described in
+* https://en.wikipedia.org/wiki/Universally_unique_identifier
+**/
+TGUID GetUuid(TStringBuf s);
+bool GetUuid(TStringBuf s, TGUID& result);
diff --git a/util/generic/guid_ut.cpp b/util/generic/guid_ut.cpp
new file mode 100644
index 0000000000..048354ff39
--- /dev/null
+++ b/util/generic/guid_ut.cpp
@@ -0,0 +1,128 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+#include "guid.h"
+
+Y_UNIT_TEST_SUITE(TGuidTest) {
+ //TODO - make real constructor
+ static TGUID Construct(ui32 d1, ui32 d2, ui32 d3, ui32 d4) {
+ TGUID ret;
+
+ ret.dw[0] = d1;
+ ret.dw[1] = d2;
+ ret.dw[2] = d3;
+ ret.dw[3] = d4;
+
+ return ret;
+ }
+
+ struct TTest {
+ TGUID G;
+ TString S;
+ };
+
+ Y_UNIT_TEST(Test1) {
+ for (size_t i = 0; i < 1000; ++i) {
+ TGUID g;
+
+ CreateGuid(&g);
+
+ UNIT_ASSERT_EQUAL(g, GetGuid(GetGuidAsString(g)));
+ }
+ }
+
+ Y_UNIT_TEST(Test2) {
+ const TTest tests[] = {
+ {Construct(1, 1, 1, 1), "1-1-1-1"},
+ {Construct(0, 0, 0, 0), "0-0-0-0"},
+ {TGUID(), "H-0-0-0"},
+ {TGUID(), "0-H-0-0"},
+ {TGUID(), "0-0-H-0"},
+ {TGUID(), "0-0-0-H"},
+ {Construct(0x8cf813d9U, 0xc098da90U, 0x7ef58954U, 0x636d04dU), "8cf813d9-c098da90-7ef58954-636d04d"},
+ {Construct(0x8cf813d9U, 0xc098da90U, 0x7ef58954U, 0x636d04dU), "8CF813D9-C098DA90-7EF58954-636D04D"},
+ {Construct(0x12345678U, 0x90abcdefU, 0xfedcba09U, 0x87654321U), "12345678-90abcdef-FEDCBA09-87654321"},
+ {Construct(0x1, 0x2, 0xabcdef, 0x400), "01-002-00ABCDEF-000400"},
+ {TGUID(), "-1-1-1"}, // empty parts
+ {TGUID(), "--1-1-1"},
+ {TGUID(), "1--1-1"},
+ {TGUID(), "1-1"}, // unexpected end
+ {TGUID(), "1-1-"},
+ {TGUID(), "1-1-1"},
+ {TGUID(), "1-1-1-"},
+ {TGUID(), "1-1-1-1-"},
+ {TGUID(), "1-1-1-1-1"},
+ {TGUID(), "1+1-1-1"}, // bad char
+ {TGUID(), "1-1:3-1-1"},
+ {Construct(0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU), "FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFF"}, // overflow
+ {TGUID(), "FFFFFFFFA-FFFFFFFF-FFFFFFFF-FFFFFFFF"},
+ {TGUID(), "100000000-0-0-0"},
+ {Construct(1, 1, 1, 1), "0000001-0000000000000000000000000000000000000001-0001-00000001"},
+ {Construct(0, 0, 0, 0), "000000000000-000000000000000000000000000000000000000-000-0"},
+ };
+
+ for (const auto& t : tests) {
+ UNIT_ASSERT_EQUAL(t.G, GetGuid(t.S));
+ }
+ }
+
+ Y_UNIT_TEST(Test3) {
+ //if this test failed, please, fix buffer size in GetGuidAsString()
+ TGUID max = Construct(Max<ui32>(), Max<ui32>(), Max<ui32>(), Max<ui32>());
+
+ UNIT_ASSERT_EQUAL(GetGuidAsString(max).length(), 35);
+ }
+
+ Y_UNIT_TEST(Test4) {
+ UNIT_ASSERT_VALUES_EQUAL(GetGuidAsString(Construct(1, 2, 3, 4)), "1-2-3-4");
+ UNIT_ASSERT_VALUES_EQUAL(GetGuidAsString(Construct(1, 2, 0xFFFFFF, 4)), "1-2-ffffff-4");
+ UNIT_ASSERT_VALUES_EQUAL(GetGuidAsString(Construct(0xFAFA, 2, 3, 4)), "fafa-2-3-4");
+ UNIT_ASSERT_VALUES_EQUAL(GetGuidAsString(Construct(1, 0xADE, 3, 4)), "1-ade-3-4");
+ UNIT_ASSERT_VALUES_EQUAL(GetGuidAsString(Construct(1, 2, 3, 0xDEAD)), "1-2-3-dead");
+ }
+
+ Y_UNIT_TEST(Test5) {
+ const TTest tests[] = {
+ {TGUID(), "1-1-1-1-1"},
+ {TGUID(), "00000001-0001-0001-0001-00000000001-"},
+ {Construct(0x10000001U, 0x10011001U, 0x10011001U, 0x10000001U), "10000001-1001-1001-1001-100110000001"},
+ {Construct(0x550e8400U, 0xe29b41d4U, 0xa7164466U, 0x55440000U), "550e8400-e29b-41d4-a716-446655440000"},
+ {Construct(0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU), "ffffffff-ffff-ffff-ffff-ffffffffffff"},
+ {TGUID(), "ffffffff-ffffff-ff-ffff-ffffffffffff"},
+ {TGUID(), "ffffffff-ffff-ffff-ff-ffffffffffffff"}};
+
+ for (const auto& t : tests) {
+ UNIT_ASSERT_EQUAL(t.G, GetUuid(t.S));
+ }
+ }
+
+ Y_UNIT_TEST(DoubleConvert) {
+ /**
+ * test print and parsing RFC4122 GUID, which described in
+ * https://en.wikipedia.org/wiki/Universally_unique_identifier
+ * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ **/
+ auto guid = TGUID::Create();
+ auto printed = guid.AsUuidString();
+
+ TGUID read;
+ UNIT_ASSERT(GetUuid(printed, read));
+
+ UNIT_ASSERT_VALUES_EQUAL(guid.dw[0], read.dw[0]);
+ UNIT_ASSERT_VALUES_EQUAL(guid.dw[1], read.dw[1]);
+ UNIT_ASSERT_VALUES_EQUAL(guid.dw[2], read.dw[2]);
+ UNIT_ASSERT_VALUES_EQUAL(guid.dw[3], read.dw[3]);
+ }
+
+ Y_UNIT_TEST(OutputFormat) {
+ TGUID guid = Construct(0x00005612U, 0x12000000U, 0x00000123U, 0x00000000U);
+
+ UNIT_ASSERT_VALUES_EQUAL(guid.AsGuidString(), "5612-12000000-123-0");
+ UNIT_ASSERT_VALUES_EQUAL(guid.AsUuidString(), "00005612-1200-0000-0000-012300000000");
+ }
+
+ Y_UNIT_TEST(TimeBased) {
+ TString guid = TGUID::CreateTimebased().AsUuidString();
+ UNIT_ASSERT(!guid.empty());
+ UNIT_ASSERT_EQUAL(guid[14], '1');
+ }
+}
diff --git a/util/generic/hash.cpp b/util/generic/hash.cpp
new file mode 100644
index 0000000000..a674ee4538
--- /dev/null
+++ b/util/generic/hash.cpp
@@ -0,0 +1,51 @@
+#include "hash.h"
+
+#include <util/string/escape.h>
+#include <util/string/cast.h>
+
+const void* const _yhashtable_empty_data[] = {(void*)3, nullptr, (void*)1};
+
+TString NPrivate::MapKeyToString(TStringBuf key) {
+ constexpr size_t HASH_KEY_MAX_LENGTH = 500;
+ try {
+ return EscapeC(key.substr(0, HASH_KEY_MAX_LENGTH));
+ } catch (...) {
+ return "TStringBuf";
+ }
+}
+
+TString NPrivate::MapKeyToString(unsigned short key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(short key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(unsigned int key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(int key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(unsigned long key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(long key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(unsigned long long key) {
+ return ToString(key);
+}
+
+TString NPrivate::MapKeyToString(long long key) {
+ return ToString(key);
+}
+
+void NPrivate::ThrowKeyNotFoundInHashTableException(const TStringBuf keyRepresentation) {
+ ythrow yexception() << "Key not found in hashtable: " << keyRepresentation;
+}
diff --git a/util/generic/hash.h b/util/generic/hash.h
new file mode 100644
index 0000000000..e46db21fa9
--- /dev/null
+++ b/util/generic/hash.h
@@ -0,0 +1,2029 @@
+#pragma once
+
+#include "fwd.h"
+#include "mapfindptr.h"
+
+#include <util/memory/alloc.h>
+#include <util/system/type_name.h>
+#include <util/system/yassert.h>
+#include <util/str_stl.h>
+#include "yexception.h"
+#include "typetraits.h"
+#include "utility.h"
+
+#include <algorithm>
+#include <initializer_list>
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include <cstdlib>
+
+#include "hash_primes.h"
+
+struct TSelect1st {
+ template <class TPair>
+ inline const typename TPair::first_type& operator()(const TPair& x) const {
+ return x.first;
+ }
+};
+
+template <class Value>
+struct __yhashtable_node {
+ /** If the first bit is not set, then this is a pointer to the next node in
+ * the list of nodes for the current bucket. Otherwise this is a pointer of
+ * type __yhashtable_node**, pointing back into the buckets array.
+ *
+ * This trick makes it possible to use only one node pointer in a hash table
+ * iterator. */
+ __yhashtable_node* next;
+
+ /** Value stored in a node. */
+ Value val;
+
+ __yhashtable_node& operator=(const __yhashtable_node&) = delete;
+};
+
+template <class Value, class Key, class HashFcn,
+ class ExtractKey, class EqualKey, class Alloc>
+class THashTable;
+
+template <class Key, class T, class HashFcn,
+ class EqualKey, typename size_type_f>
+class sthash;
+
+template <class Value>
+struct __yhashtable_iterator;
+
+template <class Value>
+struct __yhashtable_const_iterator;
+
+template <class Value>
+struct __yhashtable_iterator {
+ using iterator = __yhashtable_iterator<Value>;
+ using const_iterator = __yhashtable_const_iterator<Value>;
+ using node = __yhashtable_node<Value>;
+
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = Value;
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+ using reference = Value&;
+ using pointer = Value*;
+
+ node* cur;
+
+ explicit __yhashtable_iterator(node* n)
+ : cur(n)
+ {
+ } /*y*/
+ __yhashtable_iterator() = default;
+
+ reference operator*() const {
+ return cur->val;
+ }
+ pointer operator->() const {
+ return &(operator*());
+ }
+ iterator& operator++();
+ iterator operator++(int);
+ bool operator==(const iterator& it) const {
+ return cur == it.cur;
+ }
+ bool operator!=(const iterator& it) const {
+ return cur != it.cur;
+ }
+ bool IsEnd() const {
+ return !cur;
+ }
+ Y_FORCE_INLINE explicit operator bool() const noexcept {
+ return cur != nullptr;
+ }
+};
+
+template <class Value>
+struct __yhashtable_const_iterator {
+ using iterator = __yhashtable_iterator<Value>;
+ using const_iterator = __yhashtable_const_iterator<Value>;
+ using node = __yhashtable_node<Value>;
+
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = Value;
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+ using reference = const Value&;
+ using pointer = const Value*;
+
+ const node* cur;
+
+ explicit __yhashtable_const_iterator(const node* n)
+ : cur(n)
+ {
+ }
+ __yhashtable_const_iterator() {
+ }
+ __yhashtable_const_iterator(const iterator& it)
+ : cur(it.cur)
+ {
+ }
+ reference operator*() const {
+ return cur->val;
+ }
+ pointer operator->() const {
+ return &(operator*());
+ }
+ const_iterator& operator++();
+ const_iterator operator++(int);
+ bool operator==(const const_iterator& it) const {
+ return cur == it.cur;
+ }
+ bool operator!=(const const_iterator& it) const {
+ return cur != it.cur;
+ }
+ bool IsEnd() const {
+ return !cur;
+ }
+ Y_FORCE_INLINE explicit operator bool() const noexcept {
+ return cur != nullptr;
+ }
+};
+
+/**
+ * This class saves some space in allocator-based containers for the most common
+ * use case of empty allocators. This is achieved thanks to the application of
+ * empty base class optimization (aka EBCO).
+ */
+template <class Alloc>
+class _allocator_base: private Alloc {
+public:
+ _allocator_base(const Alloc& other)
+ : Alloc(other)
+ {
+ }
+
+ Alloc& _get_alloc() {
+ return static_cast<Alloc&>(*this);
+ }
+ const Alloc& _get_alloc() const {
+ return static_cast<const Alloc&>(*this);
+ }
+ void _set_alloc(const Alloc& allocator) {
+ _get_alloc() = allocator;
+ }
+
+ void swap(_allocator_base& other) {
+ DoSwap(_get_alloc(), other._get_alloc());
+ }
+};
+
+/**
+ * Wrapper for an array of THashTable buckets.
+ *
+ * Is better than vector for this particular use case. Main differences:
+ * - Occupies one less word on stack.
+ * - Doesn't even try to initialize its elements. It is THashTable's responsibility.
+ * - Presents a better interface in relation to THashTable's marker element trick.
+ *
+ * Internally this class is just a pointer-size pair, and the data on the heap
+ * has the following structure:
+ *
+ * +----------+----------------------+----------+-------------------------+
+ * | raw_size | elements ... | marker | unused space [optional] |
+ * +----------+----------------------+----------+-------------------------+
+ * ^ ^
+ * | |
+ * Data points here end() points here
+ *
+ * `raw_size` stores the size of the allocated memory block. It is used to
+ * support resizing without reallocation.
+ *
+ * `marker` is a special marker element that is set by the THashTable that is
+ * then used in iterator implementation to know when the end is reached.
+ *
+ * Unused space at the end of the memory block may not be present.
+ */
+template <class T, class Alloc>
+class _yhashtable_buckets: private _allocator_base<Alloc> {
+ using base_type = _allocator_base<Alloc>;
+
+ static_assert(sizeof(T) == sizeof(size_t), "T is expected to be the same size as size_t.");
+
+public:
+ using allocator_type = Alloc;
+ using value_type = T;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using TBucketDivisor = ::NPrivate::THashDivisor;
+
+ _yhashtable_buckets(const Alloc& other)
+ : base_type(other)
+ , Data(nullptr)
+ , Size()
+ {
+ }
+
+ ~_yhashtable_buckets() {
+ Y_ASSERT(!Data);
+ }
+
+ void initialize_dynamic(TBucketDivisor size) {
+ Y_ASSERT(!Data);
+
+ Data = this->_get_alloc().allocate(size() + 2) + 1;
+ Size = size;
+
+ *reinterpret_cast<size_type*>(Data - 1) = size() + 2;
+ }
+
+ void deinitialize_dynamic() {
+ Y_ASSERT(Data);
+
+ this->_get_alloc().deallocate(Data - 1, *reinterpret_cast<size_type*>(Data - 1));
+ Data = pointer();
+ Size = TBucketDivisor();
+ }
+
+ void initialize_static(pointer data, TBucketDivisor size) {
+ Y_ASSERT(!Data && data && size() >= 1);
+
+ Data = data;
+ Size = size;
+ }
+
+ void deinitialize_static() {
+ Y_ASSERT(Data);
+
+ Data = pointer();
+ Size = TBucketDivisor();
+ }
+
+ void resize_noallocate(TBucketDivisor size) {
+ Y_ASSERT(size() <= capacity());
+
+ Size = size;
+ }
+
+ iterator begin() {
+ return Data;
+ }
+ const_iterator begin() const {
+ return Data;
+ }
+ iterator end() {
+ return Data + Size();
+ }
+ const_iterator end() const {
+ return Data + Size();
+ }
+
+ pointer data() {
+ return Data;
+ }
+ const_pointer data() const {
+ return Data;
+ }
+
+ size_type size() const {
+ return Size();
+ }
+ size_type capacity() const {
+ return *reinterpret_cast<size_type*>(Data - 1);
+ }
+ TBucketDivisor ExtSize() const {
+ return Size;
+ }
+ int BucketDivisorHint() const {
+ return +Size.Hint;
+ }
+
+ allocator_type get_allocator() const {
+ return this->_get_alloc();
+ }
+
+ const_reference operator[](size_type index) const {
+ Y_ASSERT(index <= Size());
+
+ return *(Data + index);
+ }
+
+ reference operator[](size_type index) {
+ Y_ASSERT(index <= Size());
+
+ return *(Data + index);
+ }
+
+ void swap(_yhashtable_buckets& other) {
+ base_type::swap(other);
+ DoSwap(Data, other.Data);
+ DoSwap(Size, other.Size);
+ }
+
+private:
+ /** Pointer to the first element of the buckets array. */
+ pointer Data;
+
+ /** Size of the buckets array. Doesn't take the marker element at the end into account. */
+ TBucketDivisor Size;
+};
+
+/**
+ * This class saves one word in THashTable for the most common use case of empty
+ * functors. The exact implementation picks a specialization with storage allocated
+ * for the functors if those are non-empty, and another specialization that creates
+ * functors on the fly if they are empty. It is expected that empty functors have
+ * trivial constructors.
+ *
+ * Note that this is basically the only way to do it portably. Another option is
+ * multiple inheritance from empty functors, but MSVC's empty base class
+ * optimization chokes up on multiple empty bases, and we're already using
+ * EBCO in _allocator_base.
+ *
+ * Note that there are no specializations for the case when only one or two
+ * of the functors are empty as this is a case that's just way too rare.
+ */
+template <class HashFcn, class ExtractKey, class EqualKey, class Alloc, bool IsEmpty = std::is_empty<HashFcn>::value&& std::is_empty<ExtractKey>::value&& std::is_empty<EqualKey>::value>
+class _yhashtable_base: public _allocator_base<Alloc> {
+ using base_type = _allocator_base<Alloc>;
+
+public:
+ _yhashtable_base(const HashFcn& hash, const ExtractKey& extract, const EqualKey& equals, const Alloc& alloc)
+ : base_type(alloc)
+ , hash_(hash)
+ , extract_(extract)
+ , equals_(equals)
+ {
+ }
+
+ const EqualKey& _get_key_eq() const {
+ return equals_;
+ }
+ EqualKey& _get_key_eq() {
+ return equals_;
+ }
+ void _set_key_eq(const EqualKey& equals) {
+ this->equals_ = equals;
+ }
+
+ const ExtractKey& _get_key_extract() const {
+ return extract_;
+ }
+ ExtractKey& _get_key_extract() {
+ return extract_;
+ }
+ void _set_key_extract(const ExtractKey& extract) {
+ this->extract_ = extract;
+ }
+
+ const HashFcn& _get_hash_fun() const {
+ return hash_;
+ }
+ HashFcn& _get_hash_fun() {
+ return hash_;
+ }
+ void _set_hash_fun(const HashFcn& hash) {
+ this->hash_ = hash;
+ }
+
+ void swap(_yhashtable_base& other) {
+ base_type::swap(other);
+ DoSwap(equals_, other.equals_);
+ DoSwap(extract_, other.extract_);
+ DoSwap(hash_, other.hash_);
+ }
+
+private:
+ HashFcn hash_;
+ ExtractKey extract_;
+ EqualKey equals_;
+};
+
+template <class HashFcn, class ExtractKey, class EqualKey, class Alloc>
+class _yhashtable_base<HashFcn, ExtractKey, EqualKey, Alloc, true>: public _allocator_base<Alloc> {
+ using base_type = _allocator_base<Alloc>;
+
+public:
+ _yhashtable_base(const HashFcn&, const ExtractKey&, const EqualKey&, const Alloc& alloc)
+ : base_type(alloc)
+ {
+ }
+
+ EqualKey _get_key_eq() const {
+ return EqualKey();
+ }
+ void _set_key_eq(const EqualKey&) {
+ }
+
+ ExtractKey _get_key_extract() const {
+ return ExtractKey();
+ }
+ void _set_key_extract(const ExtractKey&) {
+ }
+
+ HashFcn _get_hash_fun() const {
+ return HashFcn();
+ }
+ void _set_hash_fun(const HashFcn&) {
+ }
+
+ void swap(_yhashtable_base& other) {
+ base_type::swap(other);
+ }
+};
+
+template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
+struct _yhashtable_traits {
+ using node = __yhashtable_node<Value>;
+
+ using node_allocator_type = TReboundAllocator<Alloc, node>;
+ using nodep_allocator_type = TReboundAllocator<Alloc, node*>;
+
+ using base_type = _yhashtable_base<HashFcn, ExtractKey, EqualKey, node_allocator_type>;
+};
+
+extern const void* const _yhashtable_empty_data[];
+
+template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
+class THashTable: private _yhashtable_traits<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>::base_type {
+ using traits_type = _yhashtable_traits<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc>;
+ using base_type = typename traits_type::base_type;
+ using node = typename traits_type::node;
+ using nodep_allocator_type = typename traits_type::nodep_allocator_type;
+ using buckets_type = _yhashtable_buckets<node*, nodep_allocator_type>;
+ using TBucketDivisor = ::NPrivate::THashDivisor;
+
+public:
+ using key_type = Key;
+ using value_type = Value;
+ using hasher = HashFcn;
+ using key_equal = EqualKey;
+ using key_extract = ExtractKey;
+ using allocator_type = Alloc;
+ using node_allocator_type = typename traits_type::node_allocator_type;
+
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+
+ node_allocator_type& GetNodeAllocator() {
+ return this->_get_alloc();
+ }
+ const node_allocator_type& GetNodeAllocator() const {
+ return this->_get_alloc();
+ }
+ key_equal key_eq() const {
+ return this->_get_key_eq();
+ }
+ hasher hash_function() const {
+ return this->_get_hash_fun();
+ }
+
+private:
+ template <class KeyL, class KeyR>
+ bool equals(const KeyL& l, const KeyR& r) const {
+ return this->_get_key_eq()(l, r);
+ }
+
+ /* This method is templated to postpone instantiation of key extraction functor. */
+ template <class ValueL>
+ auto get_key(const ValueL& value) const -> decltype(ExtractKey()(value)) {
+ return this->_get_key_extract()(value);
+ }
+
+ node* get_node() {
+ node* result = this->_get_alloc().allocate(1);
+ Y_ASSERT((reinterpret_cast<uintptr_t>(result) & 1) == 0); /* We're using the last bit of the node pointer. */
+ return result;
+ }
+ void put_node(node* p) {
+ this->_get_alloc().deallocate(p, 1);
+ }
+
+ buckets_type buckets;
+ size_type num_elements;
+
+public:
+ using iterator = __yhashtable_iterator<Value>;
+ using const_iterator = __yhashtable_const_iterator<Value>;
+ using insert_ctx = node**;
+
+ friend struct __yhashtable_iterator<Value>;
+ friend struct __yhashtable_const_iterator<Value>;
+
+public:
+ THashTable()
+ : base_type(HashFcn(), ExtractKey(), EqualKey(), node_allocator_type())
+ , buckets(nodep_allocator_type())
+ , num_elements(0)
+ {
+ initialize_buckets(buckets, 0);
+ }
+
+ THashTable(size_type n, const HashFcn& hf, const EqualKey& eql, const ExtractKey& ext)
+ : base_type(hf, ext, eql, node_allocator_type())
+ , buckets(nodep_allocator_type())
+ , num_elements(0)
+ {
+ initialize_buckets(buckets, n);
+ }
+
+ THashTable(size_type n, const HashFcn& hf, const EqualKey& eql)
+ : base_type(hf, ExtractKey(), eql, node_allocator_type())
+ , buckets(nodep_allocator_type())
+ , num_elements(0)
+ {
+ initialize_buckets(buckets, n);
+ }
+
+ template <class TAllocParam>
+ THashTable(size_type n, const HashFcn& hf, const EqualKey& eql, TAllocParam* allocParam)
+ : base_type(hf, ExtractKey(), eql, allocParam)
+ , buckets(allocParam)
+ , num_elements(0)
+ {
+ initialize_buckets(buckets, n);
+ }
+
+ THashTable(const THashTable& ht)
+ : base_type(ht._get_hash_fun(), ht._get_key_extract(), ht._get_key_eq(), ht._get_alloc())
+ , buckets(ht.buckets.get_allocator())
+ , num_elements(0)
+ {
+ if (ht.empty()) {
+ initialize_buckets(buckets, 0);
+ } else {
+ initialize_buckets_dynamic(buckets, ht.buckets.ExtSize());
+ copy_from_dynamic(ht);
+ }
+ }
+
+ THashTable(THashTable&& ht) noexcept
+ : base_type(ht._get_hash_fun(), ht._get_key_extract(), ht._get_key_eq(), ht._get_alloc())
+ , buckets(ht.buckets.get_allocator())
+ , num_elements(0)
+ {
+ initialize_buckets(buckets, 0);
+ this->swap(ht);
+ }
+
+ THashTable& operator=(const THashTable& ht) {
+ if (&ht != this) {
+ basic_clear();
+ this->_set_hash_fun(ht._get_hash_fun());
+ this->_set_key_eq(ht._get_key_eq());
+ this->_set_key_extract(ht._get_key_extract());
+ /* We don't copy allocator for a reason. */
+
+ if (ht.empty()) {
+ /* Some of the old code in Arcadia works around the behavior in
+ * clear() by invoking operator= with empty hash as an argument.
+ * It's expected that this will deallocate the buckets array, so
+ * this is what we have to do here. */
+ deinitialize_buckets(buckets);
+ initialize_buckets(buckets, 0);
+ } else {
+ if (buckets.capacity() > ht.buckets.size()) {
+ buckets.resize_noallocate(ht.buckets.ExtSize());
+ } else {
+ deinitialize_buckets(buckets);
+ initialize_buckets_dynamic(buckets, ht.buckets.ExtSize());
+ }
+
+ copy_from_dynamic(ht);
+ }
+ }
+ return *this;
+ }
+
+ THashTable& operator=(THashTable&& ht) noexcept {
+ basic_clear();
+ swap(ht);
+
+ return *this;
+ }
+
+ ~THashTable() {
+ basic_clear();
+ deinitialize_buckets(buckets);
+ }
+
+ size_type size() const noexcept {
+ return num_elements;
+ }
+ size_type max_size() const noexcept {
+ return size_type(-1);
+ }
+
+ Y_PURE_FUNCTION bool empty() const noexcept {
+ return size() == 0;
+ }
+
+ void swap(THashTable& ht) {
+ base_type::swap(ht);
+ buckets.swap(ht.buckets);
+ DoSwap(num_elements, ht.num_elements);
+ }
+
+ iterator begin() {
+ for (size_type n = 0; n < buckets.size(); ++n) /*y*/
+ if (buckets[n])
+ return iterator(buckets[n]); /*y*/
+ return end();
+ }
+
+ iterator end() {
+ return iterator(nullptr);
+ } /*y*/
+
+ const_iterator begin() const {
+ for (size_type n = 0; n < buckets.size(); ++n) /*y*/
+ if (buckets[n])
+ return const_iterator(buckets[n]); /*y*/
+ return end();
+ }
+
+ const_iterator end() const {
+ return const_iterator(nullptr);
+ } /*y*/
+
+public:
+ size_type bucket_count() const {
+ return buckets.size();
+ } /*y*/
+
+ size_type bucket_size(size_type bucket) const {
+ size_type result = 0;
+ if (const node* cur = buckets[bucket])
+ for (; !((uintptr_t)cur & 1); cur = cur->next)
+ result += 1;
+ return result;
+ }
+
+ template <class OtherValue>
+ std::pair<iterator, bool> insert_unique(const OtherValue& obj) {
+ reserve(num_elements + 1);
+ return insert_unique_noresize(obj);
+ }
+
+ template <class OtherValue>
+ iterator insert_equal(const OtherValue& obj) {
+ reserve(num_elements + 1);
+ return emplace_equal_noresize(obj);
+ }
+
+ template <typename... Args>
+ iterator emplace_equal(Args&&... args) {
+ reserve(num_elements + 1);
+ return emplace_equal_noresize(std::forward<Args>(args)...);
+ }
+
+ template <class OtherValue>
+ iterator insert_direct(const OtherValue& obj, insert_ctx ins) {
+ return emplace_direct(ins, obj);
+ }
+
+ template <typename... Args>
+ iterator emplace_direct(insert_ctx ins, Args&&... args) {
+ bool resized = reserve(num_elements + 1);
+ node* tmp = new_node(std::forward<Args>(args)...);
+ if (resized) {
+ find_i(get_key(tmp->val), ins);
+ }
+ tmp->next = *ins ? *ins : (node*)((uintptr_t)(ins + 1) | 1);
+ *ins = tmp;
+ ++num_elements;
+ return iterator(tmp);
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace_unique(Args&&... args) {
+ reserve(num_elements + 1);
+ return emplace_unique_noresize(std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace_unique_noresize(Args&&... args);
+
+ template <class OtherValue>
+ std::pair<iterator, bool> insert_unique_noresize(const OtherValue& obj);
+
+ template <typename... Args>
+ iterator emplace_equal_noresize(Args&&... args);
+
+ template <class InputIterator>
+ void insert_unique(InputIterator f, InputIterator l) {
+ insert_unique(f, l, typename std::iterator_traits<InputIterator>::iterator_category());
+ }
+
+ template <class InputIterator>
+ void insert_equal(InputIterator f, InputIterator l) {
+ insert_equal(f, l, typename std::iterator_traits<InputIterator>::iterator_category());
+ }
+
+ template <class InputIterator>
+ void insert_unique(InputIterator f, InputIterator l, std::input_iterator_tag) {
+ for (; f != l; ++f)
+ insert_unique(*f);
+ }
+
+ template <class InputIterator>
+ void insert_equal(InputIterator f, InputIterator l, std::input_iterator_tag) {
+ for (; f != l; ++f)
+ insert_equal(*f);
+ }
+
+ template <class ForwardIterator>
+ void insert_unique(ForwardIterator f, ForwardIterator l, std::forward_iterator_tag) {
+ difference_type n = std::distance(f, l);
+
+ reserve(num_elements + n);
+ for (; n > 0; --n, ++f)
+ insert_unique_noresize(*f);
+ }
+
+ template <class ForwardIterator>
+ void insert_equal(ForwardIterator f, ForwardIterator l, std::forward_iterator_tag) {
+ difference_type n = std::distance(f, l);
+
+ reserve(num_elements + n);
+ for (; n > 0; --n, ++f)
+ emplace_equal_noresize(*f);
+ }
+
+ template <class OtherValue>
+ reference find_or_insert(const OtherValue& v);
+
+ template <class OtherKey>
+ iterator find(const OtherKey& key) {
+ size_type n = bkt_num_key(key);
+ node* first;
+ for (first = buckets[n];
+ first && !equals(get_key(first->val), key);
+ first = ((uintptr_t)first->next & 1) ? nullptr : first->next) /*y*/
+ {
+ }
+ return iterator(first); /*y*/
+ }
+
+ template <class OtherKey>
+ const_iterator find(const OtherKey& key) const {
+ size_type n = bkt_num_key(key);
+ const node* first;
+ for (first = buckets[n];
+ first && !equals(get_key(first->val), key);
+ first = ((uintptr_t)first->next & 1) ? nullptr : first->next) /*y*/
+ {
+ }
+ return const_iterator(first); /*y*/
+ }
+
+ template <class OtherKey>
+ iterator find_i(const OtherKey& key, insert_ctx& ins);
+
+ template <class OtherKey>
+ size_type count(const OtherKey& key) const {
+ const size_type n = bkt_num_key(key);
+ size_type result = 0;
+
+ if (const node* cur = buckets[n])
+ for (; !((uintptr_t)cur & 1); cur = cur->next)
+ if (equals(get_key(cur->val), key))
+ ++result;
+ return result;
+ }
+
+ template <class OtherKey>
+ std::pair<iterator, iterator> equal_range(const OtherKey& key);
+
+ template <class OtherKey>
+ std::pair<const_iterator, const_iterator> equal_range(const OtherKey& key) const;
+
+ template <class OtherKey>
+ size_type erase(const OtherKey& key);
+
+ template <class OtherKey>
+ size_type erase_one(const OtherKey& key);
+
+ // void (instead of iterator) is intended, see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2023.pdf
+ void erase(const iterator& it);
+ void erase(iterator first, iterator last);
+
+ void erase(const const_iterator& it);
+ void erase(const_iterator first, const_iterator last);
+
+ bool reserve(size_type num_elements_hint);
+ void basic_clear();
+
+ /**
+ * Clears the hashtable without deallocating the nodes.
+ *
+ * This might come in handy with non-standard allocators, e.g. a pool
+ * allocator with a pool that is then cleared manually, thus releasing all
+ * the nodes at once.
+ */
+ void release_nodes() {
+ if (empty())
+ return; /* Need this check because empty buckets may reside in read-only memory. */
+
+ clear_buckets(buckets);
+ num_elements = 0;
+ }
+
+ // implemented in save_stl.h
+ template <class KeySaver>
+ int save_for_st(IOutputStream* stream, KeySaver& ks, sthash<int, int, THash<int>, TEqualTo<int>, typename KeySaver::TSizeType>* stHash = nullptr) const;
+
+ void clear(size_type downsize) {
+ basic_clear();
+
+ if (downsize < buckets.size()) {
+ const TBucketDivisor newSize = HashBucketCountExt(downsize);
+ if (newSize() < buckets.size()) {
+ Y_ASSERT(newSize() >= 7); /* We cannot downsize static buckets. */
+ buckets.resize_noallocate(newSize);
+ }
+ }
+ }
+
+ /**
+ * Clears the hashtable and tries to reasonably downsize it. Note that
+ * downsizing is mainly for the following use case:
+ *
+ * THashTable hash;
+ * for(...) {
+ * if (someCond())
+ * hash.clear();
+ * hash.insert(...);
+ * }
+ *
+ * Here if at some point `hash` gets really big, then all the following calls
+ * to `clear` become really slow as they have to iterate through all the the
+ * empty buckets. This is worked around by squeezing the buckets array a little
+ * bit with every `clear` call.
+ *
+ * Alternatively, the user can call `basic_clear`, which doesn't do the
+ * downsizing.
+ */
+ void clear() {
+ if (num_elements)
+ clear((num_elements * 2 + buckets.size()) / 3);
+ }
+
+private:
+ static void initialize_buckets(buckets_type& buckets, size_type sizeHint) {
+ if (sizeHint == 0) {
+ buckets.initialize_static(reinterpret_cast<node**>(const_cast<void**>(_yhashtable_empty_data)) + 1, TBucketDivisor::One());
+ } else {
+ TBucketDivisor size = HashBucketCountExt(sizeHint);
+ Y_ASSERT(size() >= 7);
+
+ initialize_buckets_dynamic(buckets, size);
+ }
+ }
+
+ static void initialize_buckets_dynamic(buckets_type& buckets, TBucketDivisor size) {
+ buckets.initialize_dynamic(size);
+ memset(buckets.data(), 0, size() * sizeof(*buckets.data()));
+ buckets[size()] = (node*)1;
+ }
+
+ static void deinitialize_buckets(buckets_type& buckets) {
+ if (buckets.size() == 1) {
+ buckets.deinitialize_static();
+ } else {
+ buckets.deinitialize_dynamic();
+ }
+ }
+
+ static void clear_buckets(buckets_type& buckets) {
+ memset(buckets.data(), 0, buckets.size() * sizeof(*buckets.data()));
+ }
+
+ template <class OtherKey>
+ size_type bkt_num_key(const OtherKey& key) const {
+ return bkt_num_key(key, buckets.ExtSize());
+ }
+
+ template <class OtherValue>
+ size_type bkt_num(const OtherValue& obj) const {
+ return bkt_num_key(get_key(obj));
+ }
+
+ template <class OtherKey>
+ size_type bkt_num_key(const OtherKey& key, TBucketDivisor n) const {
+ const size_type bucket = n.Remainder(this->_get_hash_fun()(key));
+ Y_ASSERT((0 <= bucket) && (bucket < n()));
+ return bucket;
+ }
+
+ template <class OtherValue>
+ size_type bkt_num(const OtherValue& obj, TBucketDivisor n) const {
+ return bkt_num_key(get_key(obj), n);
+ }
+
+ template <typename... Args>
+ node* new_node(Args&&... val) {
+ node* n = get_node();
+ n->next = (node*)1; /*y*/ // just for a case
+ try {
+ new (static_cast<void*>(&n->val)) Value(std::forward<Args>(val)...);
+ } catch (...) {
+ put_node(n);
+ throw;
+ }
+ return n;
+ }
+
+ void delete_node(node* n) {
+ n->val.~Value();
+ //n->next = (node*) 0xDeadBeeful;
+ put_node(n);
+ }
+
+ void erase_bucket(const size_type n, node* first, node* last);
+ void erase_bucket(const size_type n, node* last);
+
+ void copy_from_dynamic(const THashTable& ht);
+};
+
+template <class V>
+__yhashtable_iterator<V>& __yhashtable_iterator<V>::operator++() {
+ Y_ASSERT(cur);
+ cur = cur->next;
+ if ((uintptr_t)cur & 1) {
+ node** bucket = (node**)((uintptr_t)cur & ~1);
+ while (*bucket == nullptr)
+ ++bucket;
+ Y_ASSERT(*bucket != nullptr);
+ cur = (node*)((uintptr_t)*bucket & ~1);
+ }
+ return *this;
+}
+
+template <class V>
+inline __yhashtable_iterator<V> __yhashtable_iterator<V>::operator++(int) {
+ iterator tmp = *this;
+ ++*this;
+ return tmp;
+}
+
+template <class V>
+__yhashtable_const_iterator<V>& __yhashtable_const_iterator<V>::operator++() {
+ Y_ASSERT(cur);
+ cur = cur->next;
+ if ((uintptr_t)cur & 1) {
+ node** bucket = (node**)((uintptr_t)cur & ~1);
+ while (*bucket == nullptr)
+ ++bucket;
+ Y_ASSERT(*bucket != nullptr);
+ cur = (node*)((uintptr_t)*bucket & ~1);
+ }
+ return *this;
+}
+
+template <class V>
+inline __yhashtable_const_iterator<V> __yhashtable_const_iterator<V>::operator++(int) {
+ const_iterator tmp = *this;
+ ++*this;
+ return tmp;
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <typename... Args>
+std::pair<typename THashTable<V, K, HF, Ex, Eq, A>::iterator, bool> THashTable<V, K, HF, Ex, Eq, A>::emplace_unique_noresize(Args&&... args) {
+ auto deleter = [&](node* tmp) { delete_node(tmp); };
+ node* tmp = new_node(std::forward<Args>(args)...);
+ std::unique_ptr<node, decltype(deleter)> guard(tmp, deleter);
+
+ const size_type n = bkt_num(tmp->val);
+ node* first = buckets[n];
+
+ if (first) /*y*/
+ for (node* cur = first; !((uintptr_t)cur & 1); cur = cur->next) /*y*/
+ if (equals(get_key(cur->val), get_key(tmp->val)))
+ return std::pair<iterator, bool>(iterator(cur), false); /*y*/
+
+ guard.release();
+ tmp->next = first ? first : (node*)((uintptr_t)&buckets[n + 1] | 1); /*y*/
+ buckets[n] = tmp;
+ ++num_elements;
+ return std::pair<iterator, bool>(iterator(tmp), true); /*y*/
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherValue>
+std::pair<typename THashTable<V, K, HF, Ex, Eq, A>::iterator, bool> THashTable<V, K, HF, Ex, Eq, A>::insert_unique_noresize(const OtherValue& obj) {
+ const size_type n = bkt_num(obj);
+ node* first = buckets[n];
+
+ if (first) /*y*/
+ for (node* cur = first; !((uintptr_t)cur & 1); cur = cur->next) /*y*/
+ if (equals(get_key(cur->val), get_key(obj)))
+ return std::pair<iterator, bool>(iterator(cur), false); /*y*/
+
+ node* tmp = new_node(obj);
+ tmp->next = first ? first : (node*)((uintptr_t)&buckets[n + 1] | 1); /*y*/
+ buckets[n] = tmp;
+ ++num_elements;
+ return std::pair<iterator, bool>(iterator(tmp), true); /*y*/
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <typename... Args>
+__yhashtable_iterator<V> THashTable<V, K, HF, Ex, Eq, A>::emplace_equal_noresize(Args&&... args) {
+ auto deleter = [&](node* tmp) { delete_node(tmp); };
+ node* tmp = new_node(std::forward<Args>(args)...);
+ std::unique_ptr<node, decltype(deleter)> guard(tmp, deleter);
+ const size_type n = bkt_num(tmp->val);
+ node* first = buckets[n];
+
+ if (first) /*y*/
+ for (node* cur = first; !((uintptr_t)cur & 1); cur = cur->next) /*y*/
+ if (equals(get_key(cur->val), get_key(tmp->val))) {
+ guard.release();
+ tmp->next = cur->next;
+ cur->next = tmp;
+ ++num_elements;
+ return iterator(tmp); /*y*/
+ }
+
+ guard.release();
+ tmp->next = first ? first : (node*)((uintptr_t)&buckets[n + 1] | 1); /*y*/
+ buckets[n] = tmp;
+ ++num_elements;
+ return iterator(tmp); /*y*/
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherValue>
+typename THashTable<V, K, HF, Ex, Eq, A>::reference THashTable<V, K, HF, Ex, Eq, A>::find_or_insert(const OtherValue& v) {
+ reserve(num_elements + 1);
+
+ size_type n = bkt_num_key(get_key(v));
+ node* first = buckets[n];
+
+ if (first) /*y*/
+ for (node* cur = first; !((uintptr_t)cur & 1); cur = cur->next) /*y*/
+ if (equals(get_key(cur->val), get_key(v)))
+ return cur->val;
+
+ node* tmp = new_node(v);
+ tmp->next = first ? first : (node*)((uintptr_t)&buckets[n + 1] | 1); /*y*/
+ buckets[n] = tmp;
+ ++num_elements;
+ return tmp->val;
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherKey>
+__yhashtable_iterator<V> THashTable<V, K, HF, Ex, Eq, A>::find_i(const OtherKey& key, insert_ctx& ins) {
+ size_type n = bkt_num_key(key);
+ ins = &buckets[n];
+ node* first = buckets[n];
+
+ if (first) /*y*/
+ for (node* cur = first; !((uintptr_t)cur & 1); cur = cur->next) /*y*/
+ if (equals(get_key(cur->val), key))
+ return iterator(cur); /*y*/
+ return end();
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherKey>
+std::pair<__yhashtable_iterator<V>, __yhashtable_iterator<V>> THashTable<V, K, HF, Ex, Eq, A>::equal_range(const OtherKey& key) {
+ using pii = std::pair<iterator, iterator>;
+ const size_type n = bkt_num_key(key);
+ node* first = buckets[n];
+
+ if (first) /*y*/
+ for (; !((uintptr_t)first & 1); first = first->next) { /*y*/
+ if (equals(get_key(first->val), key)) {
+ for (node* cur = first->next; !((uintptr_t)cur & 1); cur = cur->next)
+ if (!equals(get_key(cur->val), key))
+ return pii(iterator(first), iterator(cur)); /*y*/
+ for (size_type m = n + 1; m < buckets.size(); ++m) /*y*/
+ if (buckets[m])
+ return pii(iterator(first), /*y*/
+ iterator(buckets[m])); /*y*/
+ return pii(iterator(first), end()); /*y*/
+ }
+ }
+ return pii(end(), end());
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherKey>
+std::pair<__yhashtable_const_iterator<V>, __yhashtable_const_iterator<V>> THashTable<V, K, HF, Ex, Eq, A>::equal_range(const OtherKey& key) const {
+ using pii = std::pair<const_iterator, const_iterator>;
+ const size_type n = bkt_num_key(key);
+ const node* first = buckets[n];
+
+ if (first) /*y*/
+ for (; !((uintptr_t)first & 1); first = first->next) { /*y*/
+ if (equals(get_key(first->val), key)) {
+ for (const node* cur = first->next; !((uintptr_t)cur & 1); cur = cur->next)
+ if (!equals(get_key(cur->val), key))
+ return pii(const_iterator(first), /*y*/
+ const_iterator(cur)); /*y*/
+ for (size_type m = n + 1; m < buckets.size(); ++m) /*y*/
+ if (buckets[m])
+ return pii(const_iterator(first /*y*/),
+ const_iterator(buckets[m] /*y*/));
+ return pii(const_iterator(first /*y*/), end());
+ }
+ }
+ return pii(end(), end());
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherKey>
+typename THashTable<V, K, HF, Ex, Eq, A>::size_type THashTable<V, K, HF, Ex, Eq, A>::erase(const OtherKey& key) {
+ const size_type n = bkt_num_key(key);
+ node* first = buckets[n];
+ size_type erased = 0;
+
+ if (first) {
+ node* cur = first;
+ node* next = cur->next;
+ while (!((uintptr_t)next & 1)) { /*y*/
+ if (equals(get_key(next->val), key)) {
+ cur->next = next->next;
+ ++erased;
+ --num_elements;
+ delete_node(next);
+ next = cur->next;
+ } else {
+ cur = next;
+ next = cur->next;
+ }
+ }
+ if (equals(get_key(first->val), key)) {
+ buckets[n] = ((uintptr_t)first->next & 1) ? nullptr : first->next; /*y*/
+ ++erased;
+ --num_elements;
+ delete_node(first);
+ }
+ }
+ return erased;
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+template <class OtherKey>
+typename THashTable<V, K, HF, Ex, Eq, A>::size_type THashTable<V, K, HF, Ex, Eq, A>::erase_one(const OtherKey& key) {
+ const size_type n = bkt_num_key(key);
+ node* first = buckets[n];
+
+ if (first) {
+ node* cur = first;
+ node* next = cur->next;
+ while (!((uintptr_t)next & 1)) { /*y*/
+ if (equals(get_key(next->val), key)) {
+ cur->next = next->next;
+ --num_elements;
+ delete_node(next);
+ return 1;
+ } else {
+ cur = next;
+ next = cur->next;
+ }
+ }
+ if (equals(get_key(first->val), key)) {
+ buckets[n] = ((uintptr_t)first->next & 1) ? nullptr : first->next; /*y*/
+ --num_elements;
+ delete_node(first);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+void THashTable<V, K, HF, Ex, Eq, A>::erase(const iterator& it) {
+ if (node* const p = it.cur) {
+ const size_type n = bkt_num(p->val);
+ node* cur = buckets[n];
+
+ if (cur == p) {
+ buckets[n] = ((uintptr_t)cur->next & 1) ? nullptr : cur->next; /*y*/
+ delete_node(cur);
+ --num_elements;
+ } else {
+ node* next = cur->next;
+ while (!((uintptr_t)next & 1)) {
+ if (next == p) {
+ cur->next = next->next;
+ delete_node(next);
+ --num_elements;
+ break;
+ } else {
+ cur = next;
+ next = cur->next;
+ }
+ }
+ }
+ }
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+void THashTable<V, K, HF, Ex, Eq, A>::erase(iterator first, iterator last) {
+ size_type f_bucket = first.cur ? bkt_num(first.cur->val) : buckets.size(); /*y*/
+ size_type l_bucket = last.cur ? bkt_num(last.cur->val) : buckets.size(); /*y*/
+
+ if (first.cur == last.cur)
+ return;
+ else if (f_bucket == l_bucket)
+ erase_bucket(f_bucket, first.cur, last.cur);
+ else {
+ erase_bucket(f_bucket, first.cur, nullptr);
+ for (size_type n = f_bucket + 1; n < l_bucket; ++n)
+ erase_bucket(n, nullptr);
+ if (l_bucket != buckets.size()) /*y*/
+ erase_bucket(l_bucket, last.cur);
+ }
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+inline void
+THashTable<V, K, HF, Ex, Eq, A>::erase(const_iterator first, const_iterator last) {
+ erase(iterator(const_cast<node*>(first.cur)), iterator(const_cast<node*>(last.cur)));
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+inline void THashTable<V, K, HF, Ex, Eq, A>::erase(const const_iterator& it) {
+ erase(iterator(const_cast<node*>(it.cur)));
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+bool THashTable<V, K, HF, Ex, Eq, A>::reserve(size_type num_elements_hint) {
+ const size_type old_n = buckets.size(); /*y*/
+ if (num_elements_hint + 1 > old_n) {
+ if (old_n != 1 && num_elements_hint <= old_n) // TODO: this if is for backwards compatibility down to order-in-buckets level. Can be safely removed.
+ return false;
+
+ const TBucketDivisor n = HashBucketCountExt(num_elements_hint + 1, buckets.BucketDivisorHint() + 1);
+ if (n() > old_n) {
+ buckets_type tmp(buckets.get_allocator());
+ initialize_buckets_dynamic(tmp, n);
+#ifdef __STL_USE_EXCEPTIONS
+ try {
+#endif /* __STL_USE_EXCEPTIONS */
+ for (size_type bucket = 0; bucket < old_n; ++bucket) {
+ node* first = buckets[bucket];
+ while (first) {
+ size_type new_bucket = bkt_num(first->val, n);
+ node* next = first->next;
+ buckets[bucket] = ((uintptr_t)next & 1) ? nullptr : next; /*y*/
+ next = tmp[new_bucket];
+ first->next = next ? next : (node*)((uintptr_t) & (tmp[new_bucket + 1]) | 1); /*y*/
+ tmp[new_bucket] = first;
+ first = buckets[bucket];
+ }
+ }
+
+ buckets.swap(tmp);
+ deinitialize_buckets(tmp);
+
+ return true;
+#ifdef __STL_USE_EXCEPTIONS
+ } catch (...) {
+ for (size_type bucket = 0; bucket < tmp.size() - 1; ++bucket) {
+ while (tmp[bucket]) {
+ node* next = tmp[bucket]->next;
+ delete_node(tmp[bucket]);
+ tmp[bucket] = ((uintptr_t)next & 1) ? nullptr : next /*y*/;
+ }
+ }
+ throw;
+ }
+#endif /* __STL_USE_EXCEPTIONS */
+ }
+ }
+
+ return false;
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+void THashTable<V, K, HF, Ex, Eq, A>::erase_bucket(const size_type n, node* first, node* last) {
+ node* cur = buckets[n];
+ if (cur == first)
+ erase_bucket(n, last);
+ else {
+ node* next;
+ for (next = cur->next; next != first; cur = next, next = cur->next)
+ ;
+ while (next != last) { /*y; 3.1*/
+ cur->next = next->next;
+ delete_node(next);
+ next = ((uintptr_t)cur->next & 1) ? nullptr : cur->next; /*y*/
+ --num_elements;
+ }
+ }
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+void THashTable<V, K, HF, Ex, Eq, A>::erase_bucket(const size_type n, node* last) {
+ node* cur = buckets[n];
+ while (cur != last) {
+ node* next = cur->next;
+ delete_node(cur);
+ cur = ((uintptr_t)next & 1) ? nullptr : next; /*y*/
+ buckets[n] = cur;
+ --num_elements;
+ }
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+void THashTable<V, K, HF, Ex, Eq, A>::basic_clear() {
+ if (!num_elements) {
+ return;
+ }
+
+ node** first = buckets.begin();
+ node** last = buckets.end();
+ for (; first < last; ++first) {
+ node* cur = *first;
+ if (cur) { /*y*/
+ while (!((uintptr_t)cur & 1)) { /*y*/
+ node* next = cur->next;
+ delete_node(cur);
+ cur = next;
+ }
+ *first = nullptr;
+ }
+ }
+ num_elements = 0;
+}
+
+template <class V, class K, class HF, class Ex, class Eq, class A>
+void THashTable<V, K, HF, Ex, Eq, A>::copy_from_dynamic(const THashTable& ht) {
+ Y_ASSERT(buckets.size() == ht.buckets.size() && !ht.empty());
+
+#ifdef __STL_USE_EXCEPTIONS
+ try {
+#endif /* __STL_USE_EXCEPTIONS */
+ for (size_type i = 0; i < ht.buckets.size(); ++i) { /*y*/
+ if (const node* cur = ht.buckets[i]) {
+ node* copy = new_node(cur->val);
+ buckets[i] = copy;
+
+ for (node* next = cur->next; !((uintptr_t)next & 1); cur = next, next = cur->next) {
+ copy->next = new_node(next->val);
+ copy = copy->next;
+ }
+ copy->next = (node*)((uintptr_t)&buckets[i + 1] | 1); /*y*/
+ }
+ }
+ num_elements = ht.num_elements;
+#ifdef __STL_USE_EXCEPTIONS
+ } catch (...) {
+ basic_clear();
+ throw;
+ }
+#endif /* __STL_USE_EXCEPTIONS */
+}
+
+namespace NPrivate {
+ template <class Key>
+ inline TString MapKeyToString(const Key&) {
+ return TypeName<Key>();
+ }
+
+ TString MapKeyToString(TStringBuf key);
+ TString MapKeyToString(unsigned short key);
+ TString MapKeyToString(short key);
+ TString MapKeyToString(unsigned int key);
+ TString MapKeyToString(int key);
+ TString MapKeyToString(unsigned long key);
+ TString MapKeyToString(long key);
+ TString MapKeyToString(unsigned long long key);
+ TString MapKeyToString(long long key);
+
+ inline TString MapKeyToString(const TString& key) {
+ return MapKeyToString(TStringBuf(key));
+ }
+
+ inline TString MapKeyToString(const char* key) {
+ return MapKeyToString(TStringBuf(key));
+ }
+
+ inline TString MapKeyToString(char* key) {
+ return MapKeyToString(TStringBuf(key));
+ }
+
+ [[noreturn]] void ThrowKeyNotFoundInHashTableException(const TStringBuf keyRepresentation);
+}
+
+template <class Key, class T, class HashFcn, class EqualKey, class Alloc>
+class THashMap: public TMapOps<THashMap<Key, T, HashFcn, EqualKey, Alloc>> {
+private:
+ using ht = THashTable<std::pair<const Key, T>, Key, HashFcn, TSelect1st, EqualKey, Alloc>;
+ ht rep;
+
+public:
+ using key_type = typename ht::key_type;
+ using value_type = typename ht::value_type;
+ using hasher = typename ht::hasher;
+ using key_equal = typename ht::key_equal;
+ using allocator_type = typename ht::allocator_type;
+ using node_allocator_type = typename ht::node_allocator_type;
+ using mapped_type = T;
+
+ using size_type = typename ht::size_type;
+ using difference_type = typename ht::difference_type;
+ using pointer = typename ht::pointer;
+ using const_pointer = typename ht::const_pointer;
+ using reference = typename ht::reference;
+ using const_reference = typename ht::const_reference;
+
+ using iterator = typename ht::iterator;
+ using const_iterator = typename ht::const_iterator;
+ using insert_ctx = typename ht::insert_ctx;
+
+ hasher hash_function() const {
+ return rep.hash_function();
+ }
+ key_equal key_eq() const {
+ return rep.key_eq();
+ }
+
+public:
+ THashMap()
+ : rep(0, hasher(), key_equal())
+ {
+ }
+ template <class TAllocParam>
+ explicit THashMap(TAllocParam* allocParam, size_type n = 0)
+ : rep(n, hasher(), key_equal(), allocParam)
+ {
+ }
+ explicit THashMap(size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ }
+ THashMap(size_type n, const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ }
+ THashMap(size_type n, const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ }
+ template <class TAllocParam>
+ explicit THashMap(size_type n, TAllocParam* allocParam)
+ : rep(n, hasher(), key_equal(), allocParam)
+ {
+ }
+ template <class InputIterator>
+ THashMap(InputIterator f, InputIterator l)
+ : rep(0, hasher(), key_equal())
+ {
+ rep.insert_unique(f, l);
+ }
+ template <class InputIterator>
+ THashMap(InputIterator f, InputIterator l, size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ rep.insert_unique(f, l);
+ }
+ template <class InputIterator>
+ THashMap(InputIterator f, InputIterator l, size_type n,
+ const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ rep.insert_unique(f, l);
+ }
+ template <class InputIterator>
+ THashMap(InputIterator f, InputIterator l, size_type n,
+ const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ rep.insert_unique(f, l);
+ }
+
+ THashMap(const std::initializer_list<std::pair<Key, T>>& list)
+ : rep(list.size(), hasher(), key_equal())
+ {
+ for (const auto& v : list) {
+ rep.insert_unique_noresize(v);
+ }
+ }
+
+ // THashMap has implicit copy/move constructors and copy-/move-assignment operators
+ // because its implementation is backed by THashTable.
+ // See hash_ut.cpp
+
+public:
+ size_type size() const noexcept {
+ return rep.size();
+ }
+ yssize_t ysize() const noexcept {
+ return (yssize_t)rep.size();
+ }
+ size_type max_size() const noexcept {
+ return rep.max_size();
+ }
+
+ Y_PURE_FUNCTION bool empty() const noexcept {
+ return rep.empty();
+ }
+ explicit operator bool() const noexcept {
+ return !empty();
+ }
+ void swap(THashMap& hs) {
+ rep.swap(hs.rep);
+ }
+
+ iterator begin() {
+ return rep.begin();
+ }
+ iterator end() {
+ return rep.end();
+ }
+ const_iterator begin() const {
+ return rep.begin();
+ }
+ const_iterator end() const {
+ return rep.end();
+ }
+ const_iterator cbegin() const {
+ return rep.begin();
+ }
+ const_iterator cend() const {
+ return rep.end();
+ }
+
+public:
+ template <class InputIterator>
+ void insert(InputIterator f, InputIterator l) {
+ rep.insert_unique(f, l);
+ }
+
+ std::pair<iterator, bool> insert(const value_type& obj) {
+ return rep.insert_unique(obj);
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace(Args&&... args) {
+ return rep.emplace_unique(std::forward<Args>(args)...);
+ }
+
+ std::pair<iterator, bool> insert_noresize(const value_type& obj) {
+ return rep.insert_unique_noresize(obj);
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace_noresize(Args&&... args) {
+ return rep.emplace_unique_noresize(std::forward<Args>(args)...);
+ }
+
+ template <class TheObj>
+ iterator insert_direct(const TheObj& obj, const insert_ctx& ins) {
+ return rep.insert_direct(obj, ins);
+ }
+
+ template <typename... Args>
+ iterator emplace_direct(const insert_ctx& ins, Args&&... args) {
+ return rep.emplace_direct(ins, std::forward<Args>(args)...);
+ }
+
+ template <typename TKey, typename... Args>
+ std::pair<iterator, bool> try_emplace(TKey&& key, Args&&... args) {
+ insert_ctx ctx = nullptr;
+ iterator it = find(key, ctx);
+ if (it == end()) {
+ it = rep.emplace_direct(ctx, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<TKey>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ return {it, true};
+ }
+ return {it, false};
+ }
+
+ template <class TheKey>
+ iterator find(const TheKey& key) {
+ return rep.find(key);
+ }
+
+ template <class TheKey>
+ const_iterator find(const TheKey& key) const {
+ return rep.find(key);
+ }
+
+ template <class TheKey>
+ iterator find(const TheKey& key, insert_ctx& ins) {
+ return rep.find_i(key, ins);
+ }
+
+ template <class TheKey>
+ bool contains(const TheKey& key) const {
+ return rep.find(key) != rep.end();
+ }
+ bool contains(const key_type& key) const {
+ return rep.find(key) != rep.end();
+ }
+
+ template <class TheKey>
+ bool contains(const TheKey& key, insert_ctx& ins) {
+ return rep.find_i(key, ins) != rep.end();
+ }
+
+ template <class TKey>
+ T& operator[](const TKey& key) {
+ insert_ctx ctx = nullptr;
+ iterator it = find(key, ctx);
+
+ if (it != end()) {
+ return it->second;
+ }
+
+ return rep.emplace_direct(ctx, std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple())->second;
+ }
+
+ template <class TheKey>
+ const T& at(const TheKey& key) const {
+ using namespace ::NPrivate;
+ const_iterator it = find(key);
+
+ if (Y_UNLIKELY(it == end())) {
+ ::NPrivate::ThrowKeyNotFoundInHashTableException(MapKeyToString(key));
+ }
+
+ return it->second;
+ }
+
+ template <class TheKey>
+ T& at(const TheKey& key) {
+ using namespace ::NPrivate;
+ iterator it = find(key);
+
+ if (Y_UNLIKELY(it == end())) {
+ ::NPrivate::ThrowKeyNotFoundInHashTableException(MapKeyToString(key));
+ }
+
+ return it->second;
+ }
+
+ template <class TKey>
+ size_type count(const TKey& key) const {
+ return rep.count(key);
+ }
+
+ template <class TKey>
+ std::pair<iterator, iterator> equal_range(const TKey& key) {
+ return rep.equal_range(key);
+ }
+
+ template <class TKey>
+ std::pair<const_iterator, const_iterator> equal_range(const TKey& key) const {
+ return rep.equal_range(key);
+ }
+
+ template <class TKey>
+ size_type erase(const TKey& key) {
+ return rep.erase_one(key);
+ }
+
+ void erase(iterator it) {
+ rep.erase(it);
+ }
+ void erase(iterator f, iterator l) {
+ rep.erase(f, l);
+ }
+ void clear() {
+ rep.clear();
+ }
+ void clear(size_t downsize_hint) {
+ rep.clear(downsize_hint);
+ }
+ void basic_clear() {
+ rep.basic_clear();
+ }
+ void release_nodes() {
+ rep.release_nodes();
+ }
+
+ // if (stHash != NULL) bucket_count() must be equal to stHash->bucket_count()
+ template <class KeySaver>
+ int save_for_st(IOutputStream* stream, KeySaver& ks, sthash<int, int, THash<int>, TEqualTo<int>, typename KeySaver::TSizeType>* stHash = nullptr) const {
+ return rep.template save_for_st<KeySaver>(stream, ks, stHash);
+ }
+
+public:
+ void reserve(size_type hint) {
+ rep.reserve(hint);
+ }
+ size_type bucket_count() const {
+ return rep.bucket_count();
+ }
+ size_type bucket_size(size_type n) const {
+ return rep.bucket_size(n);
+ }
+ node_allocator_type& GetNodeAllocator() {
+ return rep.GetNodeAllocator();
+ }
+ const node_allocator_type& GetNodeAllocator() const {
+ return rep.GetNodeAllocator();
+ }
+};
+
+template <class Key, class T, class HashFcn, class EqualKey, class Alloc>
+inline bool operator==(const THashMap<Key, T, HashFcn, EqualKey, Alloc>& hm1, const THashMap<Key, T, HashFcn, EqualKey, Alloc>& hm2) {
+ if (hm1.size() != hm2.size()) {
+ return false;
+ }
+ for (const auto& it1 : hm1) {
+ auto it2 = hm2.find(it1.first);
+ if ((it2 == hm2.end()) || !(it1 == *it2)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class Key, class T, class HashFcn, class EqualKey, class Alloc>
+inline bool operator!=(const THashMap<Key, T, HashFcn, EqualKey, Alloc>& hm1, const THashMap<Key, T, HashFcn, EqualKey, Alloc>& hm2) {
+ return !(hm1 == hm2);
+}
+
+template <class Key, class T, class HashFcn, class EqualKey, class Alloc>
+class THashMultiMap {
+private:
+ using ht = THashTable<std::pair<const Key, T>, Key, HashFcn, TSelect1st, EqualKey, Alloc>;
+ ht rep;
+
+public:
+ using key_type = typename ht::key_type;
+ using value_type = typename ht::value_type;
+ using hasher = typename ht::hasher;
+ using key_equal = typename ht::key_equal;
+ using mapped_type = T;
+ using allocator_type = typename ht::allocator_type;
+
+ using size_type = typename ht::size_type;
+ using difference_type = typename ht::difference_type;
+ using pointer = typename ht::pointer;
+ using const_pointer = typename ht::const_pointer;
+ using reference = typename ht::reference;
+ using const_reference = typename ht::const_reference;
+
+ using iterator = typename ht::iterator;
+ using const_iterator = typename ht::const_iterator;
+ using insert_ctx = typename ht::insert_ctx;
+
+ hasher hash_function() const {
+ return rep.hash_function();
+ }
+ key_equal key_eq() const {
+ return rep.key_eq();
+ }
+
+public:
+ THashMultiMap()
+ : rep(0, hasher(), key_equal())
+ {
+ }
+ template <typename TAllocParam>
+ explicit THashMultiMap(TAllocParam* allocParam)
+ : rep(0, hasher(), key_equal(), allocParam)
+ {
+ }
+ explicit THashMultiMap(size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ }
+ THashMultiMap(size_type n, const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ }
+ THashMultiMap(size_type n, const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ }
+
+ template <class InputIterator>
+ THashMultiMap(InputIterator f, InputIterator l)
+ : rep(0, hasher(), key_equal())
+ {
+ rep.insert_equal(f, l);
+ }
+ template <class InputIterator>
+ THashMultiMap(InputIterator f, InputIterator l, size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ rep.insert_equal(f, l);
+ }
+ template <class InputIterator>
+ THashMultiMap(InputIterator f, InputIterator l, size_type n, const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ rep.insert_equal(f, l);
+ }
+ template <class InputIterator>
+ THashMultiMap(InputIterator f, InputIterator l, size_type n, const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ rep.insert_equal(f, l);
+ }
+
+ THashMultiMap(std::initializer_list<std::pair<Key, T>> list)
+ : rep(list.size(), hasher(), key_equal())
+ {
+ for (const auto& v : list) {
+ rep.emplace_equal_noresize(v);
+ }
+ }
+
+ // THashMultiMap has implicit copy/move constructors and copy-/move-assignment operators
+ // because its implementation is backed by THashTable.
+ // See hash_ut.cpp
+
+public:
+ size_type size() const {
+ return rep.size();
+ }
+ yssize_t ysize() const {
+ return (yssize_t)rep.size();
+ }
+ size_type max_size() const {
+ return rep.max_size();
+ }
+
+ Y_PURE_FUNCTION bool empty() const {
+ return rep.empty();
+ }
+ explicit operator bool() const noexcept {
+ return !empty();
+ }
+ void swap(THashMultiMap& hs) {
+ rep.swap(hs.rep);
+ }
+
+ iterator begin() {
+ return rep.begin();
+ }
+ iterator end() {
+ return rep.end();
+ }
+ const_iterator begin() const {
+ return rep.begin();
+ }
+ const_iterator end() const {
+ return rep.end();
+ }
+ const_iterator cbegin() const {
+ return rep.begin();
+ }
+ const_iterator cend() const {
+ return rep.end();
+ }
+
+public:
+ template <class InputIterator>
+ void insert(InputIterator f, InputIterator l) {
+ rep.insert_equal(f, l);
+ }
+
+ iterator insert(const value_type& obj) {
+ return rep.insert_equal(obj);
+ }
+
+ template <typename... Args>
+ iterator emplace(Args&&... args) {
+ return rep.emplace_equal(std::forward<Args>(args)...);
+ }
+
+ iterator insert_noresize(const value_type& obj) {
+ return rep.emplace_equal_noresize(obj);
+ }
+
+ template <typename... Args>
+ iterator emplace_noresize(Args&&... args) {
+ return rep.emplace_equal_noresize(std::forward<Args>(args)...);
+ }
+
+ template <class TheObj>
+ iterator insert_direct(const TheObj& obj, const insert_ctx& ins) {
+ return rep.insert_direct(obj, ins);
+ }
+
+ template <typename... Args>
+ iterator emplace_direct(const insert_ctx& ins, Args&&... args) {
+ return rep.emplace_direct(ins, std::forward<Args>(args)...);
+ }
+
+ template <class TKey>
+ const_iterator find(const TKey& key) const {
+ return rep.find(key);
+ }
+
+ template <class TKey>
+ iterator find(const TKey& key) {
+ return rep.find(key);
+ }
+
+ template <class TheKey>
+ iterator find(const TheKey& key, insert_ctx& ins) {
+ return rep.find_i(key, ins);
+ }
+
+ template <class TheKey>
+ bool contains(const TheKey& key) const {
+ return rep.find(key) != rep.end();
+ }
+
+ template <class TKey>
+ size_type count(const TKey& key) const {
+ return rep.count(key);
+ }
+
+ template <class TKey>
+ std::pair<iterator, iterator> equal_range(const TKey& key) {
+ return rep.equal_range(key);
+ }
+
+ template <class TKey>
+ std::pair<const_iterator, const_iterator> equal_range(const TKey& key) const {
+ return rep.equal_range(key);
+ }
+
+ size_type erase(const key_type& key) {
+ return rep.erase(key);
+ }
+ void erase(iterator it) {
+ rep.erase(it);
+ }
+ void erase(iterator f, iterator l) {
+ rep.erase(f, l);
+ }
+ void clear() {
+ rep.clear();
+ }
+ void clear(size_t downsize_hint) {
+ rep.clear(downsize_hint);
+ }
+ void basic_clear() {
+ rep.basic_clear();
+ }
+ void release_nodes() {
+ rep.release_nodes();
+ }
+
+ // if (stHash != NULL) bucket_count() must be equal to stHash->bucket_count()
+ template <class KeySaver>
+ int save_for_st(IOutputStream* stream, KeySaver& ks, sthash<int, int, THash<int>, TEqualTo<int>, typename KeySaver::TSizeType>* stHash = nullptr) const {
+ return rep.template save_for_st<KeySaver>(stream, ks, stHash);
+ }
+
+public:
+ void reserve(size_type hint) {
+ rep.reserve(hint);
+ }
+ size_type bucket_count() const {
+ return rep.bucket_count();
+ }
+ size_type bucket_size(size_type n) const {
+ return rep.bucket_size(n);
+ }
+};
+
+template <class Key, class T, class HF, class EqKey, class Alloc>
+inline bool operator==(const THashMultiMap<Key, T, HF, EqKey, Alloc>& hm1, const THashMultiMap<Key, T, HF, EqKey, Alloc>& hm2) {
+ // NOTE: copy-pasted from
+ // contrib/libs/cxxsupp/libcxx/include/unordered_map
+ // and adapted to THashMultiMap
+ if (hm1.size() != hm2.size()) {
+ return false;
+ }
+ using const_iterator = typename THashMultiMap<Key, T, HF, EqKey, Alloc>::const_iterator;
+ using TEqualRange = std::pair<const_iterator, const_iterator>;
+ for (const_iterator it = hm1.begin(), end = hm1.end(); it != end;) {
+ TEqualRange eq1 = hm1.equal_range(it->first);
+ TEqualRange eq2 = hm2.equal_range(it->first);
+ if (std::distance(eq1.first, eq1.second) != std::distance(eq2.first, eq2.second) ||
+ !std::is_permutation(eq1.first, eq1.second, eq2.first))
+ {
+ return false;
+ }
+ it = eq1.second;
+ }
+ return true;
+}
+
+template <class Key, class T, class HF, class EqKey, class Alloc>
+inline bool operator!=(const THashMultiMap<Key, T, HF, EqKey, Alloc>& hm1, const THashMultiMap<Key, T, HF, EqKey, Alloc>& hm2) {
+ return !(hm1 == hm2);
+}
+
+// Cannot name it just 'Hash' because it clashes with too many class members in the code.
+template <class T>
+size_t ComputeHash(const T& value) {
+ return THash<T>{}(value);
+}
diff --git a/util/generic/hash.pxd b/util/generic/hash.pxd
new file mode 100644
index 0000000000..385c10d805
--- /dev/null
+++ b/util/generic/hash.pxd
@@ -0,0 +1,66 @@
+from libcpp.pair cimport pair
+
+cdef extern from "util/generic/hash.h" nogil:
+ cdef cppclass THashMap[T, U]:
+ cppclass iterator:
+ pair[T, U]& operator*()
+ iterator operator++()
+ iterator operator--()
+ bint operator==(iterator)
+ bint operator!=(iterator)
+
+ cppclass const_iterator(iterator):
+ pass
+
+ cppclass reverse_iterator:
+ pair[T, U]& operator*()
+ iterator operator++()
+ iterator operator--()
+ bint operator==(reverse_iterator)
+ bint operator!=(reverse_iterator)
+
+ cppclass const_reverse_iterator(reverse_iterator):
+ pass
+
+ THashMap() except +
+ THashMap(THashMap&) except +
+ U& operator[](T&)
+ THashMap& operator=(THashMap&)
+
+ bint operator==(THashMap&)
+ bint operator!=(THashMap&)
+ bint operator<(THashMap&)
+ bint operator>(THashMap&)
+ bint operator<=(THashMap&)
+ bint operator>=(THashMap&)
+
+ U& at(T&) except +
+ iterator begin()
+ const_iterator const_begin "begin"()
+ void clear()
+ size_t count(T&)
+ bint empty()
+ iterator end()
+ const_iterator const_end "end"()
+ pair[iterator, iterator] equal_range(T&)
+ void erase(iterator) except +
+ void erase(iterator, iterator) except +
+ size_t erase(T&)
+ iterator find(T&)
+ bint contains(T&)
+ const_iterator const_find "find"(T&)
+ pair[iterator, bint] insert(pair[T, U]) # XXX pair[T,U]&
+ iterator insert(iterator, pair[T, U]) # XXX pair[T,U]&
+ size_t max_size()
+ size_t size()
+ void swap(THashMap&)
+ iterator lower_bound(T&)
+ const_iterator const_lower_bound "lower_bound"(T&)
+ reverse_iterator rbegin()
+ const_reverse_iterator const_rbegin "rbegin"()
+ reverse_iterator rend()
+ const_reverse_iterator const_rend "rend"()
+ iterator upper_bound(T&)
+ const_iterator const_upper_bound "upper_bound"(T&)
+ void max_load_factor(float)
+ float max_load_factor()
diff --git a/util/generic/hash_primes.cpp b/util/generic/hash_primes.cpp
new file mode 100644
index 0000000000..656d31e046
--- /dev/null
+++ b/util/generic/hash_primes.cpp
@@ -0,0 +1,121 @@
+#include "hash_primes.h"
+#include "array_size.h"
+#include "algorithm.h"
+
+/// Order of fields: reciprocal, reciprocal shift, adjacent hint, divisor
+#if defined(_32_)
+static constexpr ::NPrivate::THashDivisor PRIME_DIVISORS_HOLDER[]{
+ {0x00000000u, 0u, -1, 0xffffffffu}, // guard value, not a valid divisor
+ {0x24924925u, 2u, 0, 7u},
+ {0xe1e1e1e2u, 4u, 1, 17u},
+ {0x1a7b9612u, 4u, 2, 29u},
+ {0x3521cfb3u, 5u, 3, 53u},
+ {0x51d07eafu, 6u, 4, 97u},
+ {0x53909490u, 7u, 5, 193u},
+ {0x50f22e12u, 8u, 6, 389u},
+ {0x54e3b41au, 9u, 7, 769u},
+ {0x53c8eaeeu, 10u, 8, 1543u},
+ {0x548eacc6u, 11u, 9, 3079u},
+ {0x54f1e41eu, 12u, 10, 6151u},
+ {0x554e390au, 13u, 11, 12289u},
+ {0x5518ee41u, 14u, 12, 24593u},
+ {0x554c7203u, 15u, 13, 49157u},
+ {0x5549c781u, 16u, 14, 98317u},
+ {0x55531c76u, 17u, 15, 196613u},
+ {0x554fc734u, 18u, 16, 393241u},
+ {0x555538e4u, 19u, 17, 786433u},
+ {0x55550e39u, 20u, 18, 1572869u},
+ {0x5555071du, 21u, 19, 3145739u},
+ {0x5555271du, 22u, 20, 6291469u},
+ {0x55554c72u, 23u, 21, 12582917u},
+ {0x55554472u, 24u, 22, 25165843u},
+ {0x5555531du, 25u, 23, 50331653u},
+ {0x55555039u, 26u, 24, 100663319u},
+ {0x55555339u, 27u, 25, 201326611u},
+ {0x5555550fu, 28u, 26, 402653189u},
+ {0x555552ddu, 29u, 27, 805306457u},
+ {0x55555544u, 30u, 28, 1610612741u},
+ {0x55555554u, 31u, 29, 3221225473u},
+ {0x00000006u, 31u, 30, 4294967291u},
+};
+#else
+static constexpr ::NPrivate::THashDivisor PRIME_DIVISORS_HOLDER[]{
+ {0x0000000000000000ul, 0u, -1, 0xffffffffu}, // guard value, not a valid divisor
+ {0x2492492492492493ul, 2u, 0, 7u},
+ {0xe1e1e1e1e1e1e1e2ul, 4u, 1, 17u},
+ {0x1a7b9611a7b9611bul, 4u, 2, 29u},
+ {0x3521cfb2b78c1353ul, 5u, 3, 53u},
+ {0x51d07eae2f8151d1ul, 6u, 4, 97u},
+ {0x5390948f40feac70ul, 7u, 5, 193u},
+ {0x50f22e111c4c56dful, 8u, 6, 389u},
+ {0x54e3b4194ce65de1ul, 9u, 7, 769u},
+ {0x53c8eaedea6e7f17ul, 10u, 8, 1543u},
+ {0x548eacc5e1e6e3fcul, 11u, 9, 3079u},
+ {0x54f1e41d7767d70cul, 12u, 10, 6151u},
+ {0x554e39097a781d80ul, 13u, 11, 12289u},
+ {0x5518ee4079ea6929ul, 14u, 12, 24593u},
+ {0x554c72025d459231ul, 15u, 13, 49157u},
+ {0x5549c78094504ff3ul, 16u, 14, 98317u},
+ {0x55531c757b3c329cul, 17u, 15, 196613u},
+ {0x554fc7339753b424ul, 18u, 16, 393241u},
+ {0x555538e39097b3f4ul, 19u, 17, 786433u},
+ {0x55550e38f25ecd82ul, 20u, 18, 1572869u},
+ {0x5555071c83b421d2ul, 21u, 19, 3145739u},
+ {0x5555271c78097a6aul, 22u, 20, 6291469u},
+ {0x55554c71c757b425ul, 23u, 21, 12582917u},
+ {0x55554471c7f25ec7ul, 24u, 22, 25165843u},
+ {0x5555531c71cad098ul, 25u, 23, 50331653u},
+ {0x55555038e3a1d098ul, 26u, 24, 100663319u},
+ {0x55555338e3919098ul, 27u, 25, 201326611u},
+ {0x5555550e38e39d0aul, 28u, 26, 402653189u},
+ {0x555552dc71cbb1eeul, 29u, 27, 805306457u},
+ {0x555555438e38e47cul, 30u, 28, 1610612741u},
+ {0x555555538e38e391ul, 31u, 29, 3221225473u},
+ {0x000000050000001aul, 31u, 30, 4294967291u},
+};
+#endif
+
+static constexpr const ::NPrivate::THashDivisor* PRIME_DIVISORS = &PRIME_DIVISORS_HOLDER[1]; ///< Address of the first valid divisor
+static constexpr size_t PRIME_DIVISORS_SIZE = Y_ARRAY_SIZE(PRIME_DIVISORS_HOLDER) - 1; ///< Number of valid divisors without the guarding value
+
+unsigned long HashBucketCount(unsigned long elementCount) {
+ return HashBucketCountExt(elementCount)();
+}
+
+static inline ::NPrivate::THashDivisor HashBucketBoundedSearch(unsigned long elementCount) {
+ const auto begin = PRIME_DIVISORS;
+ const auto end = PRIME_DIVISORS + PRIME_DIVISORS_SIZE - 1; // adjust range so the last element will be returned if elementCount is bigger than all PRIME_DIVISORS
+ return *LowerBoundBy(begin, end, elementCount, std::mem_fn(&::NPrivate::THashDivisor::Divisor));
+}
+
+Y_CONST_FUNCTION
+::NPrivate::THashDivisor HashBucketCountExt(unsigned long elementCount) {
+ if (elementCount <= PRIME_DIVISORS[0]()) {
+ return PRIME_DIVISORS[0];
+ }
+
+ return HashBucketBoundedSearch(elementCount);
+}
+
+Y_CONST_FUNCTION
+::NPrivate::THashDivisor HashBucketCountExt(unsigned long elementCount, int hint) {
+ if (Y_LIKELY(static_cast<size_t>(hint) < PRIME_DIVISORS_SIZE)) {
+ const int index = hint;
+ const ::NPrivate::THashDivisor* cnd = PRIME_DIVISORS + index;
+ if (Y_LIKELY(elementCount <= cnd->Divisor)) {
+ const ::NPrivate::THashDivisor* prev = cnd - 1;
+ static_assert(~PRIME_DIVISORS[-1].Divisor == 0, "Invalid guard");
+ /*
+ If index == 0 then PRIME_DIVISORS[0] should be returned.
+ Otherwise `cnd` is correct value iff (prev->Divisor < elementCount).
+ Ergo hint is correct if (index == 0 || prev->Divisor < elementCount);
+ But we can set guard's value to -1 and check both conditions at once.
+ */
+ if (Y_LIKELY(prev->Divisor + 1u <= elementCount)) {
+ return *cnd;
+ }
+ }
+ }
+
+ return HashBucketBoundedSearch(elementCount);
+}
diff --git a/util/generic/hash_primes.h b/util/generic/hash_primes.h
new file mode 100644
index 0000000000..4dc2da0b8f
--- /dev/null
+++ b/util/generic/hash_primes.h
@@ -0,0 +1,138 @@
+#pragma once
+
+#include <util/system/compiler.h>
+#include <util/system/types.h>
+
+#if defined(_MSC_VER) && defined(_M_X64)
+ #include <intrin.h>
+#endif
+
+/**
+ * Calculates the number of buckets for the hash table that will hold the given
+ * number of elements.
+ *
+ * @param elementCount Number of elements that the hash table will hold.
+ * @returns Number of buckets, a prime number that is
+ * greater or equal to `elementCount`.
+ */
+Y_CONST_FUNCTION
+unsigned long HashBucketCount(unsigned long elementCount);
+
+namespace NPrivate {
+
+ /// Implementation of algorithm 4.1 from: Torbjörn Granlund and Peter L. Montgomery. 1994. Division by invariant integers using multiplication.
+ /// @see https://gmplib.org/~tege/divcnst-pldi94.pdf
+ template <typename TDivisor, typename TDividend, typename MulUnsignedUpper>
+ class TReciprocalDivisor {
+ static_assert(sizeof(TDivisor) <= sizeof(TDividend), "TDivisor and TDividend should have the same size");
+
+ public:
+ constexpr TReciprocalDivisor() noexcept = default;
+
+ constexpr TReciprocalDivisor(TDividend reciprocal, ui8 reciprocalShift, i8 hint, TDivisor divisor) noexcept
+ : Reciprocal(reciprocal)
+ , Divisor(divisor)
+ , ReciprocalShift(reciprocalShift)
+ , Hint(hint)
+ {
+ }
+
+ /// Return (dividend % Divisor)
+ Y_FORCE_INLINE TDividend Remainder(TDividend dividend) const noexcept {
+ if (Y_UNLIKELY(Divisor == 1)) {
+ return 0;
+ }
+ TDividend r = dividend - Quotent(dividend) * Divisor;
+ return r;
+ }
+
+ Y_FORCE_INLINE TDivisor operator()() const noexcept {
+ return Divisor;
+ }
+
+ Y_FORCE_INLINE static constexpr TReciprocalDivisor One() noexcept {
+ return {1u, 0u, -1, 1u};
+ }
+
+ private:
+ /// returns (dividend / Divisor)
+ Y_FORCE_INLINE TDividend Quotent(TDividend dividend) const noexcept {
+ const TDividend t = MulUnsignedUpper{}(dividend, Reciprocal);
+ const TDividend q = (t + ((dividend - t) >> 1)) >> ReciprocalShift;
+ return q;
+ }
+
+ public:
+ TDividend Reciprocal = 0;
+ TDivisor Divisor = 0;
+ ui8 ReciprocalShift = 0;
+ i8 Hint = 0; ///< Additional data: needless for division, but useful for the adjacent divisors search
+ };
+
+ template <typename T, typename TExtended, size_t shift>
+ struct TMulUnsignedUpper {
+ /// Return the high bits of the product of two unsigned integers.
+ Y_FORCE_INLINE T operator()(T a, T b) const noexcept {
+ return (static_cast<TExtended>(a) * static_cast<TExtended>(b)) >> shift;
+ }
+ };
+
+#if defined(_32_)
+ using THashDivisor = ::NPrivate::TReciprocalDivisor<ui32, ui32, TMulUnsignedUpper<ui32, ui64, 32>>;
+#else
+ #if defined(Y_HAVE_INT128)
+ using THashDivisor = ::NPrivate::TReciprocalDivisor<ui32, ui64, TMulUnsignedUpper<ui64, unsigned __int128, 64>>;
+ #elif defined(_MSC_VER) && defined(_M_X64)
+ struct TMulUnsignedUpperVCIntrin {
+ /// Return the high 64 bits of the product of two 64-bit unsigned integers.
+ Y_FORCE_INLINE ui64 operator()(ui64 a, ui64 b) const noexcept {
+ return __umulh(a, b);
+ }
+ };
+ using THashDivisor = ::NPrivate::TReciprocalDivisor<ui32, ui64, TMulUnsignedUpperVCIntrin>;
+ #else
+ template <typename TDivisor, typename TDividend>
+ class TNaiveDivisor {
+ public:
+ constexpr TNaiveDivisor() noexcept = default;
+
+ constexpr TNaiveDivisor(TDivisor divisor) noexcept
+ : Divisor(divisor)
+ {
+ }
+
+ constexpr TNaiveDivisor(TDividend reciprocal, ui8 reciprocalShift, i8 hint, TDivisor divisor) noexcept
+ : TNaiveDivisor(divisor)
+ {
+ Y_UNUSED(reciprocal);
+ Y_UNUSED(reciprocalShift);
+ Y_UNUSED(hint);
+ }
+
+ Y_FORCE_INLINE TDividend Remainder(TDividend dividend) const noexcept {
+ return dividend % Divisor;
+ }
+
+ Y_FORCE_INLINE TDivisor operator()() const noexcept {
+ return Divisor;
+ }
+
+ Y_FORCE_INLINE static constexpr TNaiveDivisor One() noexcept {
+ return {1u};
+ }
+
+ public:
+ TDivisor Divisor = 0;
+ static constexpr i8 Hint = -1;
+ };
+
+ using THashDivisor = ::NPrivate::TNaiveDivisor<ui32, ui64>;
+ #endif
+#endif
+}
+
+Y_CONST_FUNCTION
+::NPrivate::THashDivisor HashBucketCountExt(unsigned long elementCount);
+
+Y_CONST_FUNCTION
+::NPrivate::THashDivisor HashBucketCountExt(unsigned long elementCount, int hint);
diff --git a/util/generic/hash_primes_ut.cpp b/util/generic/hash_primes_ut.cpp
new file mode 100644
index 0000000000..7b5bf8b5c9
--- /dev/null
+++ b/util/generic/hash_primes_ut.cpp
@@ -0,0 +1,80 @@
+#include "hash_primes.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/vector.h>
+#include <util/string/builder.h>
+#include <util/random/fast.h>
+
+Y_UNIT_TEST_SUITE(TestHashPrimes) {
+ Y_UNIT_TEST(Test1) {
+ UNIT_ASSERT_VALUES_EQUAL(HashBucketCount(1), 7);
+ UNIT_ASSERT_VALUES_EQUAL(HashBucketCount(6), 7);
+ UNIT_ASSERT_VALUES_EQUAL(HashBucketCount(7), 7);
+ UNIT_ASSERT_VALUES_EQUAL(HashBucketCount(8), 17);
+ }
+
+ static TVector<size_t> Numbers() {
+ TVector<size_t> numbers;
+
+ TFastRng64 rng{961923};
+ size_t k = 1;
+ for (size_t j = 0; j < 8000; ++j) {
+ numbers.push_back(rng.GenRand());
+ numbers.push_back(k *= 57);
+ }
+ for (size_t p = 1; p != 0; p <<= 1) {
+ for (size_t offset : {-2, -1, 0, 1, 2}) {
+ numbers.push_back(p + offset);
+ }
+ }
+ return numbers;
+ }
+
+ static TVector<size_t> Divisors() {
+ TVector<size_t> divisors;
+ divisors.push_back(HashBucketCountExt(0)());
+ for (;;) {
+ const size_t prevSize = divisors.back();
+ const size_t nextSize = HashBucketCountExt(prevSize + 1)();
+ if (nextSize <= prevSize) {
+ break;
+ }
+ divisors.push_back(nextSize);
+ }
+ return divisors;
+ }
+
+ Y_UNIT_TEST(Remainder) {
+ const TVector<size_t> numbers = Numbers();
+ const TVector<size_t> divisors = Divisors();
+
+ auto testDivisor = [&](const auto& c) {
+ for (size_t n : numbers) {
+ UNIT_ASSERT_VALUES_EQUAL_C(n % c(), c.Remainder(n), (TStringBuilder() << "n=" << n << "; d=" << c()));
+ }
+ };
+
+ for (size_t d : divisors) {
+ const auto c = HashBucketCountExt(d);
+ UNIT_ASSERT_VALUES_EQUAL_C(d, c(), (TStringBuilder() << "d=" << d));
+ testDivisor(c);
+ }
+ testDivisor(::NPrivate::THashDivisor::One());
+ }
+
+ Y_UNIT_TEST(MisleadingHints) {
+ TFastRng64 rng{332142};
+ TVector<size_t> cases = Numbers();
+ for (size_t d : Divisors()) {
+ cases.push_back(d);
+ }
+
+ for (size_t c : cases) {
+ for (size_t reps = 0; reps < 3; ++reps) {
+ const i8 hint = rng.Uniform(256) - 128;
+ UNIT_ASSERT_VALUES_EQUAL_C(HashBucketCountExt(c)(), HashBucketCountExt(c, hint)(), (TStringBuilder() << "c=" << c << "; hint=" << hint));
+ }
+ }
+ }
+}
diff --git a/util/generic/hash_set.cpp b/util/generic/hash_set.cpp
new file mode 100644
index 0000000000..8fabeeabd8
--- /dev/null
+++ b/util/generic/hash_set.cpp
@@ -0,0 +1 @@
+#include "hash_set.h"
diff --git a/util/generic/hash_set.h b/util/generic/hash_set.h
new file mode 100644
index 0000000000..e8088cf23b
--- /dev/null
+++ b/util/generic/hash_set.h
@@ -0,0 +1,488 @@
+#pragma once
+
+#include "fwd.h"
+#include "hash.h"
+
+#include <initializer_list>
+#include <utility>
+
+#undef value_type
+
+template <class Value, class HashFcn, class EqualKey, class Alloc>
+class THashSet {
+private:
+ using ht = THashTable<Value, Value, HashFcn, ::TIdentity, EqualKey, Alloc>;
+ ht rep;
+
+ using mutable_iterator = typename ht::iterator;
+
+public:
+ using key_type = typename ht::key_type;
+ using value_type = typename ht::value_type;
+ using hasher = typename ht::hasher;
+ using key_equal = typename ht::key_equal;
+ using allocator_type = typename ht::allocator_type;
+ using node_allocator_type = typename ht::node_allocator_type;
+
+ using size_type = typename ht::size_type;
+ using difference_type = typename ht::difference_type;
+ using pointer = typename ht::const_pointer;
+ using const_pointer = typename ht::const_pointer;
+ using reference = typename ht::const_reference;
+ using const_reference = typename ht::const_reference;
+
+ using iterator = typename ht::const_iterator;
+ using const_iterator = typename ht::const_iterator;
+ using insert_ctx = typename ht::insert_ctx;
+
+ hasher hash_function() const {
+ return rep.hash_function();
+ }
+ key_equal key_eq() const {
+ return rep.key_eq();
+ }
+
+public:
+ THashSet() {
+ }
+ template <class TT>
+ explicit THashSet(TT* allocParam, size_type n = 0)
+ : rep(n, hasher(), key_equal(), allocParam)
+ {
+ }
+ explicit THashSet(size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ }
+ THashSet(size_type n, const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ }
+ THashSet(size_type n, const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ }
+
+ THashSet(std::initializer_list<value_type> list)
+ : rep(list.size(), hasher(), key_equal())
+ {
+ rep.insert_unique(list.begin(), list.end());
+ }
+ THashSet(std::initializer_list<value_type> list, size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ rep.insert_unique(list.begin(), list.end());
+ }
+ THashSet(std::initializer_list<value_type> list, size_type n, const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ rep.insert_unique(list.begin(), list.end());
+ }
+ THashSet(std::initializer_list<value_type> list, size_type n, const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ rep.insert_unique(list.begin(), list.end());
+ }
+
+ template <class InputIterator>
+ THashSet(InputIterator f, InputIterator l)
+ : rep(0, hasher(), key_equal())
+ {
+ rep.insert_unique(f, l);
+ }
+ template <class InputIterator>
+ THashSet(InputIterator f, InputIterator l, size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ rep.insert_unique(f, l);
+ }
+ template <class InputIterator>
+ THashSet(InputIterator f, InputIterator l, size_type n,
+ const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ rep.insert_unique(f, l);
+ }
+ template <class InputIterator>
+ THashSet(InputIterator f, InputIterator l, size_type n,
+ const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ rep.insert_unique(f, l);
+ }
+
+ // THashSet has implicit copy/move constructors and copy-/move-assignment operators
+ // because its implementation is backed by THashTable.
+ // See hash_ut.cpp
+
+public:
+ size_type size() const {
+ return rep.size();
+ }
+ size_type max_size() const {
+ return rep.max_size();
+ }
+
+ Y_PURE_FUNCTION bool empty() const {
+ return rep.empty();
+ }
+ explicit operator bool() const noexcept {
+ return !empty();
+ }
+ void swap(THashSet& hs) {
+ rep.swap(hs.rep);
+ }
+
+ iterator begin() const {
+ return rep.begin();
+ }
+ iterator end() const {
+ return rep.end();
+ }
+ iterator cbegin() const {
+ return rep.begin();
+ }
+ iterator cend() const {
+ return rep.end();
+ }
+
+public:
+ void insert(std::initializer_list<value_type> ilist) {
+ insert(ilist.begin(), ilist.end());
+ }
+
+ template <class InputIterator>
+ void insert(InputIterator f, InputIterator l) {
+ rep.insert_unique(f, l);
+ }
+
+ std::pair<iterator, bool> insert(const value_type& obj) {
+ std::pair<mutable_iterator, bool> p = rep.insert_unique(obj);
+ return std::pair<iterator, bool>(p.first, p.second);
+ }
+ template <typename... Args>
+ std::pair<iterator, bool> emplace(Args&&... args) {
+ std::pair<mutable_iterator, bool> p = rep.emplace_unique(std::forward<Args>(args)...);
+ return std::pair<iterator, bool>(p.first, p.second);
+ }
+
+ iterator insert(const_iterator, const value_type& obj) { // insert_hint
+ std::pair<mutable_iterator, bool> p = rep.insert_unique(obj);
+ return p.first;
+ }
+
+ std::pair<iterator, bool> insert_noresize(const value_type& obj) {
+ std::pair<mutable_iterator, bool> p = rep.insert_unique_noresize(obj);
+ return std::pair<iterator, bool>(p.first, p.second);
+ }
+ template <typename... Args>
+ std::pair<iterator, bool> emplace_noresize(Args&&... args) {
+ std::pair<mutable_iterator, bool> p = rep.emplace_unique_noresize(std::forward<Args>(args)...);
+ return std::pair<iterator, bool>(p.first, p.second);
+ }
+
+ template <class TheObj>
+ iterator insert_direct(const TheObj& obj, const insert_ctx& ins) {
+ return rep.insert_direct(obj, ins);
+ }
+ template <typename... Args>
+ iterator emplace_direct(const insert_ctx& ins, Args&&... args) {
+ return rep.emplace_direct(ins, std::forward<Args>(args)...);
+ }
+
+ template <class TheKey>
+ const_iterator find(const TheKey& key) const {
+ return rep.find(key);
+ }
+ template <class TheKey>
+ iterator find(const TheKey& key, insert_ctx& ins) {
+ return rep.find_i(key, ins);
+ }
+
+ template <class TheKey>
+ bool contains(const TheKey& key) const {
+ return rep.find(key) != rep.end();
+ }
+ template <class TheKey>
+ bool contains(const TheKey& key, insert_ctx& ins) {
+ return rep.find_i(key, ins) != rep.end();
+ }
+
+ template <class TKey>
+ size_type count(const TKey& key) const {
+ return rep.count(key);
+ }
+
+ template <class TKey>
+ std::pair<iterator, iterator> equal_range(const TKey& key) const {
+ return rep.equal_range(key);
+ }
+
+ size_type erase(const key_type& key) {
+ return rep.erase(key);
+ }
+ void erase(iterator it) {
+ rep.erase(it);
+ }
+ void erase(iterator f, iterator l) {
+ rep.erase(f, l);
+ }
+ void clear() {
+ rep.clear();
+ }
+ void clear(size_t downsize_hint) {
+ rep.clear(downsize_hint);
+ }
+ void basic_clear() {
+ rep.basic_clear();
+ }
+ void release_nodes() {
+ rep.release_nodes();
+ }
+
+ template <class KeySaver>
+ int save_for_st(IOutputStream* stream, KeySaver& ks) const {
+ return rep.template save_for_st<KeySaver>(stream, ks);
+ }
+
+public:
+ void reserve(size_type hint) {
+ rep.reserve(hint);
+ }
+ size_type bucket_count() const {
+ return rep.bucket_count();
+ }
+ size_type bucket_size(size_type n) const {
+ return rep.bucket_size(n);
+ }
+ node_allocator_type& GetNodeAllocator() {
+ return rep.GetNodeAllocator();
+ }
+};
+
+template <class Value, class HashFcn, class EqualKey, class Alloc>
+inline bool operator==(const THashSet<Value, HashFcn, EqualKey, Alloc>& hs1, const THashSet<Value, HashFcn, EqualKey, Alloc>& hs2) {
+ if (hs1.size() != hs2.size()) {
+ return false;
+ }
+ for (const auto& it : hs1) {
+ if (!hs2.contains(it)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class Value, class HashFcn, class EqualKey, class Alloc>
+inline bool operator!=(const THashSet<Value, HashFcn, EqualKey, Alloc>& hs1, const THashSet<Value, HashFcn, EqualKey, Alloc>& hs2) {
+ return !(hs1 == hs2);
+}
+
+template <class Value, class HashFcn, class EqualKey, class Alloc>
+class THashMultiSet {
+private:
+ using ht = THashTable<Value, Value, HashFcn, ::TIdentity, EqualKey, Alloc>;
+ ht rep;
+
+public:
+ using key_type = typename ht::key_type;
+ using value_type = typename ht::value_type;
+ using hasher = typename ht::hasher;
+ using key_equal = typename ht::key_equal;
+ using allocator_type = typename ht::allocator_type;
+ using node_allocator_type = typename ht::node_allocator_type;
+
+ using size_type = typename ht::size_type;
+ using difference_type = typename ht::difference_type;
+ using pointer = typename ht::const_pointer;
+ using const_pointer = typename ht::const_pointer;
+ using reference = typename ht::const_reference;
+ using const_reference = typename ht::const_reference;
+
+ using iterator = typename ht::const_iterator;
+ using const_iterator = typename ht::const_iterator;
+
+ hasher hash_function() const {
+ return rep.hash_function();
+ }
+ key_equal key_eq() const {
+ return rep.key_eq();
+ }
+
+public:
+ THashMultiSet()
+ : rep(0, hasher(), key_equal())
+ {
+ }
+ explicit THashMultiSet(size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ }
+ THashMultiSet(size_type n, const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ }
+ THashMultiSet(size_type n, const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ }
+
+ template <class InputIterator>
+ THashMultiSet(InputIterator f, InputIterator l)
+ : rep(0, hasher(), key_equal())
+ {
+ rep.insert_equal(f, l);
+ }
+ template <class InputIterator>
+ THashMultiSet(InputIterator f, InputIterator l, size_type n)
+ : rep(n, hasher(), key_equal())
+ {
+ rep.insert_equal(f, l);
+ }
+ template <class InputIterator>
+ THashMultiSet(InputIterator f, InputIterator l, size_type n,
+ const hasher& hf)
+ : rep(n, hf, key_equal())
+ {
+ rep.insert_equal(f, l);
+ }
+ template <class InputIterator>
+ THashMultiSet(InputIterator f, InputIterator l, size_type n,
+ const hasher& hf, const key_equal& eql)
+ : rep(n, hf, eql)
+ {
+ rep.insert_equal(f, l);
+ }
+
+ THashMultiSet(std::initializer_list<value_type> list)
+ : rep(list.size(), hasher(), key_equal())
+ {
+ rep.insert_equal(list.begin(), list.end());
+ }
+
+ // THashMultiSet has implicit copy/move constructors and copy-/move-assignment operators
+ // because its implementation is backed by THashTable.
+ // See hash_ut.cpp
+
+public:
+ size_type size() const {
+ return rep.size();
+ }
+ size_type max_size() const {
+ return rep.max_size();
+ }
+
+ Y_PURE_FUNCTION bool empty() const {
+ return rep.empty();
+ }
+ explicit operator bool() const noexcept {
+ return !empty();
+ }
+ void swap(THashMultiSet& hs) {
+ rep.swap(hs.rep);
+ }
+
+ iterator begin() const {
+ return rep.begin();
+ }
+ iterator end() const {
+ return rep.end();
+ }
+ iterator cbegin() const {
+ return rep.begin();
+ }
+ iterator cend() const {
+ return rep.end();
+ }
+
+public:
+ iterator insert(const value_type& obj) {
+ return rep.insert_equal(obj);
+ }
+ template <typename... Args>
+ iterator emplace(Args&&... args) {
+ return rep.emplace_equal(std::forward<Args>(args)...);
+ }
+ template <class InputIterator>
+ void insert(InputIterator f, InputIterator l) {
+ rep.insert_equal(f, l);
+ }
+ iterator insert_noresize(const value_type& obj) {
+ return rep.insert_equal_noresize(obj);
+ }
+
+ template <class TKey>
+ iterator find(const TKey& key) const {
+ return rep.find(key);
+ }
+
+ template <class TKey>
+ size_type count(const TKey& key) const {
+ return rep.count(key);
+ }
+
+ template <class TKey>
+ std::pair<iterator, iterator> equal_range(const TKey& key) const {
+ return rep.equal_range(key);
+ }
+
+ size_type erase(const key_type& key) {
+ return rep.erase(key);
+ }
+ void erase(iterator it) {
+ rep.erase(it);
+ }
+ void erase(iterator f, iterator l) {
+ rep.erase(f, l);
+ }
+ void clear() {
+ rep.clear();
+ }
+ void clear(size_t downsize_hint) {
+ rep.clear(downsize_hint);
+ }
+ void basic_clear() {
+ rep.basic_clear();
+ }
+ void release_nodes() {
+ rep.release_nodes();
+ }
+
+public:
+ void reserve(size_type hint) {
+ rep.reserve(hint);
+ }
+ size_type bucket_count() const {
+ return rep.bucket_count();
+ }
+ size_type bucket_size(size_type n) const {
+ return rep.bucket_size(n);
+ }
+ node_allocator_type& GetNodeAllocator() {
+ return rep.GetNodeAllocator();
+ }
+};
+
+template <class Val, class HashFcn, class EqualKey, class Alloc>
+inline bool operator==(const THashMultiSet<Val, HashFcn, EqualKey, Alloc>& hs1, const THashMultiSet<Val, HashFcn, EqualKey, Alloc>& hs2) {
+ if (hs1.size() != hs2.size()) {
+ return false;
+ }
+ EqualKey equalKey;
+ auto it = hs1.begin();
+ while (it != hs1.end()) {
+ const auto& value = *it;
+ size_t count = 0;
+ for (; (it != hs1.end()) && (equalKey(*it, value)); ++it, ++count) {
+ }
+ if (hs2.count(value) != count) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class Val, class HashFcn, class EqualKey, class Alloc>
+inline bool operator!=(const THashMultiSet<Val, HashFcn, EqualKey, Alloc>& hs1, const THashMultiSet<Val, HashFcn, EqualKey, Alloc>& hs2) {
+ return !(hs1 == hs2);
+}
diff --git a/util/generic/hash_set.pxd b/util/generic/hash_set.pxd
new file mode 100644
index 0000000000..d28d90cbe7
--- /dev/null
+++ b/util/generic/hash_set.pxd
@@ -0,0 +1,43 @@
+from libcpp.pair cimport pair
+
+cdef extern from "util/generic/hash_set.h" nogil:
+ cdef cppclass THashSet[T]:
+ cppclass iterator:
+ T& operator*()
+ iterator operator++()
+ iterator operator--()
+ bint operator==(iterator)
+ bint operator!=(iterator)
+
+ cppclass const_iterator(iterator):
+ pass
+
+ THashSet() except +
+ THashSet(THashSet&) except +
+ THashSet(T* t) except +
+ THashSet& operator=(THashSet&)
+
+ bint operator==(THashSet&)
+ bint operator!=(THashSet&)
+ bint operator<(THashSet&)
+ bint operator>(THashSet&)
+ bint operator<=(THashSet&)
+ bint operator>=(THashSet&)
+
+ iterator begin()
+ const_iterator const_begin "begin"()
+ void clear()
+ size_t count(T&)
+ bint empty()
+ iterator end()
+ const_iterator const_end "end"()
+ void erase(iterator) except +
+ void erase(iterator, iterator) except +
+ size_t erase(T&)
+ iterator find(T&)
+ bint contains(T&)
+ const_iterator const_find "find"(T&)
+ pair[iterator, bint] insert(T)
+ iterator insert(iterator, T)
+ size_t size()
+ void swap(THashSet&)
diff --git a/util/generic/hash_set_ut.pyx b/util/generic/hash_set_ut.pyx
new file mode 100644
index 0000000000..bdcf6284af
--- /dev/null
+++ b/util/generic/hash_set_ut.pyx
@@ -0,0 +1,53 @@
+# cython: c_string_type=str, c_string_encoding=utf8
+
+from util.generic.hash_set cimport THashSet
+from util.generic.string cimport TString
+
+import pytest
+import unittest
+
+from cython.operator cimport dereference as deref
+
+
+class TestHashSet(unittest.TestCase):
+
+ def test_simple_constructor_equality_operator(self):
+ cdef THashSet[int] c1
+ c1.insert(1)
+ assert c1.size() == 1
+ c1.insert(2)
+ c1.insert(2)
+ c1.insert(2)
+ c1.insert(2)
+ assert c1.size() == 2
+ assert c1.contains(2)
+ assert not c1.contains(5)
+ cdef THashSet[int] c2 = c1
+ assert c1 == c2
+ c1.insert(3)
+ assert c1 != c2
+ c1.erase(3)
+ assert c1 == c2
+
+ def test_insert_erase(self):
+ cdef THashSet[TString] tmp
+ self.assertTrue(tmp.insert("one").second)
+ self.assertFalse(tmp.insert("one").second)
+ self.assertTrue(tmp.insert("two").second)
+ cdef TString one = "one"
+ cdef TString two = "two"
+ self.assertEqual(tmp.erase(one), 1)
+ self.assertEqual(tmp.erase(two), 1)
+ self.assertEqual(tmp.size(), 0)
+ self.assertTrue(tmp.empty())
+
+ def test_iterators_and_find(self):
+ cdef THashSet[TString] tmp
+ self.assertTrue(tmp.begin() == tmp.end())
+ self.assertTrue(tmp.find("1") == tmp.end())
+ tmp.insert("1")
+ self.assertTrue(tmp.begin() != tmp.end())
+ cdef THashSet[TString].iterator it = tmp.find("1")
+ self.assertTrue(it != tmp.end())
+ self.assertEqual(deref(it), "1")
+
diff --git a/util/generic/hash_ut.cpp b/util/generic/hash_ut.cpp
new file mode 100644
index 0000000000..0551d58770
--- /dev/null
+++ b/util/generic/hash_ut.cpp
@@ -0,0 +1,1271 @@
+#include "hash.h"
+#include "vector.h"
+#include "hash_set.h"
+
+#include <library/cpp/testing/common/probe.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <utility>
+#include <util/str_stl.h>
+#include <util/digest/multi.h>
+
+static const char star = 42;
+
+class THashTest: public TTestBase {
+ UNIT_TEST_SUITE(THashTest);
+ UNIT_TEST(TestHMapConstructorsAndAssignments);
+ UNIT_TEST(TestHMap1);
+ UNIT_TEST(TestHMapEqualityOperator);
+ UNIT_TEST(TestHMMapEqualityOperator);
+ UNIT_TEST(TestHMMapConstructorsAndAssignments);
+ UNIT_TEST(TestHMMap1);
+ UNIT_TEST(TestHMMapHas);
+ UNIT_TEST(TestHSetConstructorsAndAssignments);
+ UNIT_TEST(TestHSetSize);
+ UNIT_TEST(TestHSet2);
+ UNIT_TEST(TestHSetEqualityOperator);
+ UNIT_TEST(TestHMSetConstructorsAndAssignments);
+ UNIT_TEST(TestHMSetSize);
+ UNIT_TEST(TestHMSet1);
+ UNIT_TEST(TestHMSetEqualityOperator);
+ UNIT_TEST(TestHMSetEmplace);
+ UNIT_TEST(TestInsertErase);
+ UNIT_TEST(TestResizeOnInsertSmartPtrBug)
+ UNIT_TEST(TestEmpty);
+ UNIT_TEST(TestDefaultConstructor);
+ UNIT_TEST(TestSizeOf);
+ UNIT_TEST(TestInvariants);
+ UNIT_TEST(TestAllocation);
+ UNIT_TEST(TestInsertCopy);
+ UNIT_TEST(TestEmplace);
+ UNIT_TEST(TestEmplaceNoresize);
+ UNIT_TEST(TestEmplaceDirect);
+ UNIT_TEST(TestTryEmplace);
+ UNIT_TEST(TestTryEmplaceCopyKey);
+ UNIT_TEST(TestHMMapEmplace);
+ UNIT_TEST(TestHMMapEmplaceNoresize);
+ UNIT_TEST(TestHMMapEmplaceDirect);
+ UNIT_TEST(TestHSetEmplace);
+ UNIT_TEST(TestHSetEmplaceNoresize);
+ UNIT_TEST(TestHSetEmplaceDirect);
+ UNIT_TEST(TestNonCopyable);
+ UNIT_TEST(TestValueInitialization);
+ UNIT_TEST(TestAssignmentClear);
+ UNIT_TEST(TestReleaseNodes);
+ UNIT_TEST(TestAt);
+ UNIT_TEST(TestHMapInitializerList);
+ UNIT_TEST(TestHMMapInitializerList);
+ UNIT_TEST(TestHSetInitializerList);
+ UNIT_TEST(TestHMSetInitializerList);
+ UNIT_TEST(TestHSetInsertInitializerList);
+ UNIT_TEST(TestTupleHash);
+ UNIT_TEST_SUITE_END();
+
+ using hmset = THashMultiSet<char, hash<char>, TEqualTo<char>>;
+
+protected:
+ void TestHMapConstructorsAndAssignments();
+ void TestHMap1();
+ void TestHMapEqualityOperator();
+ void TestHMMapEqualityOperator();
+ void TestHMMapConstructorsAndAssignments();
+ void TestHMMap1();
+ void TestHMMapHas();
+ void TestHSetConstructorsAndAssignments();
+ void TestHSetSize();
+ void TestHSet2();
+ void TestHSetEqualityOperator();
+ void TestHMSetConstructorsAndAssignments();
+ void TestHMSetSize();
+ void TestHMSet1();
+ void TestHMSetEqualityOperator();
+ void TestHMSetEmplace();
+ void TestInsertErase();
+ void TestResizeOnInsertSmartPtrBug();
+ void TestEmpty();
+ void TestDefaultConstructor();
+ void TestSizeOf();
+ void TestInvariants();
+ void TestAllocation();
+ void TestInsertCopy();
+ void TestEmplace();
+ void TestEmplaceNoresize();
+ void TestEmplaceDirect();
+ void TestTryEmplace();
+ void TestTryEmplaceCopyKey();
+ void TestHSetEmplace();
+ void TestHSetEmplaceNoresize();
+ void TestHSetEmplaceDirect();
+ void TestHMMapEmplace();
+ void TestHMMapEmplaceNoresize();
+ void TestHMMapEmplaceDirect();
+ void TestNonCopyable();
+ void TestValueInitialization();
+ void TestAssignmentClear();
+ void TestReleaseNodes();
+ void TestAt();
+ void TestHMapInitializerList();
+ void TestHMMapInitializerList();
+ void TestHSetInitializerList();
+ void TestHMSetInitializerList();
+ void TestHSetInsertInitializerList();
+ void TestTupleHash();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(THashTest);
+
+void THashTest::TestHMapConstructorsAndAssignments() {
+ using container = THashMap<TString, int>;
+
+ container c1;
+ c1["one"] = 1;
+ c1["two"] = 2;
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(1, c1.at("one")); /* Note: fails under MSVC since it does not support implicit generation of move constructors. */
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.at("two"));
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(1, c3.at("one"));
+
+ c2["three"] = 3;
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.at("three"));
+
+ c2["four"] = 4;
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.at("four"));
+
+ const container c4{
+ {"one", 1},
+ {"two", 2},
+ {"three", 3},
+ {"four", 4},
+ };
+
+ UNIT_ASSERT_VALUES_EQUAL(4, c4.size());
+ UNIT_ASSERT_VALUES_EQUAL(1, c4.at("one"));
+ UNIT_ASSERT_VALUES_EQUAL(2, c4.at("two"));
+ UNIT_ASSERT_VALUES_EQUAL(3, c4.at("three"));
+ UNIT_ASSERT_VALUES_EQUAL(4, c4.at("four"));
+
+ // non-existent values must be zero-initialized
+ UNIT_ASSERT_VALUES_EQUAL(c1["nonexistent"], 0);
+}
+
+void THashTest::TestHMap1() {
+ using maptype = THashMap<char, TString, THash<char>, TEqualTo<char>>;
+ maptype m;
+ // Store mappings between roman numerals and decimals.
+ m['l'] = "50";
+ m['x'] = "20"; // Deliberate mistake.
+ m['v'] = "5";
+ m['i'] = "1";
+ UNIT_ASSERT(!strcmp(m['x'].c_str(), "20"));
+ m['x'] = "10"; // Correct mistake.
+ UNIT_ASSERT(!strcmp(m['x'].c_str(), "10"));
+
+ UNIT_ASSERT(!m.contains('z'));
+ UNIT_ASSERT(!strcmp(m['z'].c_str(), ""));
+ UNIT_ASSERT(m.contains('z'));
+
+ UNIT_ASSERT(m.count('z') == 1);
+ auto p = m.insert(std::pair<const char, TString>('c', TString("100")));
+
+ UNIT_ASSERT(p.second);
+
+ p = m.insert(std::pair<const char, TString>('c', TString("100")));
+ UNIT_ASSERT(!p.second);
+
+ //Some iterators compare check, really compile time checks
+ maptype::iterator ite(m.begin());
+ maptype::const_iterator cite(m.begin());
+ cite = m.begin();
+ maptype const& cm = m;
+ cite = cm.begin();
+
+ UNIT_ASSERT((maptype::const_iterator)ite == cite);
+ UNIT_ASSERT(!((maptype::const_iterator)ite != cite));
+ UNIT_ASSERT(cite == (maptype::const_iterator)ite);
+ UNIT_ASSERT(!(cite != (maptype::const_iterator)ite));
+}
+
+void THashTest::TestHMapEqualityOperator() {
+ using container = THashMap<TString, int>;
+
+ container base;
+ base["one"] = 1;
+ base["two"] = 2;
+
+ container c1(base);
+ UNIT_ASSERT(c1 == base);
+
+ container c2;
+ c2["two"] = 2;
+ c2["one"] = 1;
+ UNIT_ASSERT(c2 == base);
+
+ c2["three"] = 3;
+ UNIT_ASSERT(c2 != base);
+
+ container c3(base);
+ c3["one"] = 0;
+ UNIT_ASSERT(c3 != base);
+}
+
+void THashTest::TestHMMapEqualityOperator() {
+ using container = THashMultiMap<TString, int>;
+ using value = container::value_type;
+
+ container base;
+ base.insert(value("one", 1));
+ base.insert(value("one", -1));
+ base.insert(value("two", 2));
+
+ container c1(base);
+ UNIT_ASSERT(c1 == base);
+
+ container c2;
+ c2.insert(value("two", 2));
+ c2.insert(value("one", -1));
+ c2.insert(value("one", 1));
+ UNIT_ASSERT(c2 == base);
+
+ c2.insert(value("three", 3));
+ UNIT_ASSERT(c2 != base);
+
+ container c3;
+ c3.insert(value("one", 0));
+ c3.insert(value("one", -1));
+ c3.insert(value("two", 2));
+ UNIT_ASSERT(c3 != base);
+
+ container c4;
+ c4.insert(value("one", 1));
+ c4.insert(value("one", -1));
+ c4.insert(value("one", 0));
+ c4.insert(value("two", 2));
+ UNIT_ASSERT(c3 != base);
+}
+
+void THashTest::TestHMMapConstructorsAndAssignments() {
+ using container = THashMultiMap<TString, int>;
+
+ container c1;
+ c1.insert(container::value_type("one", 1));
+ c1.insert(container::value_type("two", 2));
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+
+ c2.insert(container::value_type("three", 3));
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+
+ c2.insert(container::value_type("four", 4));
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+}
+
+void THashTest::TestHMMap1() {
+ using mmap = THashMultiMap<char, int, THash<char>, TEqualTo<char>>;
+ mmap m;
+
+ UNIT_ASSERT(m.count('X') == 0);
+ m.insert(std::pair<const char, int>('X', 10)); // Standard way.
+ UNIT_ASSERT(m.count('X') == 1);
+
+ m.insert(std::pair<const char, int>('X', 20)); // jbuck: standard way
+ UNIT_ASSERT(m.count('X') == 2);
+
+ m.insert(std::pair<const char, int>('Y', 32)); // jbuck: standard way
+ mmap::iterator i = m.find('X'); // Find first match.
+
+ UNIT_ASSERT((*i).first == 'X');
+ UNIT_ASSERT((*i).second == 10);
+ ++i;
+ UNIT_ASSERT((*i).first == 'X');
+ UNIT_ASSERT((*i).second == 20);
+
+ i = m.find('Y');
+ UNIT_ASSERT((*i).first == 'Y');
+ UNIT_ASSERT((*i).second == 32);
+
+ i = m.find('Z');
+ UNIT_ASSERT(i == m.end());
+
+ size_t count = m.erase('X');
+ UNIT_ASSERT(count == 2);
+
+ //Some iterators compare check, really compile time checks
+ mmap::iterator ite(m.begin());
+ mmap::const_iterator cite(m.begin());
+
+ UNIT_ASSERT((mmap::const_iterator)ite == cite);
+ UNIT_ASSERT(!((mmap::const_iterator)ite != cite));
+ UNIT_ASSERT(cite == (mmap::const_iterator)ite);
+ UNIT_ASSERT(!(cite != (mmap::const_iterator)ite));
+
+ using HMapType = THashMultiMap<size_t, size_t>;
+ HMapType hmap;
+
+ //We fill the map to implicitely start a rehash.
+ for (size_t counter = 0; counter < 3077; ++counter) {
+ hmap.insert(HMapType::value_type(1, counter));
+ }
+
+ hmap.insert(HMapType::value_type(12325, 1));
+ hmap.insert(HMapType::value_type(12325, 2));
+
+ UNIT_ASSERT(hmap.count(12325) == 2);
+
+ //At this point 23 goes to the same bucket as 12325, it used to reveal a bug.
+ hmap.insert(HMapType::value_type(23, 0));
+
+ UNIT_ASSERT(hmap.count(12325) == 2);
+
+ UNIT_ASSERT(hmap.bucket_count() > 3000);
+ for (size_t n = 0; n < 10; n++) {
+ hmap.clear();
+ hmap.insert(HMapType::value_type(1, 2));
+ }
+ UNIT_ASSERT(hmap.bucket_count() < 30);
+}
+
+void THashTest::TestHMMapHas() {
+ using mmap = THashMultiMap<char, int, THash<char>, TEqualTo<char>>;
+ mmap m;
+ m.insert(std::pair<const char, int>('X', 10));
+ m.insert(std::pair<const char, int>('X', 20));
+ m.insert(std::pair<const char, int>('Y', 32));
+ UNIT_ASSERT(m.contains('X'));
+ UNIT_ASSERT(m.contains('Y'));
+ UNIT_ASSERT(!m.contains('Z'));
+}
+
+void THashTest::TestHSetConstructorsAndAssignments() {
+ using container = THashSet<int>;
+
+ container c1;
+ c1.insert(100);
+ c1.insert(200);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT(c1.contains(100));
+ UNIT_ASSERT(c2.contains(200));
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT(c3.contains(100));
+
+ c2.insert(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT(c3.contains(300));
+
+ c2.insert(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT(c3.contains(400));
+
+ container c4 = {1, 2, 3};
+ UNIT_ASSERT_VALUES_EQUAL(c4.size(), 3);
+ UNIT_ASSERT(c4.contains(1));
+ UNIT_ASSERT(c4.contains(2));
+ UNIT_ASSERT(c4.contains(3));
+}
+
+void THashTest::TestHSetSize() {
+ using container = THashSet<int>;
+
+ container c;
+ c.insert(100);
+ c.insert(200);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c.size());
+
+ c.insert(200);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c.size());
+}
+
+void THashTest::TestHSet2() {
+ THashSet<int, THash<int>, TEqualTo<int>> s;
+ auto p = s.insert(42);
+ UNIT_ASSERT(p.second);
+ UNIT_ASSERT(*(p.first) == 42);
+
+ p = s.insert(42);
+ UNIT_ASSERT(!p.second);
+}
+
+void THashTest::TestHSetEqualityOperator() {
+ using container = THashSet<int>;
+
+ container base;
+ base.insert(1);
+ base.insert(2);
+
+ container c1(base);
+ UNIT_ASSERT(c1 == base);
+
+ c1.insert(1);
+ UNIT_ASSERT(c1 == base);
+
+ c1.insert(3);
+ UNIT_ASSERT(c1 != base);
+
+ container c2;
+ c2.insert(2);
+ c2.insert(1);
+ UNIT_ASSERT(c2 == base);
+
+ container c3;
+ c3.insert(1);
+ UNIT_ASSERT(c3 != base);
+}
+
+void THashTest::TestHMSetConstructorsAndAssignments() {
+ using container = THashMultiSet<int>;
+
+ container c1;
+ c1.insert(100);
+ c1.insert(200);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT(c1.find(100) != c1.end());
+ UNIT_ASSERT(c2.find(200) != c2.end());
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT(c3.find(100) != c3.end());
+
+ c2.insert(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT(c3.find(300) != c3.end());
+
+ c2.insert(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT(c3.find(400) != c3.end());
+}
+
+void THashTest::TestHMSetSize() {
+ using container = THashMultiSet<int>;
+
+ container c;
+ c.insert(100);
+ c.insert(200);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c.size());
+
+ c.insert(200);
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c.size());
+}
+
+void THashTest::TestHMSet1() {
+ hmset s;
+ UNIT_ASSERT(s.count(star) == 0);
+ s.insert(star);
+ UNIT_ASSERT(s.count(star) == 1);
+ s.insert(star);
+ UNIT_ASSERT(s.count(star) == 2);
+ auto i = s.find(char(40));
+ UNIT_ASSERT(i == s.end());
+
+ i = s.find(star);
+ UNIT_ASSERT(i != s.end());
+ UNIT_ASSERT(*i == '*');
+ UNIT_ASSERT(s.erase(star) == 2);
+}
+
+void THashTest::TestHMSetEqualityOperator() {
+ using container = THashMultiSet<int>;
+
+ container base;
+ base.insert(1);
+ base.insert(1);
+ base.insert(2);
+
+ container c1(base);
+ UNIT_ASSERT(c1 == base);
+
+ c1.insert(1);
+ UNIT_ASSERT(!(c1 == base));
+
+ container c2;
+ c2.insert(2);
+ c2.insert(1);
+ c2.insert(1);
+ UNIT_ASSERT(c2 == base);
+
+ container c3;
+ c3.insert(1);
+ c3.insert(2);
+ UNIT_ASSERT(!(c3 == base));
+
+ c3.insert(1);
+ UNIT_ASSERT(c3 == base);
+
+ c3.insert(3);
+ UNIT_ASSERT(!(c3 == base));
+}
+
+void THashTest::TestHMSetEmplace() {
+ class TKey: public NTesting::TProbe {
+ public:
+ TKey(NTesting::TProbeState* state, int key)
+ : TProbe(state)
+ , Key_(key)
+ {
+ }
+
+ operator size_t() const {
+ return THash<int>()(Key_);
+ }
+
+ bool operator==(const TKey& other) const {
+ return Key_ == other.Key_;
+ }
+
+ private:
+ int Key_;
+ };
+
+ NTesting::TProbeState state;
+
+ {
+ THashMultiSet<TKey> c;
+ c.emplace(&state, 1);
+ c.emplace(&state, 1);
+ c.emplace(&state, 2);
+
+ UNIT_ASSERT_EQUAL(state.CopyAssignments, 0);
+ UNIT_ASSERT_EQUAL(state.MoveAssignments, 0);
+ UNIT_ASSERT_EQUAL(state.Constructors, 3);
+ UNIT_ASSERT_EQUAL(state.MoveConstructors, 0);
+
+ UNIT_ASSERT_EQUAL(c.count(TKey(&state, 1)), 2);
+ UNIT_ASSERT_EQUAL(c.count(TKey(&state, 2)), 1);
+ UNIT_ASSERT_EQUAL(c.count(TKey(&state, 3)), 0);
+
+ UNIT_ASSERT_EQUAL(state.Constructors, 6);
+ UNIT_ASSERT_EQUAL(state.Destructors, 3);
+ }
+
+ UNIT_ASSERT_EQUAL(state.CopyAssignments, 0);
+ UNIT_ASSERT_EQUAL(state.MoveAssignments, 0);
+ UNIT_ASSERT_EQUAL(state.CopyConstructors, 0);
+ UNIT_ASSERT_EQUAL(state.MoveConstructors, 0);
+ UNIT_ASSERT_EQUAL(state.Constructors, 6);
+ UNIT_ASSERT_EQUAL(state.Destructors, 6);
+}
+
+void THashTest::TestInsertErase() {
+ using hmap = THashMap<TString, size_t, THash<TString>, TEqualTo<TString>>;
+ using val_type = hmap::value_type;
+
+ {
+ hmap values;
+
+ UNIT_ASSERT(values.insert(val_type("foo", 0)).second);
+ UNIT_ASSERT(values.insert(val_type("bar", 0)).second);
+ UNIT_ASSERT(values.insert(val_type("abc", 0)).second);
+
+ UNIT_ASSERT(values.erase("foo") == 1);
+ UNIT_ASSERT(values.erase("bar") == 1);
+ UNIT_ASSERT(values.erase("abc") == 1);
+ }
+
+ {
+ hmap values;
+
+ UNIT_ASSERT(values.insert(val_type("foo", 0)).second);
+ UNIT_ASSERT(values.insert(val_type("bar", 0)).second);
+ UNIT_ASSERT(values.insert(val_type("abc", 0)).second);
+
+ UNIT_ASSERT(values.erase("abc") == 1);
+ UNIT_ASSERT(values.erase("bar") == 1);
+ UNIT_ASSERT(values.erase("foo") == 1);
+ }
+}
+
+namespace {
+ struct TItem: public TSimpleRefCount<TItem> {
+ const TString Key;
+ const TString Value;
+
+ TItem(const TString& key, const TString& value)
+ : Key(key)
+ , Value(value)
+ {
+ }
+ };
+
+ using TItemPtr = TIntrusivePtr<TItem>;
+
+ struct TSelectKey {
+ const TString& operator()(const TItemPtr& item) const {
+ return item->Key;
+ }
+ };
+
+ using TItemMapBase = THashTable<
+ TItemPtr,
+ TString,
+ THash<TString>,
+ TSelectKey,
+ TEqualTo<TString>,
+ std::allocator<TItemPtr>>;
+
+ struct TItemMap: public TItemMapBase {
+ TItemMap()
+ : TItemMapBase(1, THash<TString>(), TEqualTo<TString>())
+ {
+ }
+
+ TItem& Add(const TString& key, const TString& value) {
+ insert_ctx ins;
+ iterator it = find_i(key, ins);
+ if (it == end()) {
+ it = insert_direct(new TItem(key, value), ins);
+ }
+ return **it;
+ }
+ };
+}
+
+void THashTest::TestResizeOnInsertSmartPtrBug() {
+ TItemMap map;
+ map.Add("key1", "value1");
+ map.Add("key2", "value2");
+ map.Add("key3", "value3");
+ map.Add("key4", "value4");
+ map.Add("key5", "value5");
+ map.Add("key6", "value6");
+ map.Add("key7", "value7");
+ TItem& item = map.Add("key8", "value8");
+ UNIT_ASSERT_EQUAL(item.Key, "key8");
+ UNIT_ASSERT_EQUAL(item.Value, "value8");
+}
+
+template <typename T>
+static void EmptyAndInsertTest(typename T::value_type v) {
+ T c;
+ UNIT_ASSERT(!c);
+ c.insert(v);
+ UNIT_ASSERT(c);
+}
+
+void THashTest::TestEmpty() {
+ EmptyAndInsertTest<THashSet<int>>(1);
+ EmptyAndInsertTest<THashMap<int, int>>(std::pair<int, int>(1, 2));
+ EmptyAndInsertTest<THashMultiMap<int, int>>(std::pair<int, int>(1, 2));
+}
+
+void THashTest::TestDefaultConstructor() {
+ THashSet<int> set;
+
+ UNIT_ASSERT(set.begin() == set.end());
+
+ UNIT_ASSERT(set.find(0) == set.end());
+
+ auto range = set.equal_range(0);
+ UNIT_ASSERT(range.first == range.second);
+}
+
+void THashTest::TestSizeOf() {
+ /* This test checks that we don't waste memory when all functors passed to
+ * THashTable are empty. It does rely on knowledge of THashTable internals,
+ * so if those change, the test will have to be adjusted accordingly. */
+
+ size_t expectedSize = sizeof(uintptr_t) + 3 * sizeof(size_t);
+
+ UNIT_ASSERT_VALUES_EQUAL(sizeof(THashMap<int, int>), expectedSize);
+ UNIT_ASSERT_VALUES_EQUAL(sizeof(THashMap<std::pair<int, int>, std::pair<int, int>>), expectedSize);
+}
+
+void THashTest::TestInvariants() {
+ std::set<int> reference_set;
+ THashSet<int> set;
+
+ for (int i = 0; i < 1000; i++) {
+ set.insert(i);
+ reference_set.insert(i);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(set.size(), 1000);
+
+ int count0 = 0;
+ for (int i = 0; i < 1000; i++) {
+ count0 += (set.find(i) != set.end()) ? 1 : 0;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(count0, 1000);
+
+ int count1 = 0;
+ for (auto pos = set.begin(); pos != set.end(); pos++) {
+ ++count1;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(count1, 1000);
+
+ int count2 = 0;
+ for (const int& value : set) {
+ count2 += (reference_set.find(value) != reference_set.end()) ? 1 : 0;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(count2, 1000);
+}
+
+struct TAllocatorCounters {
+ TAllocatorCounters()
+ : Allocations(0)
+ , Deallocations(0)
+ {
+ }
+
+ ~TAllocatorCounters() {
+ std::allocator<char> allocator;
+
+ /* Release whatever was (intentionally) leaked. */
+ for (const auto& chunk : Chunks) {
+ allocator.deallocate(static_cast<char*>(chunk.first), chunk.second);
+ }
+ }
+
+ size_t Allocations;
+ size_t Deallocations;
+ TSet<std::pair<void*, size_t>> Chunks;
+};
+
+template <class T>
+class TCountingAllocator: public std::allocator<T> {
+ using base_type = std::allocator<T>;
+
+public:
+ using size_type = typename base_type::size_type;
+
+ template <class Other>
+ struct rebind {
+ using other = TCountingAllocator<Other>;
+ };
+
+ TCountingAllocator()
+ : Counters_(nullptr)
+ {
+ }
+
+ TCountingAllocator(TAllocatorCounters* counters)
+ : Counters_(counters)
+ {
+ Y_ASSERT(counters);
+ }
+
+ template <class Other>
+ TCountingAllocator(const TCountingAllocator<Other>& other)
+ : Counters_(other.Counters)
+ {
+ }
+
+ T* allocate(size_type n) {
+ auto result = base_type::allocate(n);
+
+ if (Counters_) {
+ ++Counters_->Allocations;
+ Counters_->Chunks.emplace(result, n * sizeof(T));
+ }
+
+ return result;
+ }
+
+ void deallocate(T* p, size_type n) {
+ if (Counters_) {
+ ++Counters_->Deallocations;
+ Counters_->Chunks.erase(std::make_pair(p, n * sizeof(T)));
+ }
+
+ base_type::deallocate(p, n);
+ }
+
+private:
+ TAllocatorCounters* Counters_;
+};
+
+void THashTest::TestAllocation() {
+ TAllocatorCounters counters;
+
+ using int_set = THashSet<int, THash<int>, TEqualTo<int>, TCountingAllocator<int>>;
+
+ {
+ int_set set0(&counters);
+ int_set set1(set0);
+ set0.clear();
+ int_set set2(&counters);
+ set2 = set1;
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 0); /* Copying around null sets should not trigger allocations. */
+
+ set0.insert(0);
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 2); /* One for buckets array, one for a new node. */
+
+ set0.clear();
+ set1 = set0;
+ int_set set3(set0);
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 2); /* Copying from an empty set with allocated buckets should not trigger allocations. */
+
+ for (int i = 0; i < 1000; i++) {
+ set0.insert(i);
+ }
+ size_t allocations = counters.Allocations;
+ set0.clear();
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, allocations); /* clear() should not trigger allocations. */
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, counters.Deallocations);
+}
+
+template <int Value>
+class TNonCopyableInt {
+public:
+ explicit TNonCopyableInt(int) {
+ }
+
+ TNonCopyableInt() = delete;
+ TNonCopyableInt(const TNonCopyableInt&) = delete;
+ TNonCopyableInt(TNonCopyable&&) = delete;
+ TNonCopyableInt& operator=(const TNonCopyable&) = delete;
+ TNonCopyableInt& operator=(TNonCopyable&&) = delete;
+
+ operator int() const {
+ return Value;
+ }
+};
+
+void THashTest::TestInsertCopy() {
+ THashMap<int, int> hash;
+
+ /* Insertion should not make copies of the provided key. */
+ hash[TNonCopyableInt<0>(0)] = 0;
+}
+
+void THashTest::TestEmplace() {
+ using hash_t = THashMap<int, TNonCopyableInt<0>>;
+ hash_t hash;
+ hash.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0));
+ auto it = hash.find(1);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0);
+}
+
+void THashTest::TestEmplaceNoresize() {
+ using hash_t = THashMap<int, TNonCopyableInt<0>>;
+ hash_t hash;
+ hash.reserve(1);
+ hash.emplace_noresize(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0));
+ auto it = hash.find(1);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0);
+}
+
+void THashTest::TestEmplaceDirect() {
+ using hash_t = THashMap<int, TNonCopyableInt<0>>;
+ hash_t hash;
+ hash_t::insert_ctx ins;
+ hash.find(1, ins);
+ hash.emplace_direct(ins, std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0));
+ auto it = hash.find(1);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0);
+}
+
+void THashTest::TestTryEmplace() {
+ static unsigned counter = 0u;
+
+ struct TCountConstruct {
+ explicit TCountConstruct(int v)
+ : value(v)
+ {
+ ++counter;
+ }
+ TCountConstruct(const TCountConstruct&) = delete;
+ int value;
+ };
+
+ THashMap<int, TCountConstruct> hash;
+ {
+ // try_emplace does not copy key if key is rvalue
+ auto r = hash.try_emplace(TNonCopyableInt<0>(0), 1);
+ UNIT_ASSERT(r.second);
+ UNIT_ASSERT_VALUES_EQUAL(1, counter);
+ UNIT_ASSERT_VALUES_EQUAL(1, r.first->second.value);
+ }
+ {
+ auto r = hash.try_emplace(0, 2);
+ UNIT_ASSERT(!r.second);
+ UNIT_ASSERT_VALUES_EQUAL(1, counter);
+ UNIT_ASSERT_VALUES_EQUAL(1, r.first->second.value);
+ }
+}
+
+void THashTest::TestTryEmplaceCopyKey() {
+ static unsigned counter = 0u;
+
+ struct TCountCopy {
+ explicit TCountCopy(int i)
+ : Value(i)
+ {
+ }
+ TCountCopy(const TCountCopy& other)
+ : Value(other.Value)
+ {
+ ++counter;
+ }
+
+ operator int() const {
+ return Value;
+ }
+
+ int Value;
+ };
+
+ THashMap<TCountCopy, TNonCopyableInt<0>> hash;
+ TCountCopy key(1);
+ {
+ // try_emplace copy key if key is lvalue
+ auto r = hash.try_emplace(key, 1);
+ UNIT_ASSERT(r.second);
+ UNIT_ASSERT_VALUES_EQUAL(1, counter);
+ }
+ {
+ // no insert - no copy
+ auto r = hash.try_emplace(key, 2);
+ UNIT_ASSERT(!r.second);
+ UNIT_ASSERT_VALUES_EQUAL(1, counter);
+ }
+}
+
+void THashTest::TestHMMapEmplace() {
+ using hash_t = THashMultiMap<int, TNonCopyableInt<0>>;
+ hash_t hash;
+ hash.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0));
+ auto it = hash.find(1);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0);
+}
+
+void THashTest::TestHMMapEmplaceNoresize() {
+ using hash_t = THashMultiMap<int, TNonCopyableInt<0>>;
+ hash_t hash;
+ hash.reserve(1);
+ hash.emplace_noresize(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0));
+ auto it = hash.find(1);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0);
+}
+
+void THashTest::TestHMMapEmplaceDirect() {
+ using hash_t = THashMultiMap<int, TNonCopyableInt<0>>;
+ hash_t hash;
+ hash_t::insert_ctx ins;
+ hash.find(1, ins);
+ hash.emplace_direct(ins, std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(0));
+ auto it = hash.find(1);
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(it->second), 0);
+}
+
+void THashTest::TestHSetEmplace() {
+ using hash_t = THashSet<TNonCopyableInt<0>, THash<int>, TEqualTo<int>>;
+ hash_t hash;
+ UNIT_ASSERT(!hash.contains(0));
+ hash.emplace(0);
+ UNIT_ASSERT(hash.contains(0));
+ UNIT_ASSERT(!hash.contains(1));
+}
+
+void THashTest::TestHSetEmplaceNoresize() {
+ using hash_t = THashSet<TNonCopyableInt<0>, THash<int>, TEqualTo<int>>;
+ hash_t hash;
+ hash.reserve(1);
+ UNIT_ASSERT(!hash.contains(0));
+ hash.emplace_noresize(0);
+ UNIT_ASSERT(hash.contains(0));
+ UNIT_ASSERT(!hash.contains(1));
+}
+
+void THashTest::TestHSetEmplaceDirect() {
+ using hash_t = THashSet<TNonCopyableInt<0>, THash<int>, TEqualTo<int>>;
+ hash_t hash;
+ UNIT_ASSERT(!hash.contains(0));
+ hash_t::insert_ctx ins;
+ hash.find(0, ins);
+ hash.emplace_direct(ins, 1);
+ UNIT_ASSERT(hash.contains(0));
+ UNIT_ASSERT(!hash.contains(1));
+}
+
+void THashTest::TestNonCopyable() {
+ struct TValue: public TNonCopyable {
+ int value;
+ TValue(int _value = 0)
+ : value(_value)
+ {
+ }
+ operator int() {
+ return value;
+ }
+ };
+
+ THashMap<int, TValue> hash;
+ hash.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(5));
+ auto&& value = hash[1];
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(value), 5);
+ auto&& not_inserted = hash[2];
+ UNIT_ASSERT_VALUES_EQUAL(static_cast<int>(not_inserted), 0);
+}
+
+void THashTest::TestValueInitialization() {
+ THashMap<int, int> hash;
+
+ int& value = hash[0];
+
+ /* Implicitly inserted values should be value-initialized. */
+ UNIT_ASSERT_VALUES_EQUAL(value, 0);
+}
+
+void THashTest::TestAssignmentClear() {
+ /* This one tests that assigning an empty hash resets the buckets array.
+ * See operator= for details. */
+
+ THashMap<int, int> hash;
+ size_t emptyBucketCount = hash.bucket_count();
+
+ for (int i = 0; i < 100; i++) {
+ hash[i] = i;
+ }
+
+ hash = THashMap<int, int>();
+
+ UNIT_ASSERT_VALUES_EQUAL(hash.bucket_count(), emptyBucketCount);
+}
+
+void THashTest::TestReleaseNodes() {
+ TAllocatorCounters counters;
+ using TIntSet = THashSet<int, THash<int>, TEqualTo<int>, TCountingAllocator<int>>;
+
+ TIntSet set(&counters);
+ for (int i = 0; i < 3; i++) {
+ set.insert(i);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 4);
+
+ set.release_nodes();
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 4);
+ UNIT_ASSERT_VALUES_EQUAL(set.size(), 0);
+
+ for (int i = 10; i < 13; i++) {
+ set.insert(i);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(counters.Allocations, 7);
+ UNIT_ASSERT(set.contains(10));
+ UNIT_ASSERT(!set.contains(0));
+
+ set.basic_clear();
+ UNIT_ASSERT_VALUES_EQUAL(counters.Deallocations, 3);
+
+ TIntSet set2;
+ set2.release_nodes();
+ set2.insert(1);
+ UNIT_ASSERT_VALUES_EQUAL(set2.size(), 1);
+}
+
+void THashTest::TestAt() {
+#define TEST_AT_THROWN_EXCEPTION(SRC_TYPE, DST_TYPE, KEY_TYPE, KEY, MESSAGE) \
+ { \
+ THashMap<SRC_TYPE, DST_TYPE> testMap; \
+ try { \
+ KEY_TYPE testKey = KEY; \
+ testMap.at(testKey); \
+ UNIT_ASSERT_C(false, "THashMap::at(\"" << KEY << "\") should throw"); \
+ } catch (const yexception& e) { \
+ UNIT_ASSERT_C(e.AsStrBuf().Contains(MESSAGE), "Incorrect exception description: got \"" << e.what() << "\", expected: \"" << MESSAGE << "\""); \
+ } catch (...) { \
+ UNIT_ASSERT_C(false, "THashMap::at(\"" << KEY << "\") should throw yexception"); \
+ } \
+ }
+
+ TEST_AT_THROWN_EXCEPTION(TString, TString, TString, "111", "111");
+ TEST_AT_THROWN_EXCEPTION(TString, TString, const TString, "111", "111");
+ TEST_AT_THROWN_EXCEPTION(TString, TString, TStringBuf, "111", "111");
+ TEST_AT_THROWN_EXCEPTION(TString, TString, const TStringBuf, "111", "111");
+ TEST_AT_THROWN_EXCEPTION(TStringBuf, TStringBuf, const char*, "111", "111");
+ TEST_AT_THROWN_EXCEPTION(int, int, short, 11, "11");
+ TEST_AT_THROWN_EXCEPTION(int, int, int, -1, "-1");
+ TEST_AT_THROWN_EXCEPTION(int, int, long, 111, "111");
+ TEST_AT_THROWN_EXCEPTION(int, int, long long, -1000000000000ll, "-1000000000000");
+ TEST_AT_THROWN_EXCEPTION(int, int, unsigned short, 11, "11");
+ TEST_AT_THROWN_EXCEPTION(int, int, unsigned int, 2, "2");
+ TEST_AT_THROWN_EXCEPTION(int, int, unsigned long, 131, "131");
+ TEST_AT_THROWN_EXCEPTION(int, int, unsigned long long, 1000000000000ll, "1000000000000");
+
+ char key[] = {11, 12, 0, 1, 2, 11, 0};
+ TEST_AT_THROWN_EXCEPTION(TString, TString, char*, key, "\\x0B\\x0C");
+ TEST_AT_THROWN_EXCEPTION(TString, TString, TStringBuf, TStringBuf(key, sizeof(key) - 1), "\\x0B\\x0C\\0\\1\\2\\x0B");
+
+#undef TEST_AT_THROWN_EXCEPTION
+}
+
+void THashTest::TestHMapInitializerList() {
+ THashMap<TString, TString> h1 = {{"foo", "bar"}, {"bar", "baz"}, {"baz", "qux"}};
+ THashMap<TString, TString> h2;
+ h2.insert(std::pair<TString, TString>("foo", "bar"));
+ h2.insert(std::pair<TString, TString>("bar", "baz"));
+ h2.insert(std::pair<TString, TString>("baz", "qux"));
+ UNIT_ASSERT_EQUAL(h1, h2);
+}
+
+void THashTest::TestHMMapInitializerList() {
+ THashMultiMap<TString, TString> h1 = {
+ {"foo", "bar"},
+ {"foo", "baz"},
+ {"baz", "qux"}};
+ THashMultiMap<TString, TString> h2;
+ h2.insert(std::pair<TString, TString>("foo", "bar"));
+ h2.insert(std::pair<TString, TString>("foo", "baz"));
+ h2.insert(std::pair<TString, TString>("baz", "qux"));
+ UNIT_ASSERT_EQUAL(h1, h2);
+}
+
+void THashTest::TestHSetInitializerList() {
+ THashSet<TString> h1 = {"foo", "bar", "baz"};
+ THashSet<TString> h2;
+ h2.insert("foo");
+ h2.insert("bar");
+ h2.insert("baz");
+ UNIT_ASSERT_EQUAL(h1, h2);
+}
+
+void THashTest::TestHMSetInitializerList() {
+ THashMultiSet<TString> h1 = {"foo", "foo", "bar", "baz"};
+ THashMultiSet<TString> h2;
+ h2.insert("foo");
+ h2.insert("foo");
+ h2.insert("bar");
+ h2.insert("baz");
+ UNIT_ASSERT_EQUAL(h1, h2);
+}
+
+namespace {
+ struct TFoo {
+ int A;
+ int B;
+
+ bool operator==(const TFoo& o) const {
+ return A == o.A && B == o.B;
+ }
+ };
+}
+
+template <>
+struct THash<TFoo> {
+ size_t operator()(const TFoo& v) const {
+ return v.A ^ v.B;
+ }
+};
+
+template <>
+void Out<TFoo>(IOutputStream& o, const TFoo& v) {
+ o << '{' << v.A << ';' << v.B << '}';
+}
+
+void THashTest::TestHSetInsertInitializerList() {
+ {
+ const THashSet<int> x = {1};
+ THashSet<int> y;
+ y.insert({1});
+ UNIT_ASSERT_VALUES_EQUAL(x, y);
+ }
+ {
+ const THashSet<int> x = {1, 2};
+ THashSet<int> y;
+ y.insert({1, 2});
+ UNIT_ASSERT_VALUES_EQUAL(x, y);
+ }
+ {
+ const THashSet<int> x = {1, 2, 3, 4, 5};
+ THashSet<int> y;
+ y.insert({
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ });
+ UNIT_ASSERT_VALUES_EQUAL(x, y);
+ }
+ {
+ const THashSet<TFoo> x = {{1, 2}};
+ THashSet<TFoo> y;
+ y.insert({{1, 2}});
+ UNIT_ASSERT_VALUES_EQUAL(x, y);
+ }
+ {
+ const THashSet<TFoo> x = {{1, 2}, {3, 4}};
+ THashSet<TFoo> y;
+ y.insert({{1, 2}, {3, 4}});
+ UNIT_ASSERT_VALUES_EQUAL(x, y);
+ }
+}
+
+/*
+* Sequence for MultiHash is reversed as it calculates hash as
+* f(head:tail) = f(tail)xHash(head)
+*/
+void THashTest::TestTupleHash() {
+ std::tuple<int, int> tuple{1, 3};
+ UNIT_ASSERT_VALUES_EQUAL(THash<decltype(tuple)>()(tuple), MultiHash(3, 1));
+
+ /*
+ * This thing checks that we didn't break STL code
+ * See https://a.yandex-team.ru/arc/commit/2864838#comment-401
+ * for example
+ */
+ struct A {
+ A Foo(const std::tuple<A, float>& v) {
+ return std::get<A>(v);
+ }
+ };
+}
diff --git a/util/generic/hash_ut.pyx b/util/generic/hash_ut.pyx
new file mode 100644
index 0000000000..ecf6dac2e6
--- /dev/null
+++ b/util/generic/hash_ut.pyx
@@ -0,0 +1,84 @@
+# cython: c_string_type=str, c_string_encoding=utf8
+
+from util.generic.hash cimport THashMap
+from util.generic.string cimport TString
+
+import pytest
+import unittest
+
+from libcpp.pair cimport pair
+from cython.operator cimport dereference as deref
+
+
+def _check_convert(THashMap[TString, int] x):
+ return x
+
+
+class TestHash(unittest.TestCase):
+
+ def test_constructors_and_assignments(self):
+ cdef THashMap[TString, int] c1
+ c1["one"] = 1
+ c1["two"] = 2
+ cdef THashMap[TString, int] c2 = THashMap[TString, int](c1)
+ self.assertEqual(2, c1.size())
+ self.assertEqual(2, c2.size())
+ self.assertEqual(1, c1.at("one"))
+ self.assertTrue(c1.contains("two"))
+ self.assertTrue(c2.contains("one"))
+ self.assertEqual(2, c2.at("two"))
+ c2["three"] = 3
+ c1 = c2
+ self.assertEqual(3, c1.size())
+ self.assertEqual(3, c2.size())
+ self.assertEqual(3, c1.at("three"))
+
+ def test_equality_operator(self):
+ cdef THashMap[TString, int] base
+ base["one"] = 1
+ base["two"] = 2
+
+ cdef THashMap[TString, int] c1 = THashMap[TString, int](base)
+ self.assertTrue(c1==base)
+
+ cdef THashMap[TString, int] c2
+ c2["one"] = 1
+ c2["two"] = 2
+ self.assertTrue(c2 == base)
+
+ c2["three"] = 3
+ self.assertTrue(c2 != base)
+
+ cdef THashMap[TString, int] c3 = THashMap[TString, int](base)
+ c3["one"] = 0
+ self.assertTrue(c3 != base)
+
+ def test_insert_erase(self):
+ cdef THashMap[TString, int] tmp
+ self.assertTrue(tmp.insert(pair[TString, int]("one", 0)).second)
+ self.assertFalse(tmp.insert(pair[TString, int]("one", 1)).second)
+ self.assertTrue(tmp.insert(pair[TString, int]("two", 2)).second)
+ cdef TString one = "one"
+ cdef TString two = "two"
+ self.assertEqual(tmp.erase(one), 1)
+ self.assertEqual(tmp.erase(two), 1)
+ self.assertEqual(tmp.size(), 0)
+ self.assertTrue(tmp.empty())
+
+ def test_iterators_and_find(self):
+ cdef THashMap[TString, int] tmp
+ self.assertTrue(tmp.begin() == tmp.end())
+ self.assertTrue(tmp.find("1") == tmp.end())
+ tmp["1"] = 1
+ self.assertTrue(tmp.begin() != tmp.end())
+ cdef THashMap[TString, int].iterator it = tmp.find("1")
+ self.assertTrue(it != tmp.end())
+ self.assertEqual(deref(it).second, 1)
+
+ def test_convert(self):
+ src = {'foo': 1, 'bar': 42}
+ self.assertEqual(_check_convert(src), src)
+
+ bad_src = {'foo': 1, 'bar': 'baz'}
+ with self.assertRaises(TypeError):
+ _check_convert(bad_src)
diff --git a/util/generic/hide_ptr.cpp b/util/generic/hide_ptr.cpp
new file mode 100644
index 0000000000..74eac028aa
--- /dev/null
+++ b/util/generic/hide_ptr.cpp
@@ -0,0 +1,5 @@
+#include "hide_ptr.h"
+
+void* HidePointerOrigin(void* ptr) {
+ return ptr;
+}
diff --git a/util/generic/hide_ptr.h b/util/generic/hide_ptr.h
new file mode 100644
index 0000000000..c734894bd7
--- /dev/null
+++ b/util/generic/hide_ptr.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void* HidePointerOrigin(void*);
diff --git a/util/generic/intrlist.cpp b/util/generic/intrlist.cpp
new file mode 100644
index 0000000000..18a9250cc8
--- /dev/null
+++ b/util/generic/intrlist.cpp
@@ -0,0 +1 @@
+#include "intrlist.h"
diff --git a/util/generic/intrlist.h b/util/generic/intrlist.h
new file mode 100644
index 0000000000..b5d3f2051b
--- /dev/null
+++ b/util/generic/intrlist.h
@@ -0,0 +1,872 @@
+#pragma once
+
+#include "utility.h"
+
+#include <util/system/yassert.h>
+#include <iterator>
+
+struct TIntrusiveListDefaultTag {};
+
+/*
+ * two-way linked list
+ */
+template <class T, class Tag = TIntrusiveListDefaultTag>
+class TIntrusiveListItem {
+private:
+ using TListItem = TIntrusiveListItem<T, Tag>;
+
+public:
+ inline TIntrusiveListItem() noexcept
+ : Next_(this)
+ , Prev_(Next_)
+ {
+ }
+
+ inline ~TIntrusiveListItem() {
+ Unlink();
+ }
+
+public:
+ Y_PURE_FUNCTION inline bool Empty() const noexcept {
+ return (Prev_ == this) && (Next_ == this);
+ }
+
+ inline void Unlink() noexcept {
+ if (Empty()) {
+ return;
+ }
+
+ Prev_->SetNext(Next_);
+ Next_->SetPrev(Prev_);
+
+ SetEnd();
+ }
+
+ inline void LinkBefore(TListItem* before) noexcept {
+ Unlink();
+ LinkBeforeNoUnlink(before);
+ }
+
+ inline void LinkBeforeNoUnlink(TListItem* before) noexcept {
+ TListItem* const after = before->Prev();
+
+ after->SetNext(this);
+ SetPrev(after);
+ SetNext(before);
+ before->SetPrev(this);
+ }
+
+ inline void LinkBefore(TListItem& before) noexcept {
+ LinkBefore(&before);
+ }
+
+ inline void LinkAfter(TListItem* after) noexcept {
+ Unlink();
+ LinkBeforeNoUnlink(after->Next());
+ }
+
+ inline void LinkAfter(TListItem& after) noexcept {
+ LinkAfter(&after);
+ }
+
+public:
+ inline TListItem* Prev() noexcept {
+ return Prev_;
+ }
+
+ inline const TListItem* Prev() const noexcept {
+ return Prev_;
+ }
+
+ inline TListItem* Next() noexcept {
+ return Next_;
+ }
+
+ inline const TListItem* Next() const noexcept {
+ return Next_;
+ }
+
+public:
+ inline void SetEnd() noexcept {
+ Prev_ = this;
+ Next_ = Prev_;
+ }
+
+ inline void SetNext(TListItem* item) noexcept {
+ Next_ = item;
+ }
+
+ inline void SetPrev(TListItem* item) noexcept {
+ Prev_ = item;
+ }
+
+public:
+ inline T* Node() noexcept {
+ return static_cast<T*>(this);
+ }
+
+ inline const T* Node() const noexcept {
+ return static_cast<const T*>(this);
+ }
+
+private:
+ inline TIntrusiveListItem(const TIntrusiveListItem&) = delete;
+ inline TIntrusiveListItem& operator=(const TIntrusiveListItem&) = delete;
+
+private:
+ TListItem* Next_;
+ TListItem* Prev_;
+};
+
+template <class T, class Tag>
+class TIntrusiveList {
+private:
+ using TListItem = TIntrusiveListItem<T, Tag>;
+
+ template <class TListItem, class TNode>
+ class TIteratorBase {
+ public:
+ using TItem = TListItem;
+ using TReference = TNode&;
+ using TPointer = TNode*;
+
+ using iterator_category = std::bidirectional_iterator_tag;
+ using difference_type = ptrdiff_t;
+
+ using value_type = TNode;
+ using reference = TReference;
+ using pointer = TPointer;
+
+ inline TIteratorBase() noexcept
+ : Item_(nullptr)
+ {
+ }
+
+ template <class TListItem_, class TNode_>
+ inline TIteratorBase(const TIteratorBase<TListItem_, TNode_>& right) noexcept
+ : Item_(right.Item())
+ {
+ }
+
+ inline TIteratorBase(TItem* item) noexcept
+ : Item_(item)
+ {
+ }
+
+ inline TItem* Item() const noexcept {
+ return Item_;
+ }
+
+ inline void Next() noexcept {
+ Item_ = Item_->Next();
+ }
+
+ inline void Prev() noexcept {
+ Item_ = Item_->Prev();
+ }
+
+ template <class TListItem_, class TNode_>
+ inline bool operator==(const TIteratorBase<TListItem_, TNode_>& right) const noexcept {
+ return Item() == right.Item();
+ }
+
+ template <class TListItem_, class TNode_>
+ inline bool operator!=(const TIteratorBase<TListItem_, TNode_>& right) const noexcept {
+ return Item() != right.Item();
+ }
+
+ inline TIteratorBase& operator++() noexcept {
+ Next();
+
+ return *this;
+ }
+
+ inline TIteratorBase operator++(int) noexcept {
+ TIteratorBase ret(*this);
+
+ Next();
+
+ return ret;
+ }
+
+ inline TIteratorBase& operator--() noexcept {
+ Prev();
+
+ return *this;
+ }
+
+ inline TIteratorBase operator--(int) noexcept {
+ TIteratorBase ret(*this);
+
+ Prev();
+
+ return ret;
+ }
+
+ inline TReference operator*() const noexcept {
+ return *Item_->Node();
+ }
+
+ inline TPointer operator->() const noexcept {
+ return Item_->Node();
+ }
+
+ private:
+ TItem* Item_;
+ };
+
+ template <class TIterator>
+ class TReverseIteratorBase {
+ public:
+ using TItem = typename TIterator::TItem;
+ using TReference = typename TIterator::TReference;
+ using TPointer = typename TIterator::TPointer;
+
+ using iterator_category = typename TIterator::iterator_category;
+ using difference_type = typename TIterator::difference_type;
+
+ using value_type = typename TIterator::value_type;
+ using reference = typename TIterator::reference;
+ using pointer = typename TIterator::pointer;
+
+ inline TReverseIteratorBase() noexcept = default;
+
+ template <class TIterator_>
+ inline TReverseIteratorBase(const TReverseIteratorBase<TIterator_>& right) noexcept
+ : Current_(right.Base())
+ {
+ }
+
+ inline explicit TReverseIteratorBase(TIterator item) noexcept
+ : Current_(item)
+ {
+ }
+
+ inline TIterator Base() const noexcept {
+ return Current_;
+ }
+
+ inline TItem* Item() const noexcept {
+ TIterator ret = Current_;
+
+ return (--ret).Item();
+ }
+
+ inline void Next() noexcept {
+ Current_.Prev();
+ }
+
+ inline void Prev() noexcept {
+ Current_.Next();
+ }
+
+ template <class TIterator_>
+ inline bool operator==(const TReverseIteratorBase<TIterator_>& right) const noexcept {
+ return Base() == right.Base();
+ }
+
+ template <class TIterator_>
+ inline bool operator!=(const TReverseIteratorBase<TIterator_>& right) const noexcept {
+ return Base() != right.Base();
+ }
+
+ inline TReverseIteratorBase& operator++() noexcept {
+ Next();
+
+ return *this;
+ }
+
+ inline TReverseIteratorBase operator++(int) noexcept {
+ TReverseIteratorBase ret(*this);
+
+ Next();
+
+ return ret;
+ }
+
+ inline TReverseIteratorBase& operator--() noexcept {
+ Prev();
+
+ return *this;
+ }
+
+ inline TReverseIteratorBase operator--(int) noexcept {
+ TReverseIteratorBase ret(*this);
+
+ Prev();
+
+ return ret;
+ }
+
+ inline TReference operator*() const noexcept {
+ TIterator ret = Current_;
+
+ return *--ret;
+ }
+
+ inline TPointer operator->() const noexcept {
+ TIterator ret = Current_;
+
+ return &*--ret;
+ }
+
+ private:
+ TIterator Current_;
+ };
+
+public:
+ using TIterator = TIteratorBase<TListItem, T>;
+ using TConstIterator = TIteratorBase<const TListItem, const T>;
+
+ using TReverseIterator = TReverseIteratorBase<TIterator>;
+ using TConstReverseIterator = TReverseIteratorBase<TConstIterator>;
+
+ using iterator = TIterator;
+ using const_iterator = TConstIterator;
+
+ using reverse_iterator = TReverseIterator;
+ using const_reverse_iterator = TConstReverseIterator;
+
+public:
+ inline void Swap(TIntrusiveList& right) noexcept {
+ TIntrusiveList temp;
+
+ temp.Append(right);
+ Y_ASSERT(right.Empty());
+ right.Append(*this);
+ Y_ASSERT(this->Empty());
+ this->Append(temp);
+ Y_ASSERT(temp.Empty());
+ }
+
+public:
+ inline TIntrusiveList() noexcept = default;
+
+ inline ~TIntrusiveList() = default;
+
+ inline TIntrusiveList(TIntrusiveList&& right) noexcept {
+ this->Swap(right);
+ }
+
+ inline TIntrusiveList& operator=(TIntrusiveList&& rhs) noexcept {
+ this->Swap(rhs);
+ return *this;
+ }
+
+ inline explicit operator bool() const noexcept {
+ return !Empty();
+ }
+
+ Y_PURE_FUNCTION inline bool Empty() const noexcept {
+ return End_.Empty();
+ }
+
+ inline size_t Size() const noexcept {
+ return std::distance(Begin(), End());
+ }
+
+ inline void Remove(TListItem* item) noexcept {
+ item->Unlink();
+ }
+
+ inline void Clear() noexcept {
+ End_.Unlink();
+ }
+
+public:
+ inline TIterator Begin() noexcept {
+ return ++End();
+ }
+
+ inline TIterator End() noexcept {
+ return TIterator(&End_);
+ }
+
+ inline TConstIterator Begin() const noexcept {
+ return ++End();
+ }
+
+ inline TConstIterator End() const noexcept {
+ return TConstIterator(&End_);
+ }
+
+ inline TReverseIterator RBegin() noexcept {
+ return TReverseIterator(End());
+ }
+
+ inline TReverseIterator REnd() noexcept {
+ return TReverseIterator(Begin());
+ }
+
+ inline TConstReverseIterator RBegin() const noexcept {
+ return TConstReverseIterator(End());
+ }
+
+ inline TConstReverseIterator REnd() const noexcept {
+ return TConstReverseIterator(Begin());
+ }
+
+ inline TConstIterator CBegin() const noexcept {
+ return Begin();
+ }
+
+ inline TConstIterator CEnd() const noexcept {
+ return End();
+ }
+
+ inline TConstReverseIterator CRBegin() const noexcept {
+ return RBegin();
+ }
+
+ inline TConstReverseIterator CREnd() const noexcept {
+ return REnd();
+ }
+
+public:
+ inline iterator begin() noexcept {
+ return Begin();
+ }
+
+ inline iterator end() noexcept {
+ return End();
+ }
+
+ inline const_iterator begin() const noexcept {
+ return Begin();
+ }
+
+ inline const_iterator end() const noexcept {
+ return End();
+ }
+
+ inline reverse_iterator rbegin() noexcept {
+ return RBegin();
+ }
+
+ inline reverse_iterator rend() noexcept {
+ return REnd();
+ }
+
+ inline const_iterator cbegin() const noexcept {
+ return CBegin();
+ }
+
+ inline const_iterator cend() const noexcept {
+ return CEnd();
+ }
+
+ inline const_reverse_iterator crbegin() const noexcept {
+ return CRBegin();
+ }
+
+ inline const_reverse_iterator crend() const noexcept {
+ return CREnd();
+ }
+
+public:
+ inline T* Back() noexcept {
+ return End_.Prev()->Node();
+ }
+
+ inline T* Front() noexcept {
+ return End_.Next()->Node();
+ }
+
+ inline const T* Back() const noexcept {
+ return End_.Prev()->Node();
+ }
+
+ inline const T* Front() const noexcept {
+ return End_.Next()->Node();
+ }
+
+ inline void PushBack(TListItem* item) noexcept {
+ item->LinkBefore(End_);
+ }
+
+ inline void PushFront(TListItem* item) noexcept {
+ item->LinkAfter(End_);
+ }
+
+ inline T* PopBack() noexcept {
+ TListItem* const ret = End_.Prev();
+
+ ret->Unlink();
+
+ return ret->Node();
+ }
+
+ inline T* PopFront() noexcept {
+ TListItem* const ret = End_.Next();
+
+ ret->Unlink();
+
+ return ret->Node();
+ }
+
+ inline void Append(TIntrusiveList& list) noexcept {
+ Cut(list.Begin(), list.End(), End());
+ }
+
+ inline static void Cut(TIterator begin, TIterator end, TIterator pasteBefore) noexcept {
+ if (begin == end) {
+ return;
+ }
+
+ TListItem* const cutFront = begin.Item();
+ TListItem* const gapBack = end.Item();
+
+ TListItem* const gapFront = cutFront->Prev();
+ TListItem* const cutBack = gapBack->Prev();
+
+ gapFront->SetNext(gapBack);
+ gapBack->SetPrev(gapFront);
+
+ TListItem* const pasteBack = pasteBefore.Item();
+ TListItem* const pasteFront = pasteBack->Prev();
+
+ pasteFront->SetNext(cutFront);
+ cutFront->SetPrev(pasteFront);
+
+ cutBack->SetNext(pasteBack);
+ pasteBack->SetPrev(cutBack);
+ }
+
+public:
+ template <class TFunctor>
+ inline void ForEach(TFunctor&& functor) {
+ TIterator i = Begin();
+
+ while (i != End()) {
+ functor(&*(i++));
+ }
+ }
+
+ template <class TFunctor>
+ inline void ForEach(TFunctor&& functor) const {
+ TConstIterator i = Begin();
+
+ while (i != End()) {
+ functor(&*(i++));
+ }
+ }
+
+ template <class TComparer>
+ inline void QuickSort(TComparer&& comparer) {
+ if (Begin() == End() || ++Begin() == End()) {
+ return;
+ }
+
+ T* const pivot = PopFront();
+ TIntrusiveList bigger;
+ TIterator i = Begin();
+
+ while (i != End()) {
+ if (comparer(*pivot, *i)) {
+ bigger.PushBack(&*i++);
+ } else {
+ ++i;
+ }
+ }
+
+ this->QuickSort(comparer);
+ bigger.QuickSort(comparer);
+
+ PushBack(pivot);
+ Append(bigger);
+ }
+
+private:
+ inline TIntrusiveList(const TIntrusiveList&) = delete;
+ inline TIntrusiveList& operator=(const TIntrusiveList&) = delete;
+
+private:
+ TListItem End_;
+};
+
+template <class T, class D, class Tag>
+class TIntrusiveListWithAutoDelete: public TIntrusiveList<T, Tag> {
+public:
+ using TIterator = typename TIntrusiveList<T, Tag>::TIterator;
+ using TConstIterator = typename TIntrusiveList<T, Tag>::TConstIterator;
+
+ using TReverseIterator = typename TIntrusiveList<T, Tag>::TReverseIterator;
+ using TConstReverseIterator = typename TIntrusiveList<T, Tag>::TConstReverseIterator;
+
+ using iterator = TIterator;
+ using const_iterator = TConstIterator;
+
+ using reverse_iterator = TReverseIterator;
+ using const_reverse_iterator = TConstReverseIterator;
+
+public:
+ inline TIntrusiveListWithAutoDelete() noexcept = default;
+
+ inline TIntrusiveListWithAutoDelete(TIntrusiveListWithAutoDelete&& right) noexcept
+ : TIntrusiveList<T, Tag>(std::move(right))
+ {
+ }
+
+ inline ~TIntrusiveListWithAutoDelete() {
+ this->Clear();
+ }
+
+ TIntrusiveListWithAutoDelete& operator=(TIntrusiveListWithAutoDelete&& rhs) noexcept {
+ TIntrusiveList<T, Tag>::operator=(std::move(rhs));
+ return *this;
+ }
+
+public:
+ inline void Clear() noexcept {
+ this->ForEach([](auto* item) {
+ D::Destroy(item);
+ });
+ }
+
+ inline static void Cut(TIterator begin, TIterator end) noexcept {
+ TIntrusiveListWithAutoDelete<T, D, Tag> temp;
+ Cut(begin, end, temp.End());
+ }
+
+ inline static void Cut(TIterator begin, TIterator end, TIterator pasteBefore) noexcept {
+ TIntrusiveList<T, Tag>::Cut(begin, end, pasteBefore);
+ }
+};
+
+/*
+ * one-way linked list
+ */
+template <class T, class Tag = TIntrusiveListDefaultTag>
+class TIntrusiveSListItem {
+private:
+ using TListItem = TIntrusiveSListItem<T, Tag>;
+
+public:
+ inline TIntrusiveSListItem() noexcept
+ : Next_(nullptr)
+ {
+ }
+
+ inline ~TIntrusiveSListItem() = default;
+
+ inline bool IsEnd() const noexcept {
+ return Next_ == nullptr;
+ }
+
+ inline TListItem* Next() noexcept {
+ return Next_;
+ }
+
+ inline const TListItem* Next() const noexcept {
+ return Next_;
+ }
+
+ inline void SetNext(TListItem* item) noexcept {
+ Next_ = item;
+ }
+
+public:
+ inline T* Node() noexcept {
+ return static_cast<T*>(this);
+ }
+
+ inline const T* Node() const noexcept {
+ return static_cast<const T*>(this);
+ }
+
+private:
+ TListItem* Next_;
+};
+
+template <class T, class Tag>
+class TIntrusiveSList {
+private:
+ using TListItem = TIntrusiveSListItem<T, Tag>;
+
+public:
+ template <class TListItem, class TNode>
+ class TIteratorBase {
+ public:
+ using TItem = TListItem;
+ using TReference = TNode&;
+ using TPointer = TNode*;
+
+ using difference_type = std::ptrdiff_t;
+ using value_type = TNode;
+ using pointer = TPointer;
+ using reference = TReference;
+ using iterator_category = std::forward_iterator_tag;
+
+ inline TIteratorBase(TListItem* item) noexcept
+ : Item_(item)
+ {
+ }
+
+ inline void Next() noexcept {
+ Item_ = Item_->Next();
+ }
+
+ inline bool operator==(const TIteratorBase& right) const noexcept {
+ return Item_ == right.Item_;
+ }
+
+ inline bool operator!=(const TIteratorBase& right) const noexcept {
+ return Item_ != right.Item_;
+ }
+
+ inline TIteratorBase& operator++() noexcept {
+ Next();
+
+ return *this;
+ }
+
+ inline TIteratorBase operator++(int) noexcept {
+ TIteratorBase ret(*this);
+
+ Next();
+
+ return ret;
+ }
+
+ inline TNode& operator*() noexcept {
+ return *Item_->Node();
+ }
+
+ inline TNode* operator->() noexcept {
+ return Item_->Node();
+ }
+
+ private:
+ TListItem* Item_;
+ };
+
+public:
+ using TIterator = TIteratorBase<TListItem, T>;
+ using TConstIterator = TIteratorBase<const TListItem, const T>;
+
+ using iterator = TIterator;
+ using const_iterator = TConstIterator;
+
+public:
+ inline TIntrusiveSList() noexcept
+ : Begin_(nullptr)
+ {
+ }
+
+ inline void Swap(TIntrusiveSList& right) noexcept {
+ DoSwap(Begin_, right.Begin_);
+ }
+
+ inline explicit operator bool() const noexcept {
+ return !Empty();
+ }
+
+ Y_PURE_FUNCTION inline bool Empty() const noexcept {
+ return Begin_ == nullptr;
+ }
+
+ inline size_t Size() const noexcept {
+ return std::distance(Begin(), End());
+ }
+
+ inline void Clear() noexcept {
+ Begin_ = nullptr;
+ }
+
+ inline TIterator Begin() noexcept {
+ return TIterator(Begin_);
+ }
+
+ inline TIterator End() noexcept {
+ return TIterator(nullptr);
+ }
+
+ inline TConstIterator Begin() const noexcept {
+ return TConstIterator(Begin_);
+ }
+
+ inline TConstIterator End() const noexcept {
+ return TConstIterator(nullptr);
+ }
+
+ inline TConstIterator CBegin() const noexcept {
+ return Begin();
+ }
+
+ inline TConstIterator CEnd() const noexcept {
+ return End();
+ }
+
+ //compat methods
+ inline iterator begin() noexcept {
+ return Begin();
+ }
+
+ inline iterator end() noexcept {
+ return End();
+ }
+
+ inline const_iterator begin() const noexcept {
+ return Begin();
+ }
+
+ inline const_iterator end() const noexcept {
+ return End();
+ }
+
+ inline const_iterator cbegin() const noexcept {
+ return CBegin();
+ }
+
+ inline const_iterator cend() const noexcept {
+ return CEnd();
+ }
+
+ inline T* Front() noexcept {
+ Y_ASSERT(Begin_);
+ return Begin_->Node();
+ }
+
+ inline const T* Front() const noexcept {
+ Y_ASSERT(Begin_);
+ return Begin_->Node();
+ }
+
+ inline void PushFront(TListItem* item) noexcept {
+ item->SetNext(Begin_);
+ Begin_ = item;
+ }
+
+ inline T* PopFront() noexcept {
+ Y_ASSERT(Begin_);
+
+ TListItem* const ret = Begin_;
+ Begin_ = Begin_->Next();
+
+ return ret->Node();
+ }
+
+ inline void Reverse() noexcept {
+ TIntrusiveSList temp;
+
+ while (!Empty()) {
+ temp.PushFront(PopFront());
+ }
+
+ this->Swap(temp);
+ }
+
+ template <class TFunctor>
+ inline void ForEach(TFunctor&& functor) const noexcept(noexcept(functor(std::declval<TListItem>().Node()))) {
+ TListItem* i = Begin_;
+
+ while (i) {
+ TListItem* const next = i->Next();
+ functor(i->Node());
+ i = next;
+ }
+ }
+
+private:
+ TListItem* Begin_;
+};
diff --git a/util/generic/intrlist_ut.cpp b/util/generic/intrlist_ut.cpp
new file mode 100644
index 0000000000..eff7cdf2ee
--- /dev/null
+++ b/util/generic/intrlist_ut.cpp
@@ -0,0 +1,512 @@
+#include "intrlist.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/stream/output.h>
+
+class TListTest: public TTestBase {
+ UNIT_TEST_SUITE(TListTest);
+ UNIT_TEST(TestIterate);
+ UNIT_TEST(TestRIterate);
+ UNIT_TEST(TestForEach);
+ UNIT_TEST(TestForEachWithDelete);
+ UNIT_TEST(TestSize);
+ UNIT_TEST(TestQuickSort);
+ UNIT_TEST(TestCut);
+ UNIT_TEST(TestAppend);
+ UNIT_TEST(TestMoveCtor);
+ UNIT_TEST(TestMoveOpEq);
+ UNIT_TEST(TestListWithAutoDelete);
+ UNIT_TEST(TestListWithAutoDeleteMoveCtor);
+ UNIT_TEST(TestListWithAutoDeleteMoveOpEq);
+ UNIT_TEST(TestListWithAutoDeleteClear);
+ UNIT_TEST(TestSecondTag);
+ UNIT_TEST_SUITE_END();
+
+private:
+ void TestSize();
+ void TestIterate();
+ void TestRIterate();
+ void TestForEach();
+ void TestForEachWithDelete();
+ void TestQuickSort();
+ void TestCut();
+ void TestAppend();
+ void TestMoveCtor();
+ void TestMoveOpEq();
+ void TestListWithAutoDelete();
+ void TestListWithAutoDeleteMoveCtor();
+ void TestListWithAutoDeleteMoveOpEq();
+ void TestListWithAutoDeleteClear();
+ void TestSecondTag();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TListTest);
+
+class TInt: public TIntrusiveListItem<TInt> {
+public:
+ inline TInt(int value) noexcept
+ : Value_(value)
+ {
+ }
+
+ TInt(TInt&& rhs) noexcept
+ : Value_(rhs.Value_)
+ {
+ rhs.Value_ = 0xDEAD;
+ }
+
+ TInt& operator=(TInt&& rhs) noexcept {
+ Value_ = rhs.Value_;
+ rhs.Value_ = 0xBEEF;
+ return *this;
+ }
+
+ inline operator int&() noexcept {
+ return Value_;
+ }
+
+ inline operator const int&() const noexcept {
+ return Value_;
+ }
+
+private:
+ int Value_;
+};
+
+class TMyList: public TIntrusiveList<TInt> {
+public:
+ inline TMyList(int count) {
+ while (count > 0) {
+ PushFront(new TInt(count--));
+ }
+ }
+
+ //TMyList(const TMyList& rhs) = default;
+ TMyList(TMyList&& rhs) noexcept = default;
+
+ //operator=(const TMyList& rhs) = default;
+ TMyList& operator=(TMyList&& rhs) noexcept = default;
+
+ inline ~TMyList() {
+ while (!Empty()) {
+ delete PopBack();
+ }
+ }
+};
+
+struct TIntGreater: private TGreater<int> {
+ inline bool operator()(const TInt& l, const TInt& r) const noexcept {
+ return TGreater<int>::operator()(l, r);
+ }
+};
+
+void TListTest::TestQuickSort() {
+ TMyList l(1000);
+ size_t c = 0;
+
+ l.QuickSort(TIntGreater());
+
+ UNIT_ASSERT_EQUAL(l.Size(), 1000);
+
+ for (TMyList::TIterator it = l.Begin(); it != l.End(); ++it) {
+ UNIT_ASSERT_EQUAL(*it, int(1000 - c++));
+ }
+}
+
+void TListTest::TestSize() {
+ TMyList l(1024);
+
+ UNIT_ASSERT_EQUAL(l.Size(), 1024);
+}
+
+void TListTest::TestIterate() {
+ TMyList l(1000);
+ size_t c = 0;
+
+ for (TMyList::TIterator it = l.Begin(); it != l.End(); ++it) {
+ ++c;
+
+ UNIT_ASSERT_EQUAL(*it, (int)c);
+ }
+
+ UNIT_ASSERT_EQUAL(c, 1000);
+}
+
+void TListTest::TestRIterate() {
+ TMyList l(1000);
+ size_t c = 1000;
+
+ UNIT_ASSERT_EQUAL(l.RBegin(), TMyList::TReverseIterator(l.End()));
+ UNIT_ASSERT_EQUAL(l.REnd(), TMyList::TReverseIterator(l.Begin()));
+
+ for (TMyList::TReverseIterator it = l.RBegin(); it != l.REnd(); ++it) {
+ UNIT_ASSERT_EQUAL(*it, (int)c--);
+ }
+
+ UNIT_ASSERT_EQUAL(c, 0);
+}
+
+class TSum {
+public:
+ inline TSum(size_t& sum)
+ : Sum_(sum)
+ {
+ }
+
+ inline void operator()(const TInt* v) noexcept {
+ Sum_ += *v;
+ }
+
+private:
+ size_t& Sum_;
+};
+
+class TSumWithDelete {
+public:
+ inline TSumWithDelete(size_t& sum)
+ : Sum_(sum)
+ {
+ }
+
+ inline void operator()(TInt* v) noexcept {
+ if (*v % 2) {
+ Sum_ += *v;
+ } else {
+ delete v;
+ }
+ }
+
+private:
+ size_t& Sum_;
+};
+
+void TListTest::TestForEach() {
+ TMyList l(1000);
+ size_t sum = 0;
+ TSum functor(sum);
+
+ l.ForEach(functor);
+
+ UNIT_ASSERT_EQUAL(sum, 1000 * 1001 / 2);
+}
+
+void TListTest::TestForEachWithDelete() {
+ TMyList l(1000);
+ size_t sum = 0;
+ TSumWithDelete functor(sum);
+
+ l.ForEach(functor);
+
+ UNIT_ASSERT_EQUAL(sum, 500 * 500 /*== n * (x + y * (n - 1) / 2), x == 1, y == 2*/);
+}
+
+static void CheckIterationAfterCut(const TMyList& l, const TMyList& l2, size_t N, size_t M) {
+ size_t c = 0;
+ for (TMyList::TConstIterator it = l.Begin(); it != l.End(); ++it) {
+ ++c;
+
+ UNIT_ASSERT_EQUAL(*it, (int)c);
+ }
+
+ UNIT_ASSERT_EQUAL(c, M);
+
+ for (TMyList::TConstIterator it = l2.Begin(); it != l2.End(); ++it) {
+ ++c;
+
+ UNIT_ASSERT_EQUAL(*it, (int)c);
+ }
+
+ UNIT_ASSERT_EQUAL(c, N);
+
+ for (TMyList::TConstIterator it = l2.End(); it != l2.Begin(); --c) {
+ --it;
+
+ UNIT_ASSERT_EQUAL(*it, (int)c);
+ }
+
+ UNIT_ASSERT_EQUAL(c, M);
+
+ for (TMyList::TConstIterator it = l.End(); it != l.Begin(); --c) {
+ --it;
+
+ UNIT_ASSERT_EQUAL(*it, (int)c);
+ }
+
+ UNIT_ASSERT_EQUAL(c, 0);
+}
+
+static void TestCutFront(int N, int M) {
+ TMyList l(N);
+ TMyList l2(0);
+
+ TMyList::TIterator it = l.Begin();
+ for (int i = 0; i < M; ++i) {
+ ++it;
+ }
+
+ TMyList::Cut(l.Begin(), it, l2.End());
+ CheckIterationAfterCut(l2, l, N, M);
+}
+
+static void TestCutBack(int N, int M) {
+ TMyList l(N);
+ TMyList l2(0);
+
+ TMyList::TIterator it = l.Begin();
+ for (int i = 0; i < M; ++i) {
+ ++it;
+ }
+
+ TMyList::Cut(it, l.End(), l2.End());
+ CheckIterationAfterCut(l, l2, N, M);
+}
+
+void TListTest::TestCut() {
+ TestCutFront(1000, 500);
+ TestCutBack(1000, 500);
+ TestCutFront(1, 0);
+ TestCutBack(1, 0);
+ TestCutFront(1, 1);
+ TestCutBack(1, 1);
+ TestCutFront(2, 0);
+ TestCutBack(2, 0);
+ TestCutFront(2, 1);
+ TestCutBack(2, 1);
+ TestCutFront(2, 2);
+ TestCutBack(2, 2);
+}
+
+static void CheckIterationAfterAppend(const TMyList& l, size_t N, size_t M) {
+ TMyList::TConstIterator it = l.Begin();
+
+ for (size_t i = 1; i <= N; ++i, ++it) {
+ UNIT_ASSERT_EQUAL((int)i, *it);
+ }
+
+ for (size_t i = 1; i <= M; ++i, ++it) {
+ UNIT_ASSERT_EQUAL((int)i, *it);
+ }
+
+ UNIT_ASSERT_EQUAL(it, l.End());
+}
+
+static void TestAppend(int N, int M) {
+ TMyList l(N);
+ TMyList l2(M);
+ l.Append(l2);
+
+ UNIT_ASSERT(l2.Empty());
+ CheckIterationAfterAppend(l, N, M);
+}
+
+void TListTest::TestAppend() {
+ ::TestAppend(500, 500);
+ ::TestAppend(0, 0);
+ ::TestAppend(1, 0);
+ ::TestAppend(0, 1);
+ ::TestAppend(1, 1);
+}
+
+template <typename TListType>
+static void CheckList(const TListType& lst) {
+ int i = 1;
+ for (typename TListType::TConstIterator it = lst.Begin(); it != lst.End(); ++it, ++i) {
+ UNIT_ASSERT_EQUAL(*it, i);
+ }
+}
+
+void TListTest::TestMoveCtor() {
+ const int N{42};
+ TMyList lst{N};
+ UNIT_ASSERT(!lst.Empty());
+ UNIT_ASSERT_EQUAL(lst.Size(), N);
+
+ CheckList(lst);
+ TMyList nextLst{std::move(lst)};
+ UNIT_ASSERT(lst.Empty());
+ CheckList(nextLst);
+}
+
+void TListTest::TestMoveOpEq() {
+ const int N{42};
+ TMyList lst{N};
+ UNIT_ASSERT(!lst.Empty());
+ UNIT_ASSERT_EQUAL(lst.Size(), N);
+ CheckList(lst);
+
+ const int M{2};
+ TMyList nextLst(M);
+ UNIT_ASSERT(!nextLst.Empty());
+ UNIT_ASSERT_EQUAL(nextLst.Size(), M);
+ CheckList(nextLst);
+
+ nextLst = std::move(lst);
+ UNIT_ASSERT(!nextLst.Empty());
+ UNIT_ASSERT_EQUAL(nextLst.Size(), N);
+ CheckList(nextLst);
+}
+
+class TSelfCountingInt: public TIntrusiveListItem<TSelfCountingInt> {
+public:
+ TSelfCountingInt(int& counter, int value) noexcept
+ : Counter_(counter)
+ , Value_(value)
+ {
+ ++Counter_;
+ }
+
+ TSelfCountingInt(TSelfCountingInt&& rhs) noexcept
+ : Counter_(rhs.Counter_)
+ , Value_(rhs.Value_)
+ {
+ rhs.Value_ = 0xDEAD;
+ }
+
+ TSelfCountingInt& operator=(TSelfCountingInt&& rhs) noexcept {
+ Value_ = rhs.Value_;
+ rhs.Value_ = 0xBEEF;
+ return *this;
+ }
+
+ ~TSelfCountingInt() noexcept {
+ --Counter_;
+ }
+
+ inline operator int&() noexcept {
+ return Value_;
+ }
+
+ inline operator const int&() const noexcept {
+ return Value_;
+ }
+
+private:
+ int& Counter_;
+ int Value_;
+};
+
+struct TSelfCountingIntDelete {
+ static void Destroy(TSelfCountingInt* i) noexcept {
+ delete i;
+ }
+};
+
+void TListTest::TestListWithAutoDelete() {
+ using TListType = TIntrusiveListWithAutoDelete<TSelfCountingInt, TSelfCountingIntDelete>;
+ int counter{0};
+ {
+ TListType lst;
+ UNIT_ASSERT(lst.Empty());
+ lst.PushFront(new TSelfCountingInt(counter, 2));
+ UNIT_ASSERT_EQUAL(lst.Size(), 1);
+ UNIT_ASSERT_EQUAL(counter, 1);
+ lst.PushFront(new TSelfCountingInt(counter, 1));
+ UNIT_ASSERT_EQUAL(lst.Size(), 2);
+ UNIT_ASSERT_EQUAL(counter, 2);
+ CheckList(lst);
+ }
+
+ UNIT_ASSERT_EQUAL(counter, 0);
+}
+
+void TListTest::TestListWithAutoDeleteMoveCtor() {
+ using TListType = TIntrusiveListWithAutoDelete<TSelfCountingInt, TSelfCountingIntDelete>;
+ int counter{0};
+ {
+ TListType lst;
+ lst.PushFront(new TSelfCountingInt(counter, 2));
+ lst.PushFront(new TSelfCountingInt(counter, 1));
+ UNIT_ASSERT_EQUAL(lst.Size(), 2);
+ UNIT_ASSERT_EQUAL(counter, 2);
+ CheckList(lst);
+
+ TListType nextList(std::move(lst));
+ UNIT_ASSERT_EQUAL(nextList.Size(), 2);
+ CheckList(nextList);
+ UNIT_ASSERT_EQUAL(counter, 2);
+ }
+
+ UNIT_ASSERT_EQUAL(counter, 0);
+}
+
+void TListTest::TestListWithAutoDeleteMoveOpEq() {
+ using TListType = TIntrusiveListWithAutoDelete<TSelfCountingInt, TSelfCountingIntDelete>;
+ int counter{0};
+ {
+ TListType lst;
+ lst.PushFront(new TSelfCountingInt(counter, 2));
+ lst.PushFront(new TSelfCountingInt(counter, 1));
+ UNIT_ASSERT_EQUAL(lst.Size(), 2);
+ UNIT_ASSERT_EQUAL(counter, 2);
+ CheckList(lst);
+
+ TListType nextList;
+ UNIT_ASSERT(nextList.Empty());
+ nextList = std::move(lst);
+ UNIT_ASSERT_EQUAL(nextList.Size(), 2);
+ CheckList(nextList);
+ UNIT_ASSERT_EQUAL(counter, 2);
+ }
+
+ UNIT_ASSERT_EQUAL(counter, 0);
+}
+
+void TListTest::TestListWithAutoDeleteClear() {
+ using TListType = TIntrusiveListWithAutoDelete<TSelfCountingInt, TSelfCountingIntDelete>;
+ int counter{0};
+ {
+ TListType lst;
+ UNIT_ASSERT(lst.Empty());
+ lst.PushFront(new TSelfCountingInt(counter, 2));
+ UNIT_ASSERT_EQUAL(lst.Size(), 1);
+ UNIT_ASSERT_EQUAL(counter, 1);
+ lst.PushFront(new TSelfCountingInt(counter, 1));
+ UNIT_ASSERT_EQUAL(lst.Size(), 2);
+ UNIT_ASSERT_EQUAL(counter, 2);
+ CheckList(lst);
+
+ lst.Clear();
+ UNIT_ASSERT(lst.Empty());
+ UNIT_ASSERT_EQUAL(counter, 0);
+ lst.PushFront(new TSelfCountingInt(counter, 1));
+ UNIT_ASSERT_EQUAL(lst.Size(), 1);
+ }
+
+ UNIT_ASSERT_EQUAL(counter, 0);
+}
+
+struct TSecondTag {};
+
+class TDoubleNode
+ : public TInt,
+ public TIntrusiveListItem<TDoubleNode, TSecondTag> {
+public:
+ TDoubleNode(int value) noexcept
+ : TInt(value)
+ {
+ }
+};
+
+void TListTest::TestSecondTag() {
+ TDoubleNode zero(0), one(1);
+ TIntrusiveList<TInt> first;
+ TIntrusiveList<TDoubleNode, TSecondTag> second;
+
+ first.PushFront(&zero);
+ first.PushFront(&one);
+ second.PushBack(&zero);
+ second.PushBack(&one);
+
+ UNIT_ASSERT_EQUAL(*first.Front(), 1);
+ UNIT_ASSERT_EQUAL(*++first.Begin(), 0);
+ UNIT_ASSERT_EQUAL(*first.Back(), 0);
+
+ UNIT_ASSERT_EQUAL(*second.Front(), 0);
+ UNIT_ASSERT_EQUAL(*++second.Begin(), 1);
+ UNIT_ASSERT_EQUAL(*second.Back(), 1);
+
+ second.Remove(&zero);
+ UNIT_ASSERT_EQUAL(*second.Front(), 1);
+ UNIT_ASSERT_EQUAL(*first.Back(), 0);
+}
diff --git a/util/generic/is_in.cpp b/util/generic/is_in.cpp
new file mode 100644
index 0000000000..4edfd1fe10
--- /dev/null
+++ b/util/generic/is_in.cpp
@@ -0,0 +1 @@
+#include "is_in.h"
diff --git a/util/generic/is_in.h b/util/generic/is_in.h
new file mode 100644
index 0000000000..4f175ea5eb
--- /dev/null
+++ b/util/generic/is_in.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "typetraits.h"
+
+#include <algorithm>
+#include <initializer_list>
+
+template <class I, class T>
+static inline bool IsIn(I f, I l, const T& v);
+
+template <class C, class T>
+static inline bool IsIn(const C& c, const T& e);
+
+namespace NIsInHelper {
+ Y_HAS_MEMBER(find, FindMethod);
+ Y_HAS_SUBTYPE(const_iterator, ConstIterator);
+ Y_HAS_SUBTYPE(key_type, KeyType);
+
+ template <class T>
+ using TIsAssocCont = TConjunction<THasFindMethod<T>, THasConstIterator<T>, THasKeyType<T>>;
+
+ template <class C, class T, bool isAssoc>
+ struct TIsInTraits {
+ static bool IsIn(const C& c, const T& e) {
+ using std::begin;
+ using std::end;
+ return ::IsIn(begin(c), end(c), e);
+ }
+ };
+
+ template <class C, class T>
+ struct TIsInTraits<C, T, true> {
+ static bool IsIn(const C& c, const T& e) {
+ return c.find(e) != c.end();
+ }
+ };
+}
+
+template <class I, class T>
+static inline bool IsIn(I f, I l, const T& v) {
+ return std::find(f, l, v) != l;
+}
+
+template <class C, class T>
+static inline bool IsIn(const C& c, const T& e) {
+ using namespace NIsInHelper;
+ return TIsInTraits<C, T, TIsAssocCont<C>::value>::IsIn(c, e);
+}
+
+template <class T, class U>
+static inline bool IsIn(std::initializer_list<T> l, const U& e) {
+ return ::IsIn(l.begin(), l.end(), e);
+}
diff --git a/util/generic/is_in_ut.cpp b/util/generic/is_in_ut.cpp
new file mode 100644
index 0000000000..c668bce807
--- /dev/null
+++ b/util/generic/is_in_ut.cpp
@@ -0,0 +1,116 @@
+#include <library/cpp/testing/unittest/registar.h>
+
+#include "algorithm.h"
+#include "hash.h"
+#include "hash_set.h"
+#include "is_in.h"
+#include "map.h"
+#include "set.h"
+#include "strbuf.h"
+#include "string.h"
+
+Y_UNIT_TEST_SUITE(TIsIn) {
+ template <class TCont, class T>
+ void TestIsInWithCont(const T& elem) {
+ class TMapMock: public TCont {
+ public:
+ typename TCont::const_iterator find(const typename TCont::key_type& k) const {
+ ++FindCalled;
+ return TCont::find(k);
+ }
+
+ typename TCont::iterator find(const typename TCont::key_type& k) {
+ ++FindCalled;
+ return TCont::find(k);
+ }
+
+ mutable size_t FindCalled = 1;
+ };
+
+ TMapMock m;
+ m.insert(elem);
+
+ // use more effective find method
+ UNIT_ASSERT(IsIn(m, "found"));
+ UNIT_ASSERT(m.FindCalled);
+ m.FindCalled = 0;
+
+ UNIT_ASSERT(!IsIn(m, "not found"));
+ UNIT_ASSERT(m.FindCalled);
+ m.FindCalled = 0;
+ }
+
+ Y_UNIT_TEST(IsInTest) {
+ TestIsInWithCont<TMap<TString, TString>>(std::make_pair("found", "1"));
+ TestIsInWithCont<TMultiMap<TString, TString>>(std::make_pair("found", "1"));
+ TestIsInWithCont<THashMap<TString, TString>>(std::make_pair("found", "1"));
+ TestIsInWithCont<THashMultiMap<TString, TString>>(std::make_pair("found", "1"));
+
+ TestIsInWithCont<TSet<TString>>("found");
+ TestIsInWithCont<TMultiSet<TString>>("found");
+ TestIsInWithCont<THashSet<TString>>("found");
+ TestIsInWithCont<THashMultiSet<TString>>("found");
+
+ // vector also compiles and works
+ TVector<TString> v;
+ v.push_back("found");
+ UNIT_ASSERT(IsIn(v, "found"));
+ UNIT_ASSERT(!IsIn(v, "not found"));
+
+ // iterators interface
+ UNIT_ASSERT(IsIn(v.begin(), v.end(), "found"));
+ UNIT_ASSERT(!IsIn(v.begin(), v.end(), "not found"));
+
+ // Works with TString (it has find, but find is not used)
+ TString s = "found";
+ UNIT_ASSERT(IsIn(s, 'f'));
+ UNIT_ASSERT(!IsIn(s, 'z'));
+
+ TStringBuf b = "found";
+ UNIT_ASSERT(IsIn(b, 'f'));
+ UNIT_ASSERT(!IsIn(b, 'z'));
+ }
+
+ Y_UNIT_TEST(IsInInitListTest) {
+ const char* abc = "abc";
+ const char* def = "def";
+
+ UNIT_ASSERT(IsIn({6, 2, 12}, 6));
+ UNIT_ASSERT(IsIn({6, 2, 12}, 2));
+ UNIT_ASSERT(!IsIn({6, 2, 12}, 7));
+ UNIT_ASSERT(IsIn({6}, 6));
+ UNIT_ASSERT(!IsIn({6}, 7));
+ UNIT_ASSERT(!IsIn(std::initializer_list<int>(), 6));
+ UNIT_ASSERT(IsIn({TStringBuf("abc"), TStringBuf("def")}, TStringBuf("abc")));
+ UNIT_ASSERT(IsIn({TStringBuf("abc"), TStringBuf("def")}, TStringBuf("def")));
+ UNIT_ASSERT(IsIn({"abc", "def"}, TStringBuf("def")));
+ UNIT_ASSERT(IsIn({abc, def}, def)); // direct pointer comparison
+ UNIT_ASSERT(!IsIn({TStringBuf("abc"), TStringBuf("def")}, TStringBuf("ghi")));
+ UNIT_ASSERT(!IsIn({"abc", "def"}, TStringBuf("ghi")));
+ UNIT_ASSERT(!IsIn({"abc", "def"}, TString("ghi")));
+
+ const TStringBuf str = "abc////";
+
+ UNIT_ASSERT(IsIn({"abc", "def"}, TStringBuf{str.data(), 3}));
+ }
+
+ Y_UNIT_TEST(ConfOfTest) {
+ UNIT_ASSERT(IsIn({1, 2, 3}, 1));
+ UNIT_ASSERT(!IsIn({1, 2, 3}, 4));
+
+ const TString b = "b";
+
+ UNIT_ASSERT(!IsIn({"a", "b", "c"}, b.data())); // compares pointers by value. Whether it's good or not.
+ UNIT_ASSERT(IsIn(TVector<TStringBuf>({"a", "b", "c"}), b.data()));
+ UNIT_ASSERT(IsIn(TVector<TStringBuf>({"a", "b", "c"}), "b"));
+ }
+
+ Y_UNIT_TEST(IsInArrayTest) {
+ const TString array[] = {"a", "b", "d"};
+
+ UNIT_ASSERT(IsIn(array, "a"));
+ UNIT_ASSERT(IsIn(array, TString("b")));
+ UNIT_ASSERT(!IsIn(array, "c"));
+ UNIT_ASSERT(IsIn(array, TStringBuf("d")));
+ }
+}
diff --git a/util/generic/iterator.cpp b/util/generic/iterator.cpp
new file mode 100644
index 0000000000..7c5c206cc3
--- /dev/null
+++ b/util/generic/iterator.cpp
@@ -0,0 +1 @@
+#include "iterator.h"
diff --git a/util/generic/iterator.h b/util/generic/iterator.h
new file mode 100644
index 0000000000..19e9d20976
--- /dev/null
+++ b/util/generic/iterator.h
@@ -0,0 +1,139 @@
+#pragma once
+
+#include <iterator>
+#include <utility>
+
+namespace NStlIterator {
+ template <class T>
+ class TProxy {
+ public:
+ TProxy() = default;
+ TProxy(T&& value)
+ : Value_(std::move(value))
+ {
+ }
+
+ const T* operator->() const noexcept {
+ return &Value_;
+ }
+
+ const T& operator*() const noexcept {
+ return Value_;
+ }
+
+ bool operator==(const TProxy& rhs) const {
+ return Value_ == rhs.Value_;
+ }
+
+ private:
+ T Value_;
+ };
+} // namespace NStlIterator
+
+/**
+ * Range adaptor that turns a derived class with a Java-style iteration
+ * interface into an STL range.
+ *
+ * Derived class is expected to define:
+ * \code
+ * TSomething* Next();
+ * \endcode
+ *
+ * `Next()` returning `nullptr` signals end of range. Note that you can also use
+ * pointer-like types instead of actual pointers (e.g. `TAtomicSharedPtr`).
+ *
+ * Since iteration state is stored inside the derived class, the resulting range
+ * is an input range (works for single pass algorithms only). Technically speaking,
+ * if you're returning a non-const pointer from `Next`, it can also work as an output range.
+ *
+ * Example usage:
+ * \code
+ * class TSquaresGenerator: public TInputRangeAdaptor<TSquaresGenerator> {
+ * public:
+ * const double* Next() {
+ * Current_ = State_ * State_;
+ * State_ += 1.0;
+ * // Never return nullptr => we have infinite range!
+ * return &Current_;
+ * }
+ *
+ * private:
+ * double State_ = 0.0;
+ * double Current_ = 0.0;
+ * }
+ * \endcode
+ */
+template <class TSlave>
+class TInputRangeAdaptor {
+public: // TODO: private
+ class TIterator {
+ public:
+ static constexpr bool IsNoexceptNext = noexcept(std::declval<TSlave>().Next());
+
+ using difference_type = std::ptrdiff_t;
+ using pointer = decltype(std::declval<TSlave>().Next());
+ using reference = decltype(*std::declval<TSlave>().Next());
+ using value_type = std::remove_cv_t<std::remove_reference_t<reference>>;
+ using iterator_category = std::input_iterator_tag;
+
+ inline TIterator() noexcept
+ : Slave_(nullptr)
+ , Cur_()
+ {
+ }
+
+ inline TIterator(TSlave* slave) noexcept(IsNoexceptNext)
+ : Slave_(slave)
+ , Cur_(Slave_->Next())
+ {
+ }
+
+ inline bool operator==(const TIterator& it) const noexcept {
+ return Cur_ == it.Cur_;
+ }
+
+ inline bool operator!=(const TIterator& it) const noexcept {
+ return !(*this == it);
+ }
+
+ inline pointer operator->() const noexcept {
+ return Cur_;
+ }
+
+ inline reference operator*() const noexcept {
+ return *Cur_;
+ }
+
+ inline TIterator& operator++() noexcept(IsNoexceptNext) {
+ Cur_ = Slave_->Next();
+
+ return *this;
+ }
+
+ private:
+ TSlave* Slave_;
+ pointer Cur_;
+ };
+
+public:
+ using const_iterator = TIterator;
+ using iterator = const_iterator;
+
+ inline iterator begin() const noexcept(TIterator::IsNoexceptNext) {
+ return TIterator(const_cast<TSlave*>(static_cast<const TSlave*>(this)));
+ }
+
+ inline iterator end() const noexcept {
+ return TIterator();
+ }
+};
+
+/**
+ * Transform given reverse iterator into forward iterator pointing to the same element.
+ *
+ * @see http://stackoverflow.com/a/1830240
+ */
+template <class TIterator>
+auto ToForwardIterator(TIterator iter) {
+ return std::next(iter).base();
+}
diff --git a/util/generic/iterator_range.cpp b/util/generic/iterator_range.cpp
new file mode 100644
index 0000000000..bd8d7ff81b
--- /dev/null
+++ b/util/generic/iterator_range.cpp
@@ -0,0 +1 @@
+#include "iterator_range.h"
diff --git a/util/generic/iterator_range.h b/util/generic/iterator_range.h
new file mode 100644
index 0000000000..9f4d02da29
--- /dev/null
+++ b/util/generic/iterator_range.h
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <util/system/yassert.h>
+
+#include <iterator>
+#include <utility>
+
+template <typename TBegin, typename TEnd = TBegin>
+struct TIteratorRange {
+ using TElement = std::remove_reference_t<decltype(*std::declval<TBegin>())>;
+
+ TIteratorRange(TBegin begin, TEnd end)
+ : Begin_(begin)
+ , End_(end)
+ {
+ }
+
+ TIteratorRange()
+ : TIteratorRange(TBegin{}, TEnd{})
+ {
+ }
+
+ TBegin begin() const {
+ return Begin_;
+ }
+
+ TEnd end() const {
+ return End_;
+ }
+
+ bool empty() const {
+ // because range based for requires exactly '!='
+ return !(Begin_ != End_);
+ }
+
+private:
+ TBegin Begin_;
+ TEnd End_;
+};
+
+template <class TIterator>
+class TIteratorRange<TIterator, TIterator> {
+public:
+ using iterator = TIterator;
+ using const_iterator = TIterator;
+ using value_type = typename std::iterator_traits<iterator>::value_type;
+ using reference = typename std::iterator_traits<iterator>::reference;
+ using const_reference = typename std::iterator_traits<const_iterator>::reference;
+ using difference_type = typename std::iterator_traits<iterator>::difference_type;
+ using size_type = std::size_t;
+
+ TIteratorRange()
+ : Begin_()
+ , End_()
+ {
+ }
+
+ TIteratorRange(TIterator begin, TIterator end)
+ : Begin_(begin)
+ , End_(end)
+ {
+ }
+
+ TIterator begin() const {
+ return Begin_;
+ }
+
+ TIterator end() const {
+ return End_;
+ }
+
+ Y_PURE_FUNCTION bool empty() const {
+ return Begin_ == End_;
+ }
+
+ size_type size() const {
+ return End_ - Begin_;
+ }
+
+ reference operator[](size_t at) const {
+ Y_ASSERT(at < size());
+
+ return *(Begin_ + at);
+ }
+
+private:
+ TIterator Begin_;
+ TIterator End_;
+};
+
+template <class TIterator>
+TIteratorRange<TIterator> MakeIteratorRange(TIterator begin, TIterator end) {
+ return TIteratorRange<TIterator>(begin, end);
+}
+
+template <class TIterator>
+TIteratorRange<TIterator> MakeIteratorRange(const std::pair<TIterator, TIterator>& range) {
+ return TIteratorRange<TIterator>(range.first, range.second);
+}
+
+template <class TBegin, class TEnd>
+TIteratorRange<TBegin, TEnd> MakeIteratorRange(TBegin begin, TEnd end) {
+ return {begin, end};
+}
diff --git a/util/generic/iterator_range_ut.cpp b/util/generic/iterator_range_ut.cpp
new file mode 100644
index 0000000000..a7e3670ae1
--- /dev/null
+++ b/util/generic/iterator_range_ut.cpp
@@ -0,0 +1,98 @@
+#include "iterator_range.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+#include <util/generic/algorithm.h>
+#include <util/generic/vector.h>
+
+Y_UNIT_TEST_SUITE(IteratorRange) {
+ Y_UNIT_TEST(DefaultConstructor) {
+ TIteratorRange<int*> range;
+ UNIT_ASSERT(range.empty());
+ }
+
+ Y_UNIT_TEST(DefaultConstructorSentinel) {
+ TIteratorRange<int*, void*> range;
+ UNIT_ASSERT(range.empty());
+ }
+
+ Y_UNIT_TEST(RangeBasedForLoop) {
+ // compileability test
+ for (int i : TIteratorRange<int*>()) {
+ Y_UNUSED(i);
+ }
+ }
+
+ Y_UNIT_TEST(RangeBasedForLoopSentinel) {
+ // compileability test
+ for (int i : TIteratorRange<int*, void*>()) {
+ Y_UNUSED(i);
+ }
+ }
+
+ Y_UNIT_TEST(Works) {
+ const int values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ auto range = MakeIteratorRange(values, values + Y_ARRAY_SIZE(values));
+ UNIT_ASSERT_VALUES_EQUAL(range.size(), Y_ARRAY_SIZE(values));
+ UNIT_ASSERT(Equal(range.begin(), range.end(), values));
+ UNIT_ASSERT(!range.empty());
+ }
+
+ Y_UNIT_TEST(WorksSentinel) {
+ struct TRangeSentinel {
+ };
+
+ struct TEnumerator {
+ ui32 operator*() const {
+ return Cur;
+ }
+
+ void operator++() {
+ ++Cur;
+ }
+
+ bool operator!=(const TRangeSentinel&) const {
+ return Cur < End;
+ }
+
+ ui32 Cur;
+ ui32 End;
+ };
+
+ auto range = MakeIteratorRange(TEnumerator{0, 10}, TRangeSentinel{});
+ UNIT_ASSERT(!range.empty());
+
+ ui32 i = 0;
+ for (auto j : range) {
+ UNIT_ASSERT_VALUES_EQUAL(j, i++);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(i, 10);
+ }
+
+ Y_UNIT_TEST(OperatorsAndReferences) {
+ TVector<size_t> values{1, 2, 3, 4, 5};
+ auto range = MakeIteratorRange(values.begin(), values.end());
+ UNIT_ASSERT_VALUES_EQUAL(range[2], 3);
+ UNIT_ASSERT_VALUES_EQUAL(range[range[2]], 4);
+ *values.begin() = 100500;
+ UNIT_ASSERT_VALUES_EQUAL(range[0], 100500);
+ range[0] = 100501;
+ UNIT_ASSERT_VALUES_EQUAL(range[0], 100501);
+
+ TVector<bool> valuesBool{false, true, false, false, false, false, true};
+ auto rangeBVector = MakeIteratorRange(valuesBool.begin(), valuesBool.end());
+ UNIT_ASSERT_VALUES_EQUAL(rangeBVector[1], true);
+ rangeBVector[0] = true;
+ valuesBool.back() = false;
+ UNIT_ASSERT_VALUES_EQUAL(rangeBVector[0], true);
+ UNIT_ASSERT_VALUES_EQUAL(rangeBVector[2], false);
+ UNIT_ASSERT_VALUES_EQUAL(rangeBVector[6], false);
+ }
+
+ Y_UNIT_TEST(CanUseInAlgorithms) {
+ const int values[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ auto range = MakeIteratorRange(values, values + Y_ARRAY_SIZE(values));
+ // more like compileability test
+ // we should be able to use TIteratorRange as a container parameter for standard algorithms
+ UNIT_ASSERT(AllOf(range, [](int x) { return x > 0; }));
+ }
+}
diff --git a/util/generic/iterator_ut.cpp b/util/generic/iterator_ut.cpp
new file mode 100644
index 0000000000..00be19e10e
--- /dev/null
+++ b/util/generic/iterator_ut.cpp
@@ -0,0 +1,63 @@
+#include "iterator.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TIterator) {
+ Y_UNIT_TEST(ToForwardIteratorTest) {
+ TVector<int> x = {1, 2};
+ UNIT_ASSERT_VALUES_EQUAL(*std::prev(x.end()), *ToForwardIterator(x.rbegin()));
+ UNIT_ASSERT_VALUES_EQUAL(*ToForwardIterator(std::prev(x.rend())), *x.begin());
+ }
+}
+
+Y_UNIT_TEST_SUITE(TInputRangeAdaptor) {
+ class TSquaresGenerator: public TInputRangeAdaptor<TSquaresGenerator> {
+ public:
+ const i64* Next() {
+ Current_ = State_ * State_;
+ ++State_;
+ // Never return nullptr => we have infinite range!
+ return &Current_;
+ }
+
+ private:
+ i64 State_ = 0.0;
+ i64 Current_ = 0.0;
+ };
+
+ Y_UNIT_TEST(TSquaresGenerator) {
+ i64 cur = 0;
+ for (i64 sqr : TSquaresGenerator{}) {
+ UNIT_ASSERT_VALUES_EQUAL(cur * cur, sqr);
+
+ if (++cur > 10) {
+ break;
+ }
+ }
+ }
+
+ class TUrlPart: public TInputRangeAdaptor<TUrlPart> {
+ public:
+ TUrlPart(const TStringBuf& url)
+ : Url_(url)
+ {
+ }
+
+ NStlIterator::TProxy<TStringBuf> Next() {
+ return Url_.NextTok('/');
+ }
+
+ private:
+ TStringBuf Url_;
+ };
+
+ Y_UNIT_TEST(TUrlPart) {
+ const TVector<TStringBuf> expected = {TStringBuf("yandex.ru"), TStringBuf("search?")};
+ auto expected_part = expected.begin();
+ for (const TStringBuf& part : TUrlPart(TStringBuf("yandex.ru/search?"))) {
+ UNIT_ASSERT_VALUES_EQUAL(part, *expected_part);
+ ++expected_part;
+ }
+ UNIT_ASSERT(expected_part == expected.end());
+ }
+}
diff --git a/util/generic/lazy_value.cpp b/util/generic/lazy_value.cpp
new file mode 100644
index 0000000000..e687ec1a59
--- /dev/null
+++ b/util/generic/lazy_value.cpp
@@ -0,0 +1 @@
+#include "lazy_value.h"
diff --git a/util/generic/lazy_value.h b/util/generic/lazy_value.h
new file mode 100644
index 0000000000..3c720f76b5
--- /dev/null
+++ b/util/generic/lazy_value.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "maybe.h"
+#include "function.h"
+
+template <class T>
+class TLazyValueBase {
+public:
+ using TInitializer = std::function<T()>;
+
+ TLazyValueBase() = default;
+
+ TLazyValueBase(TInitializer initializer)
+ : Initializer(std::move(initializer))
+ {
+ }
+
+ explicit operator bool() const noexcept {
+ return Defined();
+ }
+
+ bool Defined() const noexcept {
+ return ValueHolder.Defined();
+ }
+
+ const T& GetRef() const {
+ if (!Defined()) {
+ InitDefault();
+ }
+ return *ValueHolder;
+ }
+
+ const T& operator*() const {
+ return GetRef();
+ }
+
+ const T* operator->() const {
+ return &GetRef();
+ }
+
+ void InitDefault() const {
+ Y_ASSERT(Initializer);
+ ValueHolder = Initializer();
+ }
+
+private:
+ mutable TMaybe<T> ValueHolder;
+ TInitializer Initializer;
+};
+
+// we need this to get implicit construction TLazyValue from lambda
+// and save default copy constructor and operator= for type TLazyValue
+template <class T>
+class TLazyValue: public TLazyValueBase<T> {
+public:
+ template <typename... TArgs>
+ TLazyValue(TArgs&&... args)
+ : TLazyValueBase<T>(std::forward<TArgs>(args)...)
+ {
+ }
+};
+
+template <typename F>
+TLazyValue<TFunctionResult<F>> MakeLazy(F&& f) {
+ return {std::forward<F>(f)};
+}
diff --git a/util/generic/lazy_value_ut.cpp b/util/generic/lazy_value_ut.cpp
new file mode 100644
index 0000000000..f6135880c3
--- /dev/null
+++ b/util/generic/lazy_value_ut.cpp
@@ -0,0 +1,157 @@
+#include "lazy_value.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TLazyValueTestSuite) {
+ Y_UNIT_TEST(TestLazyValue) {
+ TLazyValue<int> value([]() {
+ return 5;
+ });
+ UNIT_ASSERT(!value);
+ UNIT_ASSERT_EQUAL(*value, 5);
+ UNIT_ASSERT(value);
+ }
+
+ Y_UNIT_TEST(TestLazyValueInitialization) {
+ TLazyValue<int> value1([]() { return 5; });
+
+ TLazyValue<int> value2 = []() { return 5; };
+
+ TLazyValue<int> notInitialized{};
+
+ TLazyValue<int> copy1(value1);
+
+ copy1 = value2;
+ }
+
+ Y_UNIT_TEST(TestLazyValueCopy) {
+ TLazyValue<int> value([]() { return 5; });
+ UNIT_ASSERT(!value);
+
+ TLazyValue<int> emptyCopy = value;
+ UNIT_ASSERT(!emptyCopy);
+
+ UNIT_ASSERT_EQUAL(*emptyCopy, 5);
+ UNIT_ASSERT(emptyCopy);
+ UNIT_ASSERT(!value);
+
+ UNIT_ASSERT_EQUAL(*value, 5);
+
+ TLazyValue<int> notEmptyCopy = value;
+ UNIT_ASSERT(notEmptyCopy);
+ UNIT_ASSERT_EQUAL(*notEmptyCopy, 5);
+ }
+
+ struct TCopyCounter {
+ TCopyCounter(size_t& numCopies)
+ : NumCopies(&numCopies)
+ {
+ }
+
+ TCopyCounter() = default;
+
+ TCopyCounter(const TCopyCounter& other)
+ : NumCopies(other.NumCopies)
+ {
+ ++(*NumCopies);
+ }
+
+ TCopyCounter(TCopyCounter&&) = default;
+
+ TCopyCounter& operator=(const TCopyCounter& other) {
+ if (this == &other) {
+ return *this;
+ }
+ NumCopies = other.NumCopies;
+ ++(*NumCopies);
+ return *this;
+ }
+
+ TCopyCounter& operator=(TCopyCounter&&) = default;
+
+ size_t* NumCopies = nullptr;
+ };
+
+ Y_UNIT_TEST(TestLazyValueMoveValueInitialization) {
+ size_t numCopies = 0;
+ TCopyCounter counter{numCopies};
+ TLazyValue<TCopyCounter> value{[v = std::move(counter)]() mutable { return std::move(v); }};
+ value.InitDefault();
+ UNIT_ASSERT_EQUAL(numCopies, 0);
+ }
+
+ Y_UNIT_TEST(TestLazyValueCopyValueInitialization) {
+ size_t numCopies = 0;
+ TCopyCounter counter{numCopies};
+ TLazyValue<TCopyCounter> value{[&counter]() { return counter; }};
+ UNIT_ASSERT_EQUAL(numCopies, 0);
+ value.InitDefault();
+ UNIT_ASSERT_EQUAL(numCopies, 1);
+ }
+
+ class TValueProvider {
+ public:
+ static size_t CountParseDataCalled;
+
+ TValueProvider()
+ : Data_([&] { return this->ParseData(); })
+ {
+ }
+
+ const TString& GetData() const {
+ return *Data_;
+ }
+
+ private:
+ TLazyValue<TString> Data_;
+
+ TString ParseData() {
+ CountParseDataCalled++;
+ return "hi";
+ }
+ };
+
+ size_t TValueProvider::CountParseDataCalled = 0;
+
+ Y_UNIT_TEST(TestValueProvider) {
+ TValueProvider provider;
+
+ UNIT_ASSERT(provider.GetData() == "hi");
+ }
+
+ Y_UNIT_TEST(TestValueProviderCopy) {
+ TValueProvider provider;
+ provider.GetData();
+ const auto countParsed = TValueProvider::CountParseDataCalled;
+ provider.GetData();
+ UNIT_ASSERT_EQUAL(countParsed, TValueProvider::CountParseDataCalled);
+
+ TValueProvider providerCopy;
+ providerCopy = provider;
+ providerCopy.GetData();
+ UNIT_ASSERT_EQUAL(countParsed, TValueProvider::CountParseDataCalled);
+ }
+
+ Y_UNIT_TEST(TestEmptyProviderCopy) {
+ TValueProvider provider;
+ TValueProvider copy(provider);
+
+ const auto countParsed = TValueProvider::CountParseDataCalled;
+ provider.GetData();
+ UNIT_ASSERT_EQUAL(countParsed + 1, TValueProvider::CountParseDataCalled);
+ copy.GetData();
+ UNIT_ASSERT_EQUAL(countParsed + 2, TValueProvider::CountParseDataCalled);
+ const TValueProvider notEmptyCopy(copy);
+ notEmptyCopy.GetData();
+ UNIT_ASSERT_EQUAL(countParsed + 2, TValueProvider::CountParseDataCalled);
+ }
+
+ Y_UNIT_TEST(TestMakeLazy) {
+ auto lv = MakeLazy([] {
+ return 100500;
+ });
+ UNIT_ASSERT(!lv);
+ UNIT_ASSERT(lv.GetRef() == 100500);
+ UNIT_ASSERT(lv);
+ }
+}
diff --git a/util/generic/list.cpp b/util/generic/list.cpp
new file mode 100644
index 0000000000..471c2a14b7
--- /dev/null
+++ b/util/generic/list.cpp
@@ -0,0 +1 @@
+#include "list.h"
diff --git a/util/generic/list.h b/util/generic/list.h
new file mode 100644
index 0000000000..7b0b8ffc72
--- /dev/null
+++ b/util/generic/list.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "fwd.h"
+
+#include <util/memory/alloc.h>
+
+#include <initializer_list>
+#include <list>
+#include <memory>
+#include <utility>
+
+template <class T, class A>
+class TList: public std::list<T, TReboundAllocator<A, T>> {
+ using TBase = std::list<T, TReboundAllocator<A, T>>;
+
+public:
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+};
diff --git a/util/generic/list.pxd b/util/generic/list.pxd
new file mode 100644
index 0000000000..5f3d92eac1
--- /dev/null
+++ b/util/generic/list.pxd
@@ -0,0 +1,65 @@
+cdef extern from "util/generic/list.h":
+ cdef cppclass TList[T]:
+ TList() except +
+ TList(TList&) except +
+ TList(size_t, T&) except +
+
+ cppclass iterator:
+ iterator()
+ iterator(iterator &)
+ T& operator*()
+ iterator operator++()
+ iterator operator--()
+ bint operator==(iterator)
+ bint operator!=(iterator)
+ cppclass reverse_iterator:
+ reverse_iterator()
+ reverse_iterator(iterator &)
+ T& operator*()
+ reverse_iterator operator++()
+ reverse_iterator operator--()
+ bint operator==(reverse_iterator)
+ bint operator!=(reverse_iterator)
+ cppclass const_iterator(iterator):
+ pass
+ cppclass const_reverse_iterator(reverse_iterator):
+ pass
+ bint operator==(TList&, TList&)
+ bint operator!=(TList&, TList&)
+ bint operator<(TList&, TList&)
+ bint operator>(TList&, TList&)
+ bint operator<=(TList&, TList&)
+ bint operator>=(TList&, TList&)
+ void assign(size_t, T&)
+ T& back()
+ iterator begin()
+ const_iterator const_begin "begin"()
+ void clear()
+ bint empty()
+ iterator end()
+ const_iterator const_end "end"()
+ iterator erase(iterator)
+ iterator erase(iterator, iterator)
+ T& front()
+ iterator insert(iterator, T&)
+ void insert(iterator, size_t, T&)
+ size_t max_size()
+ void merge(TList&)
+ void pop_back()
+ void pop_front()
+ void push_back(T&)
+ void push_front(T&)
+ reverse_iterator rbegin()
+ const_reverse_iterator const_rbegin "rbegin"()
+ void remove(T&)
+ reverse_iterator rend()
+ const_reverse_iterator const_rend "rend"()
+ void resize(size_t, T&)
+ void reverse()
+ size_t size()
+ void sort()
+ void swap(TList&)
+ void splice(iterator, TList&)
+ void splice(iterator, TList&, iterator)
+ void splice(iterator, TList&, iterator, iterator)
+ void unique()
diff --git a/util/generic/list_ut.cpp b/util/generic/list_ut.cpp
new file mode 100644
index 0000000000..9e60ecf01b
--- /dev/null
+++ b/util/generic/list_ut.cpp
@@ -0,0 +1,14 @@
+#include "list.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TYListSuite) {
+ Y_UNIT_TEST(TestInitializerList) {
+ TList<int> l = {3, 42, 6};
+ TList<int> expected;
+ expected.push_back(3);
+ expected.push_back(42);
+ expected.push_back(6);
+ UNIT_ASSERT_VALUES_EQUAL(l, expected);
+ }
+}
diff --git a/util/generic/list_ut.pyx b/util/generic/list_ut.pyx
new file mode 100644
index 0000000000..129e5bc9b6
--- /dev/null
+++ b/util/generic/list_ut.pyx
@@ -0,0 +1,158 @@
+from util.generic.list cimport TList
+
+import unittest
+from cython.operator cimport preincrement
+
+
+class TestList(unittest.TestCase):
+
+ def test_ctor1(self):
+ cdef TList[int] tmp = TList[int]()
+ self.assertEqual(tmp.size(), 0)
+
+ def test_ctor2(self):
+ cdef TList[int] tmp = TList[int](10, 42)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp.front(), 42)
+
+ def test_ctor3(self):
+ cdef TList[int] tmp = TList[int](10, 42)
+ cdef TList[int] tmp2 = TList[int](tmp)
+ self.assertEqual(tmp2.size(), 10)
+ self.assertEqual(tmp2.front(), 42)
+
+ def test_operator_assign(self):
+ cdef TList[int] tmp2
+ tmp2.push_back(1)
+ tmp2.push_back(2)
+
+ cdef TList[int] tmp3
+ tmp3.push_back(1)
+ tmp3.push_back(3)
+
+ tmp3 = tmp2
+
+ def test_compare(self):
+ cdef TList[int] tmp1
+ tmp1.push_back(1)
+ tmp1.push_back(2)
+
+ cdef TList[int] tmp2
+ tmp2.push_back(1)
+ tmp2.push_back(2)
+
+ cdef TList[int] tmp3
+ tmp3.push_back(1)
+ tmp3.push_back(3)
+
+ self.assertTrue(tmp1 == tmp2)
+ self.assertTrue(tmp1 != tmp3)
+
+ self.assertTrue(tmp1 < tmp3)
+ self.assertTrue(tmp1 <= tmp3)
+
+ self.assertTrue(tmp3 > tmp1)
+ self.assertTrue(tmp3 >= tmp1)
+
+ def test_push_pop_back(self):
+ cdef TList[int] tmp
+ self.assertEqual(tmp.size(), 0)
+
+ tmp.push_back(42)
+ self.assertEqual(tmp.size(), 1)
+ self.assertEqual(tmp.back(), 42)
+
+ tmp.push_back(77)
+ self.assertEqual(tmp.size(), 2)
+ self.assertEqual(tmp.back(), 77)
+
+ tmp.pop_back()
+ self.assertEqual(tmp.size(), 1)
+ self.assertEqual(tmp.back(), 42)
+
+ tmp.pop_back()
+ self.assertEqual(tmp.size(), 0)
+
+ def test_front(self):
+ cdef TList[int] tmp
+ tmp.push_back(42)
+ self.assertEqual(tmp.front(), 42)
+
+ def test_empty(self):
+ cdef TList[int] tmp
+ self.assertTrue(tmp.empty())
+ tmp.push_back(42)
+ self.assertFalse(tmp.empty())
+
+ def test_max_size(self):
+ cdef TList[int] tmp
+ self.assertTrue(tmp.max_size() > 0)
+
+ def test_resize(self):
+ cdef TList[int] tmp
+
+ tmp.resize(100, 42)
+ self.assertEqual(tmp.size(), 100)
+ self.assertEqual(tmp.front(), 42)
+ self.assertEqual(tmp.back(), 42)
+
+ def test_iter(self):
+ cdef TList[int] tmp
+ tmp.push_back(1)
+ tmp.push_back(20)
+ tmp.push_back(300)
+
+ self.assertEqual([i for i in tmp], [1, 20, 300])
+
+ def test_iterator(self):
+ cdef TList[int] tmp
+
+ self.assertTrue(tmp.begin() == tmp.end())
+ self.assertTrue(tmp.rbegin() == tmp.rend())
+ self.assertTrue(tmp.const_begin() == tmp.const_end())
+ self.assertTrue(tmp.const_rbegin() == tmp.const_rend())
+
+ tmp.push_back(1)
+
+ self.assertTrue(tmp.begin() != tmp.end())
+ self.assertTrue(tmp.rbegin() != tmp.rend())
+ self.assertTrue(tmp.const_begin() != tmp.const_end())
+ self.assertTrue(tmp.const_rbegin() != tmp.const_rend())
+
+ self.assertTrue(preincrement(tmp.begin()) == tmp.end())
+ self.assertTrue(preincrement(tmp.rbegin()) == tmp.rend())
+ self.assertTrue(preincrement(tmp.const_begin()) == tmp.const_end())
+ self.assertTrue(preincrement(tmp.const_rbegin()) == tmp.const_rend())
+
+ def test_assign(self):
+ cdef TList[int] tmp
+
+ tmp.assign(10, 42)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp.front(), 42)
+ self.assertEqual(tmp.back(), 42)
+
+ def test_insert(self):
+ cdef TList[int] tmp
+ tmp.push_back(1)
+ tmp.push_back(2)
+ tmp.push_back(3)
+
+ tmp.insert(tmp.begin(), 8)
+ self.assertEqual([i for i in tmp], [8, 1, 2, 3])
+
+ tmp.insert(tmp.begin(), 2, 6)
+ self.assertEqual([i for i in tmp], [6, 6, 8, 1, 2, 3])
+
+ def test_erase(self):
+ cdef TList[int] tmp
+ tmp.push_back(1)
+ tmp.push_back(2)
+ tmp.push_back(3)
+ tmp.push_back(4)
+
+ tmp.erase(preincrement(tmp.begin()))
+ self.assertEqual([i for i in tmp], [1, 3, 4])
+
+ tmp.erase(tmp.begin(), preincrement(preincrement(tmp.begin())))
+ self.assertEqual([i for i in tmp], [4])
diff --git a/util/generic/map.cpp b/util/generic/map.cpp
new file mode 100644
index 0000000000..b323fbb46d
--- /dev/null
+++ b/util/generic/map.cpp
@@ -0,0 +1 @@
+#include "map.h"
diff --git a/util/generic/map.h b/util/generic/map.h
new file mode 100644
index 0000000000..b5001b56c0
--- /dev/null
+++ b/util/generic/map.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "fwd.h"
+#include "mapfindptr.h"
+
+#include <util/str_stl.h>
+#include <util/memory/alloc.h>
+
+#include <utility>
+#include <initializer_list>
+#include <map>
+#include <memory>
+
+template <class K, class V, class Less, class A>
+class TMap: public std::map<K, V, Less, TReboundAllocator<A, std::pair<const K, V>>>, public TMapOps<TMap<K, V, Less, A>> {
+ using TBase = std::map<K, V, Less, TReboundAllocator<A, std::pair<const K, V>>>;
+
+public:
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ inline bool contains(const K& key) const {
+ return this->find(key) != this->end();
+ }
+};
+
+template <class K, class V, class Less, class A>
+class TMultiMap: public std::multimap<K, V, Less, TReboundAllocator<A, std::pair<const K, V>>> {
+ using TBase = std::multimap<K, V, Less, TReboundAllocator<A, std::pair<const K, V>>>;
+
+public:
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ inline bool contains(const K& key) const {
+ return this->find(key) != this->end();
+ }
+};
diff --git a/util/generic/map.pxd b/util/generic/map.pxd
new file mode 100644
index 0000000000..5c9fc32804
--- /dev/null
+++ b/util/generic/map.pxd
@@ -0,0 +1,46 @@
+from libcpp.pair cimport pair
+
+cdef extern from "util/generic/map.h" nogil:
+ cdef cppclass TMap[T, U]:
+ cppclass iterator:
+ pair[T, U]& operator*()
+ iterator operator++()
+ iterator operator--()
+ bint operator==(iterator)
+ bint operator!=(iterator)
+
+ cppclass const_iterator(iterator):
+ pass
+
+ TMap() except +
+ TMap(TMap&) except +
+ U& operator[](T&)
+ TMap& operator=(TMap&)
+
+ bint operator==(TMap&)
+ bint operator!=(TMap&)
+ bint operator<(TMap&)
+ bint operator>(TMap&)
+ bint operator<=(TMap&)
+ bint operator>=(TMap&)
+
+ U& at(T&) except +
+ iterator begin()
+ const_iterator const_begin "begin"()
+ void clear()
+ size_t count(T&)
+ bint empty()
+ iterator end()
+ const_iterator const_end "end"()
+ pair[iterator, iterator] equal_range(T&)
+ void erase(iterator) except +
+ void erase(iterator, iterator) except +
+ size_t erase(T&)
+ iterator find(T&)
+ bint contains(T&)
+ const_iterator const_find "find"(T&)
+ pair[iterator, bint] insert(pair[T, U]) # XXX pair[T,U]&
+ iterator insert(iterator, pair[T, U]) # XXX pair[T,U]&
+ size_t max_size()
+ size_t size()
+ void swap(TMap&)
diff --git a/util/generic/map_ut.cpp b/util/generic/map_ut.cpp
new file mode 100644
index 0000000000..79e832b024
--- /dev/null
+++ b/util/generic/map_ut.cpp
@@ -0,0 +1,496 @@
+#include "map.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+#include <util/memory/pool.h>
+#include <algorithm>
+
+Y_UNIT_TEST_SUITE(TYMapTest) {
+ template <typename TAlloc>
+ void DoTestMap1(TMap<char, int, TLess<char>, TAlloc>& m);
+
+ template <typename TAlloc>
+ void DoTestMMap1(TMultiMap<char, int, TLess<char>, TAlloc>& mm);
+
+ Y_UNIT_TEST(TestMap1) {
+ {
+ TMap<char, int, TLess<char>> m;
+ DoTestMap1(m);
+ }
+ {
+ TMemoryPool p(100);
+ TMap<char, int, TLess<char>, TPoolAllocator> m(&p);
+ DoTestMap1(m);
+ }
+ }
+
+ Y_UNIT_TEST(TestMMap1) {
+ {
+ TMultiMap<char, int, TLess<char>> mm;
+ DoTestMMap1(mm);
+ }
+ {
+ TMemoryPool p(100);
+ TMultiMap<char, int, TLess<char>, TPoolAllocator> mm(&p);
+ DoTestMMap1(mm);
+ }
+ }
+
+ template <typename TAlloc>
+ void DoTestMap1(TMap<char, int, TLess<char>, TAlloc>& m) {
+ using maptype = TMap<char, int, TLess<char>, TAlloc>;
+ // Store mappings between roman numerals and decimals.
+ m['l'] = 50;
+ m['x'] = 20; // Deliberate mistake.
+ m['v'] = 5;
+ m['i'] = 1;
+
+ UNIT_ASSERT(m['x'] == 20);
+ m['x'] = 10; // Correct mistake.
+ UNIT_ASSERT(m['x'] == 10);
+ UNIT_ASSERT(m['z'] == 0);
+
+ UNIT_ASSERT(m.count('z') == 1);
+
+ std::pair<typename maptype::iterator, bool> p = m.insert(std::pair<const char, int>('c', 100));
+
+ UNIT_ASSERT(p.second);
+ UNIT_ASSERT(p.first != m.end());
+ UNIT_ASSERT((*p.first).first == 'c');
+ UNIT_ASSERT((*p.first).second == 100);
+
+ p = m.insert(std::pair<const char, int>('c', 100));
+
+ UNIT_ASSERT(!p.second); // already existing pair
+ UNIT_ASSERT(p.first != m.end());
+ UNIT_ASSERT((*p.first).first == 'c');
+ UNIT_ASSERT((*p.first).second == 100);
+ }
+
+ template <typename TAlloc>
+ void DoTestMMap1(TMultiMap<char, int, TLess<char>, TAlloc>& m) {
+ using mmap = TMultiMap<char, int, TLess<char>, TAlloc>;
+
+ UNIT_ASSERT(m.count('X') == 0);
+
+ m.insert(std::pair<const char, int>('X', 10)); // Standard way.
+ UNIT_ASSERT(m.count('X') == 1);
+
+ m.insert(std::pair<const char, int>('X', 20)); // jbuck: standard way
+ UNIT_ASSERT(m.count('X') == 2);
+
+ m.insert(std::pair<const char, int>('Y', 32)); // jbuck: standard way
+ typename mmap::iterator i = m.find('X'); // Find first match.
+ ++i;
+ UNIT_ASSERT((*i).first == 'X');
+ UNIT_ASSERT((*i).second == 20);
+ ++i;
+ UNIT_ASSERT((*i).first == 'Y');
+ UNIT_ASSERT((*i).second == 32);
+ ++i;
+ UNIT_ASSERT(i == m.end());
+
+ size_t count = m.erase('X');
+ UNIT_ASSERT(count == 2);
+ }
+
+ Y_UNIT_TEST(TestMMap2) {
+ using pair_type = std::pair<const int, char>;
+
+ pair_type p1(3, 'c');
+ pair_type p2(6, 'f');
+ pair_type p3(1, 'a');
+ pair_type p4(2, 'b');
+ pair_type p5(3, 'x');
+ pair_type p6(6, 'f');
+
+ using mmap = TMultiMap<int, char, TLess<int>>;
+
+ pair_type array[] = {
+ p1,
+ p2,
+ p3,
+ p4,
+ p5,
+ p6};
+
+ mmap m(array + 0, array + 6);
+ mmap::iterator i;
+ i = m.lower_bound(3);
+ UNIT_ASSERT((*i).first == 3);
+ UNIT_ASSERT((*i).second == 'c');
+
+ i = m.upper_bound(3);
+ UNIT_ASSERT((*i).first == 6);
+ UNIT_ASSERT((*i).second == 'f');
+ }
+
+ Y_UNIT_TEST(TestIterators) {
+ using int_map = TMap<int, char, TLess<int>>;
+ int_map imap;
+
+ {
+ int_map::iterator ite(imap.begin());
+ int_map::const_iterator cite(imap.begin());
+
+ UNIT_ASSERT(ite == cite);
+ UNIT_ASSERT(!(ite != cite));
+ UNIT_ASSERT(cite == ite);
+ UNIT_ASSERT(!(cite != ite));
+ }
+
+ using mmap = TMultiMap<int, char, TLess<int>>;
+ using pair_type = mmap::value_type;
+
+ pair_type p1(3, 'c');
+ pair_type p2(6, 'f');
+ pair_type p3(1, 'a');
+ pair_type p4(2, 'b');
+ pair_type p5(3, 'x');
+ pair_type p6(6, 'f');
+
+ pair_type array[] = {
+ p1,
+ p2,
+ p3,
+ p4,
+ p5,
+ p6};
+
+ mmap m(array + 0, array + 6);
+
+ {
+ mmap::iterator ite(m.begin());
+ mmap::const_iterator cite(m.begin());
+ //test compare between const_iterator and iterator
+ UNIT_ASSERT(ite == cite);
+ UNIT_ASSERT(!(ite != cite));
+ UNIT_ASSERT(cite == ite);
+ UNIT_ASSERT(!(cite != ite));
+ }
+
+ mmap::reverse_iterator ri = m.rbegin();
+
+ UNIT_ASSERT(ri != m.rend());
+ UNIT_ASSERT(ri == m.rbegin());
+ UNIT_ASSERT((*ri).first == 6);
+ UNIT_ASSERT((*ri++).second == 'f');
+ UNIT_ASSERT((*ri).first == 6);
+ UNIT_ASSERT((*ri).second == 'f');
+
+ mmap const& cm = m;
+ mmap::const_reverse_iterator rci = cm.rbegin();
+
+ UNIT_ASSERT(rci != cm.rend());
+ UNIT_ASSERT((*rci).first == 6);
+ UNIT_ASSERT((*rci++).second == 'f');
+ UNIT_ASSERT((*rci).first == 6);
+ UNIT_ASSERT((*rci).second == 'f');
+ }
+
+ Y_UNIT_TEST(TestEqualRange) {
+ using maptype = TMap<char, int, TLess<char>>;
+
+ {
+ maptype m;
+ m['x'] = 10;
+
+ std::pair<maptype::iterator, maptype::iterator> ret;
+ ret = m.equal_range('x');
+ UNIT_ASSERT(ret.first != ret.second);
+ UNIT_ASSERT((*(ret.first)).first == 'x');
+ UNIT_ASSERT((*(ret.first)).second == 10);
+ UNIT_ASSERT(++(ret.first) == ret.second);
+ }
+
+ {
+ {
+ maptype m;
+
+ maptype::iterator i = m.lower_bound('x');
+ UNIT_ASSERT(i == m.end());
+
+ i = m.upper_bound('x');
+ UNIT_ASSERT(i == m.end());
+
+ std::pair<maptype::iterator, maptype::iterator> ret;
+ ret = m.equal_range('x');
+ UNIT_ASSERT(ret.first == ret.second);
+ UNIT_ASSERT(ret.first == m.end());
+ }
+
+ {
+ const maptype m;
+ std::pair<maptype::const_iterator, maptype::const_iterator> ret;
+ ret = m.equal_range('x');
+ UNIT_ASSERT(ret.first == ret.second);
+ UNIT_ASSERT(ret.first == m.end());
+ }
+ }
+ }
+
+ struct TKey {
+ TKey()
+ : m_data(0)
+ {
+ }
+
+ explicit TKey(int data)
+ : m_data(data)
+ {
+ }
+
+ int m_data;
+ };
+
+ struct TKeyCmp {
+ bool operator()(TKey lhs, TKey rhs) const {
+ return lhs.m_data < rhs.m_data;
+ }
+
+ bool operator()(TKey lhs, int rhs) const {
+ return lhs.m_data < rhs;
+ }
+
+ bool operator()(int lhs, TKey rhs) const {
+ return lhs < rhs.m_data;
+ }
+
+ using is_transparent = void;
+ };
+
+ struct TKeyCmpPtr {
+ bool operator()(TKey const volatile* lhs, TKey const volatile* rhs) const {
+ return (*lhs).m_data < (*rhs).m_data;
+ }
+
+ bool operator()(TKey const volatile* lhs, int rhs) const {
+ return (*lhs).m_data < rhs;
+ }
+
+ bool operator()(int lhs, TKey const volatile* rhs) const {
+ return lhs < (*rhs).m_data;
+ }
+
+ using is_transparent = void;
+ };
+
+ Y_UNIT_TEST(TestTemplateMethods) {
+ {
+ using Container = TMap<TKey, int, TKeyCmp>;
+ using value = Container::value_type;
+
+ Container cont;
+
+ cont.insert(value(TKey(1), 1));
+ cont.insert(value(TKey(2), 2));
+ cont.insert(value(TKey(3), 3));
+ cont.insert(value(TKey(4), 4));
+
+ UNIT_ASSERT(cont.count(TKey(1)) == 1);
+ UNIT_ASSERT(cont.count(1) == 1);
+ UNIT_ASSERT(cont.count(5) == 0);
+
+ UNIT_ASSERT(cont.find(2) != cont.end());
+ UNIT_ASSERT(cont.lower_bound(2) != cont.end());
+ UNIT_ASSERT(cont.upper_bound(2) != cont.end());
+ UNIT_ASSERT(cont.equal_range(2) != std::make_pair(cont.begin(), cont.end()));
+
+ Container const& ccont = cont;
+
+ UNIT_ASSERT(ccont.find(2) != ccont.end());
+ UNIT_ASSERT(ccont.lower_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.upper_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.equal_range(2) != std::make_pair(ccont.end(), ccont.end()));
+ }
+
+ {
+ using Container = TMap<TKey*, int, TKeyCmpPtr>;
+ using value = Container::value_type;
+
+ Container cont;
+
+ TKey key1(1), key2(2), key3(3), key4(4);
+
+ cont.insert(value(&key1, 1));
+ cont.insert(value(&key2, 2));
+ cont.insert(value(&key3, 3));
+ cont.insert(value(&key4, 4));
+
+ UNIT_ASSERT(cont.count(1) == 1);
+ UNIT_ASSERT(cont.count(5) == 0);
+
+ UNIT_ASSERT(cont.find(2) != cont.end());
+ UNIT_ASSERT(cont.lower_bound(2) != cont.end());
+ UNIT_ASSERT(cont.upper_bound(2) != cont.end());
+ UNIT_ASSERT(cont.equal_range(2) != std::make_pair(cont.begin(), cont.end()));
+
+ Container const& ccont = cont;
+
+ UNIT_ASSERT(ccont.find(2) != ccont.end());
+ UNIT_ASSERT(ccont.lower_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.upper_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.equal_range(2) != std::make_pair(ccont.begin(), ccont.end()));
+ }
+
+ {
+ using Container = TMultiMap<TKey, int, TKeyCmp>;
+ using value = Container::value_type;
+
+ Container cont;
+
+ cont.insert(value(TKey(1), 1));
+ cont.insert(value(TKey(2), 2));
+ cont.insert(value(TKey(3), 3));
+ cont.insert(value(TKey(4), 4));
+
+ UNIT_ASSERT(cont.count(TKey(1)) == 1);
+ UNIT_ASSERT(cont.count(1) == 1);
+ UNIT_ASSERT(cont.count(5) == 0);
+
+ UNIT_ASSERT(cont.find(2) != cont.end());
+ UNIT_ASSERT(cont.lower_bound(2) != cont.end());
+ UNIT_ASSERT(cont.upper_bound(2) != cont.end());
+ UNIT_ASSERT(cont.equal_range(2) != std::make_pair(cont.begin(), cont.end()));
+
+ Container const& ccont = cont;
+
+ UNIT_ASSERT(ccont.find(2) != ccont.end());
+ UNIT_ASSERT(ccont.lower_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.upper_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.equal_range(2) != std::make_pair(ccont.end(), ccont.end()));
+ }
+
+ {
+ using Container = TMultiMap<TKey const volatile*, int, TKeyCmpPtr>;
+ using value = Container::value_type;
+
+ Container cont;
+
+ TKey key1(1), key2(2), key3(3), key4(4);
+
+ cont.insert(value(&key1, 1));
+ cont.insert(value(&key2, 2));
+ cont.insert(value(&key3, 3));
+ cont.insert(value(&key4, 4));
+
+ UNIT_ASSERT(cont.count(1) == 1);
+ UNIT_ASSERT(cont.count(5) == 0);
+
+ UNIT_ASSERT(cont.find(2) != cont.end());
+ UNIT_ASSERT(cont.lower_bound(2) != cont.end());
+ UNIT_ASSERT(cont.upper_bound(2) != cont.end());
+ UNIT_ASSERT(cont.equal_range(2) != std::make_pair(cont.begin(), cont.end()));
+
+ Container const& ccont = cont;
+
+ UNIT_ASSERT(ccont.find(2) != ccont.end());
+ UNIT_ASSERT(ccont.lower_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.upper_bound(2) != ccont.end());
+ UNIT_ASSERT(ccont.equal_range(2) != std::make_pair(ccont.begin(), ccont.end()));
+ }
+ }
+
+ template <typename T>
+ static void EmptyAndInsertTest(typename T::value_type v) {
+ T c;
+ UNIT_ASSERT(!c);
+ c.insert(v);
+ UNIT_ASSERT(c);
+ }
+
+ Y_UNIT_TEST(TestEmpty) {
+ EmptyAndInsertTest<TMap<char, int, TLess<char>>>(std::pair<char, int>('a', 1));
+ EmptyAndInsertTest<TMultiMap<char, int, TLess<char>>>(std::pair<char, int>('a', 1));
+ }
+
+ struct TParametrizedKeyCmp {
+ bool Inverse;
+
+ TParametrizedKeyCmp(bool inverse = false)
+ : Inverse(inverse)
+ {
+ }
+
+ bool operator()(TKey lhs, TKey rhs) const {
+ if (Inverse) {
+ return lhs.m_data > rhs.m_data;
+ } else {
+ return lhs.m_data < rhs.m_data;
+ }
+ }
+ };
+
+ Y_UNIT_TEST(TestMoveComparator) {
+ using Container = TMultiMap<TKey, int, TParametrizedKeyCmp>;
+
+ TParametrizedKeyCmp direct(false);
+ TParametrizedKeyCmp inverse(true);
+
+ {
+ Container c(direct);
+ c = Container(inverse);
+
+ c.insert(std::make_pair(TKey(1), 101));
+ c.insert(std::make_pair(TKey(2), 102));
+ c.insert(std::make_pair(TKey(3), 103));
+
+ TVector<int> values;
+ for (auto& i : c) {
+ values.push_back(i.second);
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(values.size(), 3);
+ UNIT_ASSERT_VALUES_EQUAL(values[0], 103);
+ UNIT_ASSERT_VALUES_EQUAL(values[1], 102);
+ UNIT_ASSERT_VALUES_EQUAL(values[2], 101);
+ }
+ }
+
+ Y_UNIT_TEST(TestMapInitializerList) {
+ TMap<TString, int> m = {
+ {"one", 1},
+ {"two", 2},
+ {"three", 3},
+ {"four", 4},
+ };
+
+ UNIT_ASSERT_VALUES_EQUAL(m.size(), 4);
+ UNIT_ASSERT_VALUES_EQUAL(m["one"], 1);
+ UNIT_ASSERT_VALUES_EQUAL(m["two"], 2);
+ UNIT_ASSERT_VALUES_EQUAL(m["three"], 3);
+ UNIT_ASSERT_VALUES_EQUAL(m["four"], 4);
+ }
+
+ Y_UNIT_TEST(TestMMapInitializerList) {
+ TMultiMap<TString, int> mm = {
+ {"one", 1},
+ {"two", 2},
+ {"two", -2},
+ {"three", 3},
+ };
+ UNIT_ASSERT(mm.contains("two"));
+ TMultiMap<TString, int> expected;
+ expected.emplace("one", 1);
+ expected.emplace("two", 2);
+ expected.emplace("two", -2);
+ expected.emplace("three", 3);
+ UNIT_ASSERT_VALUES_EQUAL(mm, expected);
+ }
+
+ Y_UNIT_TEST(TestMovePoolAlloc) {
+ using TMapInPool = TMap<int, int, TLess<int>, TPoolAllocator>;
+
+ TMemoryPool pool(1);
+
+ TMapInPool m(&pool);
+ m.emplace(0, 1);
+
+ UNIT_ASSERT(m.contains(0));
+ UNIT_ASSERT_VALUES_EQUAL(1, m[0]);
+
+ TMapInPool movedM = std::move(m);
+
+ UNIT_ASSERT(movedM.contains(0));
+ UNIT_ASSERT_VALUES_EQUAL(1, movedM[0]);
+ }
+}
diff --git a/util/generic/map_ut.pyx b/util/generic/map_ut.pyx
new file mode 100644
index 0000000000..d6ec29724d
--- /dev/null
+++ b/util/generic/map_ut.pyx
@@ -0,0 +1,82 @@
+from util.generic.map cimport TMap
+from util.generic.string cimport TString
+
+import pytest
+import unittest
+
+from libcpp.pair cimport pair
+from cython.operator cimport dereference as deref
+
+
+def _check_convert(TMap[TString, int] x):
+ return x
+
+
+class TestMap(unittest.TestCase):
+
+ def test_constructors_and_assignments(self):
+ cdef TMap[TString, int] c1
+ c1["one"] = 1
+ c1["two"] = 2
+ cdef TMap[TString, int] c2 = TMap[TString, int](c1)
+ self.assertEqual(2, c1.size())
+ self.assertEqual(2, c2.size())
+ self.assertEqual(1, c1.at("one"))
+ self.assertTrue(c1.contains("two"))
+ self.assertTrue(c2.contains("one"))
+ self.assertEqual(2, c2.at("two"))
+ c2["three"] = 3
+ c1 = c2
+ self.assertEqual(3, c1.size())
+ self.assertEqual(3, c2.size())
+ self.assertEqual(3, c1.at("three"))
+
+ def test_equality_operator(self):
+ cdef TMap[TString, int] base
+ base["one"] = 1
+ base["two"] = 2
+
+ cdef TMap[TString, int] c1 = TMap[TString, int](base)
+ self.assertTrue(c1==base)
+
+ cdef TMap[TString, int] c2
+ c2["one"] = 1
+ c2["two"] = 2
+ self.assertTrue(c2 == base)
+
+ c2["three"] = 3
+ self.assertTrue(c2 != base)
+
+ cdef TMap[TString, int] c3 = TMap[TString, int](base)
+ c3["one"] = 0
+ self.assertTrue(c3 != base)
+
+ def test_insert_erase(self):
+ cdef TMap[TString, int] tmp
+ self.assertTrue(tmp.insert(pair[TString, int]("one", 0)).second)
+ self.assertFalse(tmp.insert(pair[TString, int]("one", 1)).second)
+ self.assertTrue(tmp.insert(pair[TString, int]("two", 2)).second)
+ cdef TString one = "one"
+ cdef TString two = "two"
+ self.assertEqual(tmp.erase(one), 1)
+ self.assertEqual(tmp.erase(two), 1)
+ self.assertEqual(tmp.size(), 0)
+ self.assertTrue(tmp.empty())
+
+ def test_iterators_and_find(self):
+ cdef TMap[TString, int] tmp
+ self.assertTrue(tmp.begin() == tmp.end())
+ self.assertTrue(tmp.find("1") == tmp.end())
+ tmp["1"] = 1
+ self.assertTrue(tmp.begin() != tmp.end())
+ cdef TMap[TString, int].iterator it = tmp.find("1")
+ self.assertTrue(it != tmp.end())
+ self.assertEqual(deref(it).second, 1)
+
+ def test_convert(self):
+ src = {'foo': 1, 'bar': 42}
+ self.assertEqual(_check_convert(src), src)
+
+ bad_src = {'foo': 1, 'bar': 'baz'}
+ with self.assertRaises(TypeError):
+ _check_convert(bad_src)
diff --git a/util/generic/mapfindptr.cpp b/util/generic/mapfindptr.cpp
new file mode 100644
index 0000000000..5e3ff4b779
--- /dev/null
+++ b/util/generic/mapfindptr.cpp
@@ -0,0 +1 @@
+#include "mapfindptr.h"
diff --git a/util/generic/mapfindptr.h b/util/generic/mapfindptr.h
new file mode 100644
index 0000000000..bc10cac60f
--- /dev/null
+++ b/util/generic/mapfindptr.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <type_traits>
+
+/** MapFindPtr usage:
+
+if (T* value = MapFindPtr(myMap, someKey) {
+ Cout << *value;
+}
+
+*/
+
+template <class Map, class K>
+inline auto MapFindPtr(Map& map, const K& key) {
+ auto i = map.find(key);
+
+ return (i == map.end() ? nullptr : &i->second);
+}
+
+template <class Map, class K>
+inline auto MapFindPtr(const Map& map, const K& key) {
+ auto i = map.find(key);
+
+ return (i == map.end() ? nullptr : &i->second);
+}
+
+/** helper for THashMap/TMap */
+template <class Derived>
+struct TMapOps {
+ template <class K>
+ inline auto FindPtr(const K& key) {
+ return MapFindPtr(static_cast<Derived&>(*this), key);
+ }
+
+ template <class K>
+ inline auto FindPtr(const K& key) const {
+ return MapFindPtr(static_cast<const Derived&>(*this), key);
+ }
+
+ template <class K, class DefaultValue>
+ inline auto Value(const K& key, const DefaultValue& defaultValue) const -> std::remove_reference_t<decltype(*this->FindPtr(key))> {
+ if (auto found = FindPtr(key)) {
+ return *found;
+ }
+ return defaultValue;
+ }
+
+ template <class K, class V>
+ inline const V& ValueRef(const K& key, V& defaultValue) const {
+ static_assert(std::is_same<std::remove_const_t<V>, typename Derived::mapped_type>::value, "Passed default value must have the same type as the underlying map's mapped_type.");
+
+ if (auto found = FindPtr(key)) {
+ return *found;
+ }
+ return defaultValue;
+ }
+
+ template <class K, class V>
+ inline const V& ValueRef(const K& key, V&& defaultValue) const = delete;
+};
diff --git a/util/generic/mapfindptr_ut.cpp b/util/generic/mapfindptr_ut.cpp
new file mode 100644
index 0000000000..613da7a96b
--- /dev/null
+++ b/util/generic/mapfindptr_ut.cpp
@@ -0,0 +1,67 @@
+#include "string.h"
+#include "hash.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <map>
+
+#include "mapfindptr.h"
+
+Y_UNIT_TEST_SUITE(TMapFindPtrTest) {
+ struct TTestMap: std::map<int, TString>, TMapOps<TTestMap> {};
+
+ Y_UNIT_TEST(TestDerivedClass) {
+ TTestMap a;
+
+ a[42] = "cat";
+ UNIT_ASSERT(a.FindPtr(42));
+ UNIT_ASSERT_EQUAL(*a.FindPtr(42), "cat");
+ UNIT_ASSERT_EQUAL(a.FindPtr(0), nullptr);
+
+ //test mutation
+ if (TString* p = a.FindPtr(42)) {
+ *p = "dog";
+ }
+ UNIT_ASSERT(a.FindPtr(42));
+ UNIT_ASSERT_EQUAL(*a.FindPtr(42), "dog");
+
+ //test const-overloaded functions too
+ const TTestMap& b = a;
+ UNIT_ASSERT(b.FindPtr(42) && *b.FindPtr(42) == "dog");
+ UNIT_ASSERT_EQUAL(b.FindPtr(0), nullptr);
+
+ UNIT_ASSERT_STRINGS_EQUAL(b.Value(42, "cat"), "dog");
+ UNIT_ASSERT_STRINGS_EQUAL(b.Value(0, "alien"), "alien");
+ }
+
+ Y_UNIT_TEST(TestTemplateFind) {
+ THashMap<TString, int> m;
+
+ m[TString("x")] = 2;
+
+ UNIT_ASSERT(m.FindPtr(TStringBuf("x")));
+ UNIT_ASSERT_EQUAL(*m.FindPtr(TStringBuf("x")), 2);
+ }
+
+ Y_UNIT_TEST(TestValue) {
+ TTestMap a;
+
+ a[1] = "lol";
+
+ UNIT_ASSERT_VALUES_EQUAL(a.Value(1, "123"), "lol");
+ UNIT_ASSERT_VALUES_EQUAL(a.Value(2, "123"), "123");
+ }
+
+ Y_UNIT_TEST(TestValueRef) {
+ TTestMap a;
+
+ a[1] = "lol";
+
+ const TString str123 = "123";
+ TString str1234 = "1234";
+
+ UNIT_ASSERT_VALUES_EQUAL(a.ValueRef(1, str123), "lol");
+ UNIT_ASSERT_VALUES_EQUAL(a.ValueRef(2, str123), "123");
+ UNIT_ASSERT_VALUES_EQUAL(a.ValueRef(3, str1234), "1234");
+ }
+}
diff --git a/util/generic/maybe.cpp b/util/generic/maybe.cpp
new file mode 100644
index 0000000000..43262934f8
--- /dev/null
+++ b/util/generic/maybe.cpp
@@ -0,0 +1,16 @@
+#include "maybe.h"
+#include <util/system/type_name.h>
+
+[[noreturn]] void NMaybe::TPolicyUndefinedExcept::OnEmpty(const std::type_info& valueTypeInfo) {
+ ythrow yexception() << "TMaybe is empty, value type: "sv << TypeName(valueTypeInfo);
+}
+
+[[noreturn]] void NMaybe::TPolicyUndefinedFail::OnEmpty(const std::type_info& valueTypeInfo) {
+ const TString typeName = TypeName(valueTypeInfo);
+ Y_FAIL("TMaybe is empty, value type: %s", typeName.c_str());
+}
+
+template <>
+void Out<TNothing>(IOutputStream& o, const TNothing&) {
+ o << "(empty maybe)";
+}
diff --git a/util/generic/maybe.h b/util/generic/maybe.h
new file mode 100644
index 0000000000..34d21aebcd
--- /dev/null
+++ b/util/generic/maybe.h
@@ -0,0 +1,722 @@
+#pragma once
+
+#include <utility>
+
+#include "maybe_traits.h"
+#include "yexception.h"
+
+#include <util/system/align.h>
+#include <util/stream/output.h>
+#include <util/ysaveload.h>
+
+namespace NMaybe {
+ struct TPolicyUndefinedExcept {
+ [[noreturn]] static void OnEmpty(const std::type_info& valueTypeInfo);
+ };
+
+ struct TPolicyUndefinedFail {
+ [[noreturn]] static void OnEmpty(const std::type_info& valueTypeInfo);
+ };
+}
+
+struct TNothing {
+ explicit constexpr TNothing(int) noexcept {
+ }
+};
+
+constexpr TNothing NothingObject{0};
+
+constexpr TNothing Nothing() noexcept {
+ return NothingObject;
+}
+
+constexpr bool operator==(TNothing, TNothing) noexcept {
+ return true;
+}
+
+template <class T, class Policy /*= ::NMaybe::TPolicyUndefinedExcept*/>
+class TMaybe: private TMaybeBase<T> {
+public:
+ using TInPlace = NMaybe::TInPlace;
+
+private:
+ static_assert(!std::is_same<std::remove_cv_t<T>, TNothing>::value,
+ "Instantiation of TMaybe with a TNothing type is ill-formed");
+ static_assert(!std::is_same<std::remove_cv_t<T>, TInPlace>::value,
+ "Instantiation of TMaybe with a TInPlace type is ill-formed");
+ static_assert(!std::is_reference<T>::value,
+ "Instantiation of TMaybe with reference type is ill-formed");
+ static_assert(std::is_destructible<T>::value,
+ "Instantiation of TMaybe with non-destructible type is ill-formed");
+
+ template <class U>
+ struct TConstructibleFromMaybeSomehow {
+ public:
+ static constexpr bool value = std::is_constructible<T, TMaybe<U, Policy>&>::value ||
+ std::is_constructible<T, const TMaybe<U, Policy>&>::value ||
+ std::is_constructible<T, TMaybe<U, Policy>&&>::value ||
+ std::is_constructible<T, const TMaybe<U, Policy>&&>::value ||
+ std::is_convertible<TMaybe<U, Policy>&, T>::value ||
+ std::is_convertible<const TMaybe<U, Policy>&, T>::value ||
+ std::is_convertible<TMaybe<U, Policy>&&, T>::value ||
+ std::is_convertible<const TMaybe<U, Policy>&&, T>::value;
+ };
+
+ template <class U>
+ struct TAssignableFromMaybeSomehow {
+ public:
+ static constexpr bool value = TConstructibleFromMaybeSomehow<U>::value ||
+ std::is_assignable<T&, TMaybe<U, Policy>&>::value ||
+ std::is_assignable<T&, const TMaybe<U, Policy>&>::value ||
+ std::is_assignable<T&, TMaybe<U, Policy>&&>::value ||
+ std::is_assignable<T&, const TMaybe<U, Policy>&&>::value;
+ };
+
+ template <class U>
+ struct TImplicitCopyCtor {
+ public:
+ static constexpr bool value = std::is_constructible<T, const U&>::value &&
+ std::is_convertible<const U&, T>::value &&
+ !TConstructibleFromMaybeSomehow<U>::value;
+ };
+
+ template <class U>
+ struct TExplicitCopyCtor {
+ public:
+ static constexpr bool value = std::is_constructible<T, const U&>::value &&
+ !std::is_convertible<const U&, T>::value &&
+ !TConstructibleFromMaybeSomehow<U>::value;
+ };
+
+ template <class U>
+ struct TImplicitMoveCtor {
+ public:
+ static constexpr bool value = std::is_constructible<T, U&&>::value &&
+ std::is_convertible<U&&, T>::value &&
+ !TConstructibleFromMaybeSomehow<U>::value;
+ };
+
+ template <class U>
+ struct TExplicitMoveCtor {
+ public:
+ static constexpr bool value = std::is_constructible<T, U&&>::value &&
+ !std::is_convertible<U&&, T>::value &&
+ !TConstructibleFromMaybeSomehow<U>::value;
+ };
+
+ template <class U>
+ struct TCopyAssignable {
+ public:
+ static constexpr bool value = std::is_constructible<T, const U&>::value &&
+ std::is_assignable<T&, const U&>::value &&
+ !TAssignableFromMaybeSomehow<U>::value;
+ };
+
+ template <class U>
+ struct TMoveAssignable {
+ public:
+ static constexpr bool value = std::is_constructible<T, U&&>::value &&
+ std::is_assignable<T&, U&&>::value &&
+ !TAssignableFromMaybeSomehow<U>::value;
+ };
+
+ template <class U>
+ struct TImplicitAnyCtor {
+ public:
+ using UDec = std::decay_t<U>;
+
+ static constexpr bool value = std::is_constructible<T, U>::value &&
+ std::is_convertible<U, T>::value &&
+ !std::is_same<UDec, TInPlace>::value &&
+ !std::is_same<UDec, TMaybe>::value;
+ };
+
+ template <class U>
+ struct TExplicitAnyCtor {
+ public:
+ using UDec = std::decay_t<U>;
+ static constexpr bool value = std::is_constructible<T, U>::value &&
+ !std::is_convertible<U, T>::value &&
+ !std::is_same<UDec, TInPlace>::value &&
+ !std::is_same<UDec, TMaybe>::value;
+ };
+
+ template <class U>
+ struct TAssignableFromAny {
+ public:
+ using UDec = std::decay_t<U>;
+ static constexpr bool value = !std::is_same<UDec, TMaybe>::value &&
+ std::is_constructible<T, U>::value &&
+ std::is_assignable<T&, U>::value &&
+ (!std::is_scalar<T>::value || !std::is_same<UDec, T>::value);
+ };
+
+ using TBase = TMaybeBase<T>;
+
+public:
+ using value_type = T;
+ using TValueType = value_type;
+
+ TMaybe() noexcept = default;
+
+ constexpr TMaybe(const TMaybe&) = default;
+ constexpr TMaybe(TMaybe&&) = default;
+
+ template <class... Args>
+ constexpr explicit TMaybe(TInPlace, Args&&... args)
+ : TBase(TInPlace{}, std::forward<Args>(args)...)
+ {
+ }
+
+ template <class U, class... TArgs>
+ constexpr explicit TMaybe(TInPlace, std::initializer_list<U> il, TArgs&&... args)
+ : TBase(TInPlace{}, il, std::forward<TArgs>(args)...)
+ {
+ }
+
+ constexpr TMaybe(TNothing) noexcept {
+ }
+
+ template <class U, class = std::enable_if_t<TImplicitCopyCtor<U>::value>>
+ TMaybe(const TMaybe<U, Policy>& right) {
+ if (right.Defined()) {
+ new (Data()) T(right.GetRef());
+ this->Defined_ = true;
+ }
+ }
+
+ template <class U, std::enable_if_t<TExplicitCopyCtor<U>::value, bool> = false>
+ explicit TMaybe(const TMaybe<U, Policy>& right) {
+ if (right.Defined()) {
+ new (Data()) T(right.GetRef());
+ this->Defined_ = true;
+ }
+ }
+
+ template <class U, class = std::enable_if_t<TImplicitMoveCtor<U>::value>>
+ TMaybe(TMaybe<U, Policy>&& right) noexcept(std::is_nothrow_constructible<T, U&&>::value) {
+ if (right.Defined()) {
+ new (Data()) T(std::move(right.GetRef()));
+ this->Defined_ = true;
+ }
+ }
+
+ template <class U, std::enable_if_t<TExplicitMoveCtor<U>::value, bool> = false>
+ explicit TMaybe(TMaybe<U, Policy>&& right) noexcept(std::is_nothrow_constructible<T, U&&>::value) {
+ if (right.Defined()) {
+ new (Data()) T(std::move(right.GetRef()));
+ this->Defined_ = true;
+ }
+ }
+
+ template <class U = T, class = std::enable_if_t<TImplicitAnyCtor<U>::value>>
+ constexpr TMaybe(U&& right)
+ : TBase(TInPlace{}, std::forward<U>(right))
+ {
+ }
+
+ template <class U = T, std::enable_if_t<TExplicitAnyCtor<U>::value, bool> = false>
+ constexpr explicit TMaybe(U&& right)
+ : TBase(TInPlace{}, std::forward<U>(right))
+ {
+ }
+
+ ~TMaybe() = default;
+
+ constexpr TMaybe& operator=(const TMaybe&) = default;
+ constexpr TMaybe& operator=(TMaybe&&) = default;
+
+ TMaybe& operator=(TNothing) noexcept {
+ Clear();
+ return *this;
+ }
+
+ template <class U = T>
+ std::enable_if_t<TAssignableFromAny<U>::value, TMaybe&> operator=(U&& right) {
+ if (Defined()) {
+ *Data() = std::forward<U>(right);
+ } else {
+ Init(std::forward<U>(right));
+ }
+ return *this;
+ }
+
+ template <class U>
+ std::enable_if_t<TCopyAssignable<U>::value,
+ TMaybe&>
+ operator=(const TMaybe<U, Policy>& right) {
+ if (right.Defined()) {
+ if (Defined()) {
+ *Data() = right.GetRef();
+ } else {
+ Init(right.GetRef());
+ }
+ } else {
+ Clear();
+ }
+
+ return *this;
+ }
+
+ template <class U>
+ std::enable_if_t<TMoveAssignable<U>::value,
+ TMaybe&>
+ operator=(TMaybe<U, Policy>&& right) noexcept(
+ std::is_nothrow_assignable<T&, U&&>::value&& std::is_nothrow_constructible<T, U&&>::value)
+ {
+ if (right.Defined()) {
+ if (Defined()) {
+ *Data() = std::move(right.GetRef());
+ } else {
+ Init(std::move(right.GetRef()));
+ }
+ } else {
+ Clear();
+ }
+
+ return *this;
+ }
+
+ template <typename... Args>
+ T& ConstructInPlace(Args&&... args) {
+ Clear();
+ Init(std::forward<Args>(args)...);
+ return *Data();
+ }
+
+ void Clear() noexcept {
+ if (Defined()) {
+ this->Defined_ = false;
+ Data()->~T();
+ }
+ }
+
+ constexpr bool Defined() const noexcept {
+ return this->Defined_;
+ }
+
+ Y_PURE_FUNCTION constexpr bool Empty() const noexcept {
+ return !Defined();
+ }
+
+ void CheckDefined() const {
+ if (Y_UNLIKELY(!Defined())) {
+ Policy::OnEmpty(typeid(TValueType));
+ }
+ }
+
+ const T* Get() const noexcept {
+ return Defined() ? Data() : nullptr;
+ }
+
+ T* Get() noexcept {
+ return Defined() ? Data() : nullptr;
+ }
+
+ constexpr const T& GetRef() const& {
+ CheckDefined();
+
+ return *Data();
+ }
+
+ constexpr T& GetRef() & {
+ CheckDefined();
+
+ return *Data();
+ }
+
+ constexpr const T&& GetRef() const&& {
+ CheckDefined();
+
+ return std::move(*Data());
+ }
+
+ constexpr T&& GetRef() && {
+ CheckDefined();
+
+ return std::move(*Data());
+ }
+
+ constexpr const T& operator*() const& {
+ return GetRef();
+ }
+
+ constexpr T& operator*() & {
+ return GetRef();
+ }
+
+ constexpr const T&& operator*() const&& {
+ return std::move(GetRef());
+ }
+
+ constexpr T&& operator*() && {
+ return std::move(GetRef());
+ }
+
+ constexpr const T* operator->() const {
+ return &GetRef();
+ }
+
+ constexpr T* operator->() {
+ return &GetRef();
+ }
+
+ constexpr const T& GetOrElse(const T& elseValue) const {
+ return Defined() ? *Data() : elseValue;
+ }
+
+ constexpr T& GetOrElse(T& elseValue) {
+ return Defined() ? *Data() : elseValue;
+ }
+
+ constexpr const TMaybe& OrElse(const TMaybe& elseValue) const noexcept {
+ return Defined() ? *this : elseValue;
+ }
+
+ constexpr TMaybe& OrElse(TMaybe& elseValue) {
+ return Defined() ? *this : elseValue;
+ }
+
+ template <typename U>
+ TMaybe<U, Policy> Cast() const {
+ return Defined() ? TMaybe<U, Policy>(*Data()) : TMaybe<U, Policy>();
+ }
+
+ constexpr explicit operator bool() const noexcept {
+ return Defined();
+ }
+
+ void Save(IOutputStream* out) const {
+ const bool defined = Defined();
+
+ ::Save<bool>(out, defined);
+
+ if (defined) {
+ ::Save(out, *Data());
+ }
+ }
+
+ void Load(IInputStream* in) {
+ bool defined;
+
+ ::Load(in, defined);
+
+ if (defined) {
+ if (!Defined()) {
+ ConstructInPlace();
+ }
+
+ ::Load(in, *Data());
+ } else {
+ Clear();
+ }
+ }
+
+ void Swap(TMaybe& other) {
+ if (this->Defined_ == other.Defined_) {
+ if (this->Defined_) {
+ ::DoSwap(this->Data_, other.Data_);
+ }
+ } else {
+ if (this->Defined_) {
+ other.Init(std::move(this->Data_));
+ this->Clear();
+ } else {
+ this->Init(std::move(other.Data_));
+ other.Clear();
+ }
+ }
+ }
+
+ void swap(TMaybe& other) {
+ Swap(other);
+ }
+
+private:
+ constexpr const T* Data() const noexcept {
+ return std::addressof(this->Data_);
+ }
+
+ constexpr T* Data() noexcept {
+ return std::addressof(this->Data_);
+ }
+
+ template <typename... Args>
+ void Init(Args&&... args) {
+ new (Data()) T(std::forward<Args>(args)...);
+ this->Defined_ = true;
+ }
+};
+
+template <class T>
+using TMaybeFail = TMaybe<T, NMaybe::TPolicyUndefinedFail>;
+
+template <class T, class TPolicy = ::NMaybe::TPolicyUndefinedExcept>
+constexpr TMaybe<std::decay_t<T>, TPolicy> MakeMaybe(T&& value) {
+ return TMaybe<std::decay_t<T>, TPolicy>(std::forward<T>(value));
+}
+
+template <class T, class... TArgs>
+constexpr TMaybe<T> MakeMaybe(TArgs&&... args) {
+ return TMaybe<T>(typename TMaybe<T>::TInPlace{}, std::forward<TArgs>(args)...);
+}
+
+template <class T, class U, class... TArgs>
+constexpr TMaybe<T> MakeMaybe(std::initializer_list<U> il, TArgs&&... args) {
+ return TMaybe<T>(typename TMaybe<T>::TInPlace{}, il, std::forward<TArgs>(args)...);
+}
+
+template <class T, class TPolicy>
+void Swap(TMaybe<T, TPolicy>& lhs, TMaybe<T, TPolicy>& rhs) {
+ lhs.Swap(rhs);
+}
+
+template <class T, class TPolicy>
+void swap(TMaybe<T, TPolicy>& lhs, TMaybe<T, TPolicy>& rhs) {
+ lhs.Swap(rhs);
+}
+
+template <typename T, class TPolicy>
+struct THash<TMaybe<T, TPolicy>> {
+ constexpr size_t operator()(const TMaybe<T, TPolicy>& data) const {
+ return (data.Defined()) ? THash<T>()(data.GetRef()) : 42;
+ }
+};
+
+// Comparisons between TMaybe
+template <class T, class TPolicy>
+constexpr bool operator==(const ::TMaybe<T, TPolicy>& left, const ::TMaybe<T, TPolicy>& right) {
+ return (static_cast<bool>(left) != static_cast<bool>(right))
+ ? false
+ : (
+ !static_cast<bool>(left)
+ ? true
+ : *left == *right);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator!=(const TMaybe<T, TPolicy>& left, const TMaybe<T, TPolicy>& right) {
+ return !(left == right);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<(const TMaybe<T, TPolicy>& left, const TMaybe<T, TPolicy>& right) {
+ return (!static_cast<bool>(right))
+ ? false
+ : (
+ !static_cast<bool>(left)
+ ? true
+ : (*left < *right));
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>(const TMaybe<T, TPolicy>& left, const TMaybe<T, TPolicy>& right) {
+ return right < left;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<=(const TMaybe<T, TPolicy>& left, const TMaybe<T, TPolicy>& right) {
+ return !(right < left);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>=(const TMaybe<T, TPolicy>& left, const TMaybe<T, TPolicy>& right) {
+ return !(left < right);
+}
+
+// Comparisons with TNothing
+template <class T, class TPolicy>
+constexpr bool operator==(const TMaybe<T, TPolicy>& left, TNothing) noexcept {
+ return !static_cast<bool>(left);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator==(TNothing, const TMaybe<T, TPolicy>& right) noexcept {
+ return !static_cast<bool>(right);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator!=(const TMaybe<T, TPolicy>& left, TNothing) noexcept {
+ return static_cast<bool>(left);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator!=(TNothing, const TMaybe<T, TPolicy>& right) noexcept {
+ return static_cast<bool>(right);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<(const TMaybe<T, TPolicy>&, TNothing) noexcept {
+ return false;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<(TNothing, const TMaybe<T, TPolicy>& right) noexcept {
+ return static_cast<bool>(right);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<=(const TMaybe<T, TPolicy>& left, TNothing) noexcept {
+ return !static_cast<bool>(left);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<=(TNothing, const TMaybe<T, TPolicy>&) noexcept {
+ return true;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>(const TMaybe<T, TPolicy>& left, TNothing) noexcept {
+ return static_cast<bool>(left);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>(TNothing, const TMaybe<T, TPolicy>&) noexcept {
+ return false;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>=(const TMaybe<T, TPolicy>&, TNothing) noexcept {
+ return true;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>=(TNothing, const TMaybe<T, TPolicy>& right) noexcept {
+ return !static_cast<bool>(right);
+}
+
+// Comparisons with T
+
+template <class T, class TPolicy>
+constexpr bool operator==(const TMaybe<T, TPolicy>& maybe, const T& value) {
+ return static_cast<bool>(maybe) ? *maybe == value : false;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator==(const T& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? *maybe == value : false;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator!=(const TMaybe<T, TPolicy>& maybe, const T& value) {
+ return static_cast<bool>(maybe) ? !(*maybe == value) : true;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator!=(const T& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? !(*maybe == value) : true;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<(const TMaybe<T, TPolicy>& maybe, const T& value) {
+ return static_cast<bool>(maybe) ? std::less<T>{}(*maybe, value) : true;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<(const T& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? std::less<T>{}(value, *maybe) : false;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<=(const TMaybe<T, TPolicy>& maybe, const T& value) {
+ return !(maybe > value);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator<=(const T& value, const TMaybe<T, TPolicy>& maybe) {
+ return !(value > maybe);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>(const TMaybe<T, TPolicy>& maybe, const T& value) {
+ return static_cast<bool>(maybe) ? value < maybe : false;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>(const T& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? maybe < value : true;
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>=(const TMaybe<T, TPolicy>& maybe, const T& value) {
+ return !(maybe < value);
+}
+
+template <class T, class TPolicy>
+constexpr bool operator>=(const T& value, const TMaybe<T, TPolicy>& maybe) {
+ return !(value < maybe);
+}
+
+// Comparison with values convertible to T
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator==(const ::TMaybe<T, TPolicy>& maybe, const U& value) {
+ return static_cast<bool>(maybe) ? *maybe == value : false;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator==(const U& value, const ::TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? *maybe == value : false;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator!=(const TMaybe<T, TPolicy>& maybe, const U& value) {
+ return static_cast<bool>(maybe) ? !(*maybe == value) : true;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator!=(const U& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? !(*maybe == value) : true;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator<(const TMaybe<T, TPolicy>& maybe, const U& value) {
+ return static_cast<bool>(maybe) ? std::less<T>{}(*maybe, value) : true;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator<(const U& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? std::less<T>{}(value, *maybe) : false;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator<=(const TMaybe<T, TPolicy>& maybe, const U& value) {
+ return !(maybe > value);
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator<=(const U& value, const TMaybe<T, TPolicy>& maybe) {
+ return !(value > maybe);
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator>(const TMaybe<T, TPolicy>& maybe, const U& value) {
+ return static_cast<bool>(maybe) ? value < maybe : false;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator>(const U& value, const TMaybe<T, TPolicy>& maybe) {
+ return static_cast<bool>(maybe) ? maybe < value : true;
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator>=(const TMaybe<T, TPolicy>& maybe, const U& value) {
+ return !(maybe < value);
+}
+
+template <class T, class TPolicy, class U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0>
+constexpr bool operator>=(const U& value, const TMaybe<T, TPolicy>& maybe) {
+ return !(value < maybe);
+}
+
+class IOutputStream;
+
+template <class T, class TPolicy>
+inline IOutputStream& operator<<(IOutputStream& out, const TMaybe<T, TPolicy>& maybe) {
+ if (maybe.Defined()) {
+ out << *maybe;
+ } else {
+ out << TStringBuf("(empty maybe)");
+ }
+ return out;
+}
diff --git a/util/generic/maybe.pxd b/util/generic/maybe.pxd
new file mode 100644
index 0000000000..15161dd204
--- /dev/null
+++ b/util/generic/maybe.pxd
@@ -0,0 +1,35 @@
+cdef extern from "<util/generic/maybe.h>" nogil:
+ cdef cppclass TNothing:
+ pass
+
+ cdef TNothing Nothing()
+
+ cdef cppclass TMaybe[T]:
+ TMaybe(...) except +
+
+ TMaybe& operator=(...) except +
+
+ void ConstructInPlace(...) except +
+ void Clear() except +
+
+ bint Defined()
+ bint Empty()
+
+ void CheckDefined() except +
+
+ T* Get() except +
+ T& GetRef() except +
+
+ T GetOrElse(T&) except +
+ TMaybe OrElse(TMaybe&) except +
+
+ TMaybe[U] Cast[U]() except +
+
+ void Swap(TMaybe& other) except +
+
+ bint operator ==[U](U&) except +
+ bint operator !=[U](U&) except +
+ bint operator <[U](U&) except +
+ bint operator >[U](U&) except +
+ bint operator <=[U](U&) except +
+ bint operator >=[U](U&) except +
diff --git a/util/generic/maybe_traits.h b/util/generic/maybe_traits.h
new file mode 100644
index 0000000000..9e9f56955e
--- /dev/null
+++ b/util/generic/maybe_traits.h
@@ -0,0 +1,173 @@
+#pragma once
+
+#include <memory>
+#include <type_traits>
+#include <initializer_list>
+
+namespace NMaybe {
+ struct TInPlace {};
+
+ template <class T, bool = std::is_trivially_destructible<T>::value>
+ struct TStorageBase {
+ constexpr TStorageBase() noexcept
+ : NullState_('\0')
+ {
+ }
+
+ template <class... Args>
+ constexpr TStorageBase(TInPlace, Args&&... args)
+ : Data_(std::forward<Args>(args)...)
+ , Defined_(true)
+ {
+ }
+
+ ~TStorageBase() = default;
+
+ union {
+ char NullState_;
+ T Data_;
+ };
+ bool Defined_ = false;
+ };
+
+ template <class T>
+ struct TStorageBase<T, false> {
+ constexpr TStorageBase() noexcept
+ : NullState_('\0')
+ {
+ }
+
+ template <class... Args>
+ constexpr TStorageBase(TInPlace, Args&&... args)
+ : Data_(std::forward<Args>(args)...)
+ , Defined_(true)
+ {
+ }
+
+ ~TStorageBase() {
+ if (this->Defined_) {
+ this->Data_.~T();
+ }
+ }
+
+ union {
+ char NullState_;
+ T Data_;
+ };
+ bool Defined_ = false;
+ };
+
+ // -------------------- COPY CONSTRUCT --------------------
+
+ template <class T, bool = std::is_trivially_copy_constructible<T>::value>
+ struct TCopyBase: TStorageBase<T> {
+ using TStorageBase<T>::TStorageBase;
+ };
+
+ template <class T>
+ struct TCopyBase<T, false>: TStorageBase<T> {
+ using TStorageBase<T>::TStorageBase;
+
+ constexpr TCopyBase() = default;
+ constexpr TCopyBase(const TCopyBase& rhs) {
+ if (rhs.Defined_) {
+ new (std::addressof(this->Data_)) T(rhs.Data_);
+ this->Defined_ = true;
+ }
+ }
+ constexpr TCopyBase(TCopyBase&&) = default;
+ TCopyBase& operator=(const TCopyBase&) = default;
+ TCopyBase& operator=(TCopyBase&&) = default;
+ };
+
+ // -------------------- MOVE CONSTRUCT --------------------
+
+ template <class T, bool = std::is_trivially_move_constructible<T>::value>
+ struct TMoveBase: TCopyBase<T> {
+ using TCopyBase<T>::TCopyBase;
+ };
+
+ template <class T>
+ struct TMoveBase<T, false>: TCopyBase<T> {
+ using TCopyBase<T>::TCopyBase;
+
+ constexpr TMoveBase() noexcept = default;
+ constexpr TMoveBase(const TMoveBase&) = default;
+ constexpr TMoveBase(TMoveBase&& rhs) noexcept(std::is_nothrow_move_constructible<T>::value) {
+ if (rhs.Defined_) {
+ new (std::addressof(this->Data_)) T(std::move(rhs.Data_));
+ this->Defined_ = true;
+ }
+ }
+ TMoveBase& operator=(const TMoveBase&) = default;
+ TMoveBase& operator=(TMoveBase&&) = default;
+ };
+
+ // -------------------- COPY ASSIGN --------------------
+
+ template <class T, bool = std::is_trivially_copy_assignable<T>::value>
+ struct TCopyAssignBase: TMoveBase<T> {
+ using TMoveBase<T>::TMoveBase;
+ };
+
+ template <class T>
+ struct TCopyAssignBase<T, false>: TMoveBase<T> {
+ using TMoveBase<T>::TMoveBase;
+
+ constexpr TCopyAssignBase() noexcept = default;
+ constexpr TCopyAssignBase(const TCopyAssignBase&) = default;
+ constexpr TCopyAssignBase(TCopyAssignBase&&) = default;
+ TCopyAssignBase& operator=(const TCopyAssignBase& rhs) {
+ if (this->Defined_) {
+ if (rhs.Defined_) {
+ this->Data_ = rhs.Data_;
+ } else {
+ this->Data_.~T();
+ this->Defined_ = false;
+ }
+ } else if (rhs.Defined_) {
+ new (std::addressof(this->Data_)) T(rhs.Data_);
+ this->Defined_ = true;
+ }
+ return *this;
+ }
+ TCopyAssignBase& operator=(TCopyAssignBase&&) = default;
+ };
+
+ // -------------------- MOVE ASSIGN --------------------
+
+ template <class T, bool = std::is_trivially_copy_assignable<T>::value>
+ struct TMoveAssignBase: TCopyAssignBase<T> {
+ using TCopyAssignBase<T>::TCopyAssignBase;
+ };
+
+ template <class T>
+ struct TMoveAssignBase<T, false>: TCopyAssignBase<T> {
+ using TCopyAssignBase<T>::TCopyAssignBase;
+
+ constexpr TMoveAssignBase() noexcept = default;
+ constexpr TMoveAssignBase(const TMoveAssignBase&) = default;
+ constexpr TMoveAssignBase(TMoveAssignBase&&) = default;
+ TMoveAssignBase& operator=(const TMoveAssignBase&) = default;
+ TMoveAssignBase& operator=(TMoveAssignBase&& rhs) noexcept(
+ std::is_nothrow_move_assignable<T>::value&&
+ std::is_nothrow_move_constructible<T>::value)
+ {
+ if (this->Defined_) {
+ if (rhs.Defined_) {
+ this->Data_ = std::move(rhs.Data_);
+ } else {
+ this->Data_.~T();
+ this->Defined_ = false;
+ }
+ } else if (rhs.Defined_) {
+ new (std::addressof(this->Data_)) T(std::move(rhs.Data_));
+ this->Defined_ = true;
+ }
+ return *this;
+ }
+ };
+}
+
+template <class T>
+using TMaybeBase = NMaybe::TMoveAssignBase<T>;
diff --git a/util/generic/maybe_ut.cpp b/util/generic/maybe_ut.cpp
new file mode 100644
index 0000000000..2c1a425c5e
--- /dev/null
+++ b/util/generic/maybe_ut.cpp
@@ -0,0 +1,1006 @@
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/stream/str.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+#include "maybe.h"
+
+class TIncrementOnDestroy {
+private:
+ int* Ptr_;
+
+public:
+ TIncrementOnDestroy(int* ptr) noexcept
+ : Ptr_(ptr)
+ {
+ }
+
+ ~TIncrementOnDestroy() {
+ ++*Ptr_;
+ }
+};
+
+Y_UNIT_TEST_SUITE(TMaybeTest) {
+ Y_UNIT_TEST(TestStatic) {
+ using T1 = TMaybe<int>;
+ static_assert(std::is_trivially_copy_constructible<T1>::value, "");
+ static_assert(std::is_trivially_destructible<T1>::value, "");
+
+ using T2 = TMaybe<TString*>;
+ static_assert(std::is_trivially_copy_constructible<T2>::value, "");
+ static_assert(std::is_trivially_destructible<T2>::value, "");
+
+ using T3 = TMaybe<TMaybe<double>>;
+ static_assert(std::is_trivially_copy_constructible<T3>::value, "");
+ static_assert(std::is_trivially_destructible<T3>::value, "");
+
+ using T4 = TMaybe<TString>;
+ static_assert(!std::is_trivially_copy_constructible<T4>::value, "");
+ static_assert(!std::is_trivially_destructible<T4>::value, "");
+ }
+
+ Y_UNIT_TEST(TestWarning) {
+ TMaybe<size_t> x;
+ TStringStream ss;
+ TString line;
+
+ while (ss.ReadLine(line)) {
+ x = line.size();
+ }
+
+ if (x == 5u) {
+ ss << "5\n";
+ }
+ }
+
+ Y_UNIT_TEST(TTestConstructorDestructor) {
+ int a = 0;
+ int b = 0;
+
+ TMaybe<TIncrementOnDestroy>();
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ TMaybe<TIncrementOnDestroy>(TIncrementOnDestroy(&a));
+ b += 2;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ {
+ TMaybe<TIncrementOnDestroy> m1 = TIncrementOnDestroy(&a);
+ b += 1;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ TMaybe<TIncrementOnDestroy> m2 = m1;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ TMaybe<TIncrementOnDestroy> m3;
+ m3 = m1;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+ }
+
+ b += 3;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ {
+ TMaybe<TIncrementOnDestroy> m4 = TIncrementOnDestroy(&a);
+ b += 1;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ m4 = TIncrementOnDestroy(&a);
+ b += 1;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ m4.Clear();
+ b += 1;
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+
+ m4.Clear();
+ UNIT_ASSERT_VALUES_EQUAL(a, b);
+ }
+ }
+
+ Y_UNIT_TEST(TestAssignmentClear) {
+ TMaybe<int> m5;
+ UNIT_ASSERT(!m5.Defined());
+ UNIT_ASSERT(m5.Empty());
+ UNIT_ASSERT(m5 == TMaybe<int>());
+ UNIT_ASSERT(m5 == Nothing());
+ UNIT_ASSERT(m5 != TMaybe<int>(4));
+
+ m5 = 4;
+
+ UNIT_ASSERT(m5.Defined());
+ UNIT_ASSERT(!m5.Empty());
+
+ UNIT_ASSERT_VALUES_EQUAL(4, m5.GetRef());
+ UNIT_ASSERT(m5 == TMaybe<int>(4));
+ UNIT_ASSERT(m5 != TMaybe<int>(3));
+ UNIT_ASSERT(m5 != TMaybe<int>());
+ UNIT_ASSERT(m5 != Nothing());
+
+ m5 = TMaybe<int>(5);
+ UNIT_ASSERT(m5.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(5, m5.GetRef());
+ UNIT_ASSERT(m5 == TMaybe<int>(5));
+ UNIT_ASSERT(m5 != TMaybe<int>(4));
+
+ m5 = TMaybe<int>();
+ UNIT_ASSERT(m5.Empty());
+ UNIT_ASSERT(m5 == TMaybe<int>());
+ UNIT_ASSERT(m5 == Nothing());
+ UNIT_ASSERT(m5 != TMaybe<int>(5));
+
+ m5 = 4;
+ m5 = Nothing();
+
+ UNIT_ASSERT(m5.Empty());
+ UNIT_ASSERT(m5 == TMaybe<int>());
+ UNIT_ASSERT(m5 == Nothing());
+ UNIT_ASSERT(m5 != TMaybe<int>(5));
+
+ m5 = {};
+ UNIT_ASSERT(m5.Empty());
+ }
+
+ Y_UNIT_TEST(TestInPlace) {
+ TMaybe<int> m;
+
+ UNIT_ASSERT(!m);
+
+ m.ConstructInPlace(1);
+
+ UNIT_ASSERT(m == 1);
+
+ auto& x = m.ConstructInPlace(2);
+
+ UNIT_ASSERT(m == 2);
+ x = 7;
+ UNIT_ASSERT(m == 7);
+ }
+
+ Y_UNIT_TEST(TestMove) {
+ struct TMovable {
+ int Flag = 0;
+
+ TMovable(int flag)
+ : Flag(flag)
+ {
+ }
+
+ TMovable(const TMovable&) = delete;
+ TMovable& operator=(const TMovable&) = delete;
+
+ TMovable(TMovable&& other) {
+ std::swap(Flag, other.Flag);
+ }
+ TMovable& operator=(TMovable&& other) {
+ std::swap(Flag, other.Flag);
+ return *this;
+ }
+ };
+
+ // Move ctor from value
+ TMovable value1(1);
+ TMaybe<TMovable> m1(std::move(value1));
+ UNIT_ASSERT(m1.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(m1->Flag, 1);
+
+ // Move assignment from value
+ TMovable value2(2);
+ TMaybe<TMovable> m2;
+ m2 = std::move(value2);
+ UNIT_ASSERT(m2.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(m2->Flag, 2);
+
+ // Move ctor from maybe
+ TMaybe<TMovable> m3(std::move(m1));
+ UNIT_ASSERT(m3.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(m3->Flag, 1);
+
+ // Move assignment from maybe
+ TMaybe<TMovable> m4;
+ m4 = std::move(m2);
+ UNIT_ASSERT(m4.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(m4->Flag, 2);
+
+ // Move value from temporary maybe instance
+ TMovable o5 = *MakeMaybe<TMovable>(5);
+ UNIT_ASSERT_VALUES_EQUAL(o5.Flag, 5);
+ TMovable o6 = MakeMaybe<TMovable>(6).GetRef();
+ UNIT_ASSERT_VALUES_EQUAL(o6.Flag, 6);
+ }
+
+ Y_UNIT_TEST(TestCast) {
+ // Undefined maybe casts to undefined maybe
+ TMaybe<short> shortMaybe;
+ const auto undefinedMaybe = shortMaybe.Cast<long>();
+ UNIT_ASSERT(!undefinedMaybe.Defined());
+
+ // Defined maybe casts to defined maybe of another type
+ shortMaybe = 34;
+ const auto longMaybe = shortMaybe.Cast<long>();
+ UNIT_ASSERT(longMaybe.Defined());
+ UNIT_ASSERT_VALUES_EQUAL(34, longMaybe.GetRef());
+ }
+
+ Y_UNIT_TEST(TestGetOr) {
+ UNIT_ASSERT_VALUES_EQUAL(TMaybe<TString>().GetOrElse("xxx"), TString("xxx"));
+ UNIT_ASSERT_VALUES_EQUAL(TMaybe<TString>("yyy").GetOrElse("xxx"), TString("yyy"));
+
+ {
+ TString xxx = "xxx";
+ UNIT_ASSERT_VALUES_EQUAL(TMaybe<TString>().GetOrElse(xxx).append('x'), TString("xxxx"));
+ UNIT_ASSERT_VALUES_EQUAL(xxx, "xxxx");
+ }
+
+ {
+ TString xxx = "xxx";
+ UNIT_ASSERT_VALUES_EQUAL(TMaybe<TString>("yyy").GetOrElse(xxx).append('x'), TString("yyyx"));
+ UNIT_ASSERT_VALUES_EQUAL(xxx, "xxx");
+ }
+ }
+
+ /*
+ ==
+ !=
+ <
+ <=
+ >
+ >=
+*/
+
+ Y_UNIT_TEST(TestCompareEqualEmpty) {
+ TMaybe<int> m1;
+ TMaybe<int> m2;
+
+ UNIT_ASSERT(m1 == m2);
+ UNIT_ASSERT(!(m1 != m2));
+ UNIT_ASSERT(!(m1 < m2));
+ UNIT_ASSERT(m1 <= m2);
+ UNIT_ASSERT(!(m1 > m2));
+ UNIT_ASSERT(m1 >= m2);
+ }
+
+ Y_UNIT_TEST(TestCompareEqualNonEmpty) {
+ TMaybe<int> m1{1};
+ TMaybe<int> m2{1};
+
+ UNIT_ASSERT(m1 == m2);
+ UNIT_ASSERT(!(m1 != m2));
+ UNIT_ASSERT(!(m1 < m2));
+ UNIT_ASSERT(m1 <= m2);
+ UNIT_ASSERT(!(m1 > m2));
+ UNIT_ASSERT(m1 >= m2);
+ }
+
+ Y_UNIT_TEST(TestCompareOneLessThanOther) {
+ TMaybe<int> m1{1};
+ TMaybe<int> m2{2};
+
+ UNIT_ASSERT(!(m1 == m2));
+ UNIT_ASSERT(m1 != m2);
+ UNIT_ASSERT(m1 < m2);
+ UNIT_ASSERT(m1 <= m2);
+ UNIT_ASSERT(!(m1 > m2));
+ UNIT_ASSERT(!(m1 >= m2));
+ }
+
+ Y_UNIT_TEST(TestCompareTMaybeAndT_Equal) {
+ TMaybe<int> m{1};
+ int v{1};
+
+ UNIT_ASSERT(m == v);
+ UNIT_ASSERT(!(m != v));
+ UNIT_ASSERT(!(m < v));
+ UNIT_ASSERT(m <= v);
+ UNIT_ASSERT(!(m > v));
+ UNIT_ASSERT(m >= v);
+
+ UNIT_ASSERT(v == m);
+ UNIT_ASSERT(!(v != m));
+ UNIT_ASSERT(!(v < m));
+ UNIT_ASSERT(v <= m);
+ UNIT_ASSERT(!(v > m));
+ UNIT_ASSERT(v >= m);
+ }
+
+ Y_UNIT_TEST(TestCompareTMaybeAndT_TMaybeLessThanT) {
+ TMaybe<int> m{1};
+ int v{2};
+
+ UNIT_ASSERT(!(m == v));
+ UNIT_ASSERT(m != v);
+ UNIT_ASSERT(m < v);
+ UNIT_ASSERT(m <= v);
+ UNIT_ASSERT(!(m > v));
+ UNIT_ASSERT(!(m >= v));
+
+ UNIT_ASSERT(!(v == m));
+ UNIT_ASSERT(v != m);
+ UNIT_ASSERT(!(v < m));
+ UNIT_ASSERT(!(v <= m));
+ UNIT_ASSERT(v > m);
+ UNIT_ASSERT(v >= m);
+ }
+
+ Y_UNIT_TEST(TestCompareTMaybeAndT_TMaybeGreaterThanT) {
+ TMaybe<int> m{2};
+ int v{1};
+
+ UNIT_ASSERT(!(m == v));
+ UNIT_ASSERT(m != v);
+ UNIT_ASSERT(!(m < v));
+ UNIT_ASSERT(!(m <= v));
+ UNIT_ASSERT(m > v);
+ UNIT_ASSERT(m >= v);
+
+ UNIT_ASSERT(!(v == m));
+ UNIT_ASSERT(v != m);
+ UNIT_ASSERT(v < m);
+ UNIT_ASSERT(v <= m);
+ UNIT_ASSERT(!(v > m));
+ UNIT_ASSERT(!(v >= m));
+ }
+
+ Y_UNIT_TEST(TestCompareEmptyTMaybeAndT) {
+ TMaybe<int> m;
+ int v{1};
+
+ UNIT_ASSERT(!(m == v));
+ UNIT_ASSERT(m != v);
+ UNIT_ASSERT(m < v);
+ UNIT_ASSERT(m <= v);
+ UNIT_ASSERT(!(m > v));
+ UNIT_ASSERT(!(m >= v));
+
+ UNIT_ASSERT(!(v == m));
+ UNIT_ASSERT(v != m);
+ UNIT_ASSERT(!(v < m));
+ UNIT_ASSERT(!(v <= m));
+ UNIT_ASSERT(v > m);
+ UNIT_ASSERT(v >= m);
+ }
+
+ Y_UNIT_TEST(TestCompareEmptyTMaybeAndNothing) {
+ TMaybe<int> m;
+ auto n = Nothing();
+
+ UNIT_ASSERT(m == n);
+ UNIT_ASSERT(!(m != n));
+ UNIT_ASSERT(!(m < n));
+ UNIT_ASSERT(m <= n);
+ UNIT_ASSERT(!(m > n));
+ UNIT_ASSERT(m >= n);
+
+ UNIT_ASSERT(n == m);
+ UNIT_ASSERT(!(n != m));
+ UNIT_ASSERT(!(n < m));
+ UNIT_ASSERT(n <= m);
+ UNIT_ASSERT(!(n > m));
+ UNIT_ASSERT(n >= m);
+ }
+
+ Y_UNIT_TEST(TestCompareNonEmptyTMaybeAndNothing) {
+ TMaybe<int> m{1};
+ auto n = Nothing();
+
+ UNIT_ASSERT(!(m == n));
+ UNIT_ASSERT(m != n);
+ UNIT_ASSERT(!(m < n));
+ UNIT_ASSERT(!(m <= n));
+ UNIT_ASSERT(m > n);
+ UNIT_ASSERT(m >= n);
+
+ UNIT_ASSERT(!(n == m));
+ UNIT_ASSERT(n != m);
+ UNIT_ASSERT(n < m);
+ UNIT_ASSERT(n <= m);
+ UNIT_ASSERT(!(n > m));
+ UNIT_ASSERT(!(n >= m));
+ }
+
+ Y_UNIT_TEST(TestCompareTMaybeAndConvertibleT_Equal) {
+ TMaybe<size_t> m{1};
+ unsigned int v{1};
+
+ UNIT_ASSERT(m == v);
+ UNIT_ASSERT(!(m != v));
+ UNIT_ASSERT(!(m < v));
+ UNIT_ASSERT(m <= v);
+ UNIT_ASSERT(!(m > v));
+ UNIT_ASSERT(m >= v);
+
+ UNIT_ASSERT(v == m);
+ UNIT_ASSERT(!(v != m));
+ UNIT_ASSERT(!(v < m));
+ UNIT_ASSERT(v <= m);
+ UNIT_ASSERT(!(v > m));
+ UNIT_ASSERT(v >= m);
+ }
+
+ Y_UNIT_TEST(TestCompareTMaybeAndConvertibleT_TMaybeLessThanT) {
+ TMaybe<size_t> m{1};
+ unsigned int v{2};
+
+ UNIT_ASSERT(!(m == v));
+ UNIT_ASSERT(m != v);
+ UNIT_ASSERT(m < v);
+ UNIT_ASSERT(m <= v);
+ UNIT_ASSERT(!(m > v));
+ UNIT_ASSERT(!(m >= v));
+
+ UNIT_ASSERT(!(v == m));
+ UNIT_ASSERT(v != m);
+ UNIT_ASSERT(!(v < m));
+ UNIT_ASSERT(!(v <= m));
+ UNIT_ASSERT(v > m);
+ UNIT_ASSERT(v >= m);
+ }
+
+ Y_UNIT_TEST(TestCompareTMaybeAndConvertibleT_TMaybeGreaterThanT) {
+ TMaybe<size_t> m{2};
+ unsigned int v{1};
+
+ UNIT_ASSERT(!(m == v));
+ UNIT_ASSERT(m != v);
+ UNIT_ASSERT(!(m < v));
+ UNIT_ASSERT(!(m <= v));
+ UNIT_ASSERT(m > v);
+ UNIT_ASSERT(m >= v);
+
+ UNIT_ASSERT(!(v == m));
+ UNIT_ASSERT(v != m);
+ UNIT_ASSERT(v < m);
+ UNIT_ASSERT(v <= m);
+ UNIT_ASSERT(!(v > m));
+ UNIT_ASSERT(!(v >= m));
+ }
+
+ Y_UNIT_TEST(TestCompareEmptyTMaybeAndConvertibleT) {
+ TMaybe<size_t> m;
+ unsigned int v{1};
+
+ UNIT_ASSERT(!(m == v));
+ UNIT_ASSERT(m != v);
+ UNIT_ASSERT(m < v);
+ UNIT_ASSERT(m <= v);
+ UNIT_ASSERT(!(m > v));
+ UNIT_ASSERT(!(m >= v));
+
+ UNIT_ASSERT(!(v == m));
+ UNIT_ASSERT(v != m);
+ UNIT_ASSERT(!(v < m));
+ UNIT_ASSERT(!(v <= m));
+ UNIT_ASSERT(v > m);
+ UNIT_ASSERT(v >= m);
+ }
+
+ Y_UNIT_TEST(TestMakeMaybe) {
+ {
+ auto m1 = MakeMaybe<int>(1);
+ UNIT_ASSERT(*m1 == 1);
+ }
+
+ {
+ struct TMockClass {
+ TMockClass(int i)
+ : I_(i)
+ {
+ }
+
+ TMockClass(const TMockClass& other)
+ : I_(other.I_)
+ {
+ IsCopyConstructorCalled_ = true;
+ }
+
+ TMockClass& operator=(const TMockClass& other) {
+ if (this != &other) {
+ I_ = other.I_;
+ IsCopyAssignmentOperatorCalled_ = true;
+ }
+
+ return *this;
+ }
+
+ TMockClass(TMockClass&& other)
+ : I_(other.I_)
+ {
+ IsMoveConstructorCalled_ = true;
+ }
+
+ TMockClass& operator=(TMockClass&& other) {
+ if (this != &other) {
+ I_ = other.I_;
+ IsMoveAssignmentOperatorCalled_ = true;
+ }
+
+ return *this;
+ }
+
+ int I_;
+ bool IsCopyConstructorCalled_{false};
+ bool IsMoveConstructorCalled_{false};
+ bool IsCopyAssignmentOperatorCalled_{false};
+ bool IsMoveAssignmentOperatorCalled_{false};
+ };
+
+ auto m2 = MakeMaybe<TMockClass>(1);
+ UNIT_ASSERT(m2->I_ == 1);
+ UNIT_ASSERT(!m2->IsCopyConstructorCalled_);
+ UNIT_ASSERT(!m2->IsMoveConstructorCalled_);
+ UNIT_ASSERT(!m2->IsCopyAssignmentOperatorCalled_);
+ UNIT_ASSERT(!m2->IsMoveAssignmentOperatorCalled_);
+ }
+
+ {
+ auto m3 = MakeMaybe<TVector<int>>({1, 2, 3, 4, 5});
+ UNIT_ASSERT(m3->size() == 5);
+ UNIT_ASSERT(m3->at(0) == 1);
+ UNIT_ASSERT(m3->at(1) == 2);
+ UNIT_ASSERT(m3->at(2) == 3);
+ UNIT_ASSERT(m3->at(3) == 4);
+ UNIT_ASSERT(m3->at(4) == 5);
+ }
+
+ {
+ struct TMockStruct4 {
+ TMockStruct4(int a, int b, int c)
+ : A_(a)
+ , B_(b)
+ , C_(c)
+ {
+ }
+
+ int A_;
+ int B_;
+ int C_;
+ };
+
+ auto m4 = MakeMaybe<TMockStruct4>(1, 2, 3);
+ UNIT_ASSERT(m4->A_ == 1);
+ UNIT_ASSERT(m4->B_ == 2);
+ UNIT_ASSERT(m4->C_ == 3);
+ }
+
+ {
+ struct TMockStruct5 {
+ TMockStruct5(const TVector<int>& vec, bool someFlag)
+ : Vec_(vec)
+ , SomeFlag_(someFlag)
+ {
+ }
+
+ TVector<int> Vec_;
+ bool SomeFlag_;
+ };
+
+ auto m5 = MakeMaybe<TMockStruct5>({1, 2, 3}, true);
+ UNIT_ASSERT(m5->Vec_.size() == 3);
+ UNIT_ASSERT(m5->Vec_[0] == 1);
+ UNIT_ASSERT(m5->Vec_[1] == 2);
+ UNIT_ASSERT(m5->Vec_[2] == 3);
+ UNIT_ASSERT(m5->SomeFlag_);
+ }
+ }
+
+ Y_UNIT_TEST(TestSwappingUsingMemberSwap) {
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = 2;
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(*m2 == 2);
+
+ m1.Swap(m2);
+
+ UNIT_ASSERT(*m1 == 2);
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = Nothing();
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+
+ m1.Swap(m2);
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = Nothing();
+ TMaybe<int> m2 = 1;
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+
+ m1.Swap(m2);
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+ }
+ }
+
+ Y_UNIT_TEST(TestSwappingUsingMemberLittleSwap) {
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = 2;
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(*m2 == 2);
+
+ m1.swap(m2);
+
+ UNIT_ASSERT(*m1 == 2);
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = Nothing();
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+
+ m1.swap(m2);
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = Nothing();
+ TMaybe<int> m2 = 1;
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+
+ m1.swap(m2);
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+ }
+ }
+
+ Y_UNIT_TEST(TestSwappingUsingGlobalSwap) {
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = 2;
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(*m2 == 2);
+
+ ::Swap(m1, m2);
+
+ UNIT_ASSERT(*m1 == 2);
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = Nothing();
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+
+ ::Swap(m1, m2);
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = Nothing();
+ TMaybe<int> m2 = 1;
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+
+ ::Swap(m1, m2);
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+ }
+ }
+
+ Y_UNIT_TEST(TestSwappingUsingGlobalDoSwap) {
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = 2;
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(*m2 == 2);
+
+ ::DoSwap(m1, m2);
+
+ UNIT_ASSERT(*m1 == 2);
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = Nothing();
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+
+ ::DoSwap(m1, m2);
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = Nothing();
+ TMaybe<int> m2 = 1;
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+
+ ::DoSwap(m1, m2);
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+ }
+ }
+
+ Y_UNIT_TEST(TestSwappingUsingStdSwap) {
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = 2;
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(*m2 == 2);
+
+ ::std::swap(m1, m2);
+
+ UNIT_ASSERT(*m1 == 2);
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = 1;
+ TMaybe<int> m2 = Nothing();
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+
+ ::std::swap(m1, m2);
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+ }
+
+ {
+ TMaybe<int> m1 = Nothing();
+ TMaybe<int> m2 = 1;
+
+ UNIT_ASSERT(m1 == Nothing());
+ UNIT_ASSERT(*m2 == 1);
+
+ ::std::swap(m1, m2);
+
+ UNIT_ASSERT(*m1 == 1);
+ UNIT_ASSERT(m2 == Nothing());
+ }
+ }
+
+ Y_UNIT_TEST(TestOutputStreamEmptyMaybe) {
+ TString s;
+ TStringOutput output(s);
+ output << TMaybe<int>();
+ UNIT_ASSERT_EQUAL("(empty maybe)", s);
+ }
+
+ Y_UNIT_TEST(TestOutputStreamNothing) {
+ TString s;
+ TStringOutput output(s);
+ output << Nothing();
+ UNIT_ASSERT_VALUES_EQUAL("(empty maybe)", s);
+ }
+
+ Y_UNIT_TEST(TestOutputStreamDefinedMaybe) {
+ TString s;
+ TStringOutput output(s);
+ output << TMaybe<int>(42);
+ UNIT_ASSERT_EQUAL("42", s);
+ }
+
+ Y_UNIT_TEST(TestMaybeCovarianceImplicit) {
+ struct TestStruct {
+ TestStruct(int value)
+ : Value_(value)
+ {
+ }
+
+ operator int() const {
+ return Value_;
+ }
+
+ static TMaybe<int> Unwrap(TMaybe<TestStruct> testStructMaybe) {
+ return testStructMaybe;
+ }
+
+ int Value_;
+ };
+
+ TMaybe<int> testMaybeFull = TestStruct::Unwrap(TMaybe<int>(42));
+ UNIT_ASSERT(testMaybeFull.Defined());
+ UNIT_ASSERT_EQUAL(testMaybeFull.GetRef(), 42);
+
+ TMaybe<int> testMaybeEmpty = TestStruct::Unwrap(TMaybe<int>());
+ UNIT_ASSERT(!testMaybeEmpty.Defined());
+ }
+
+ Y_UNIT_TEST(TestMaybeCovarianceExplicit) {
+ struct TestStruct {
+ explicit TestStruct(int value)
+ : Value_(value)
+ {
+ }
+ int Value_;
+ };
+
+ TMaybe<TestStruct> testStructMaybeFull(TMaybe<int>(42));
+ UNIT_ASSERT(testStructMaybeFull.Defined());
+ UNIT_ASSERT_EQUAL(testStructMaybeFull.GetRef().Value_, 42);
+
+ TMaybe<int> empty;
+ TMaybe<TestStruct> testStructMaybeEmpty(empty);
+ UNIT_ASSERT(!testStructMaybeEmpty.Defined());
+ }
+
+ Y_UNIT_TEST(TestMaybeCovarianceAssign) {
+ struct TestStruct {
+ explicit TestStruct(int value)
+ : Value_(value)
+ {
+ }
+ TestStruct& operator=(int value) {
+ Value_ = value;
+ return *this;
+ }
+ int Value_;
+ };
+
+ TMaybe<TestStruct> testStructMaybe(Nothing());
+ UNIT_ASSERT(!testStructMaybe.Defined());
+
+ testStructMaybe = TMaybe<int>(42);
+ UNIT_ASSERT(testStructMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().Value_, 42);
+
+ testStructMaybe = TMaybe<int>(23);
+ UNIT_ASSERT(testStructMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().Value_, 23);
+
+ testStructMaybe = TMaybe<int>();
+ UNIT_ASSERT(!testStructMaybe.Defined());
+ }
+
+ Y_UNIT_TEST(TestMaybeCovarianceNonTrivial) {
+ struct TestStruct {
+ enum {
+ FromValue,
+ FromMaybe,
+ };
+ TestStruct(int value)
+ : Value_(value)
+ , From_(FromValue)
+ {
+ }
+ TestStruct(TMaybe<int> value)
+ : Value_(value.Defined() ? value.GetRef() : 0)
+ , From_(FromMaybe)
+ {
+ }
+ int Value_;
+ int From_;
+ };
+
+ TMaybe<TestStruct> testStructFromMaybe(TMaybe<int>(42));
+ UNIT_ASSERT(testStructFromMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructFromMaybe.GetRef().From_, TestStruct::FromMaybe);
+ UNIT_ASSERT_EQUAL(testStructFromMaybe.GetRef().Value_, 42);
+
+ TMaybe<int> empty;
+ TMaybe<TestStruct> testStructFromEmptyMaybe(empty);
+ UNIT_ASSERT(testStructFromEmptyMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructFromEmptyMaybe.GetRef().From_, TestStruct::FromMaybe);
+ UNIT_ASSERT_EQUAL(testStructFromEmptyMaybe.GetRef().Value_, 0);
+
+ TMaybe<TestStruct> testStructFromValue(23);
+ UNIT_ASSERT(testStructFromValue.Defined());
+ UNIT_ASSERT_EQUAL(testStructFromValue.GetRef().From_, TestStruct::FromValue);
+ UNIT_ASSERT_EQUAL(testStructFromValue.GetRef().Value_, 23);
+ }
+
+ Y_UNIT_TEST(TestMaybeCovarianceNonTrivialAssign) {
+ struct TestStruct {
+ enum {
+ FromValue,
+ FromMaybe,
+ };
+ TestStruct(int value)
+ : Value_(value)
+ , From_(FromValue)
+ {
+ }
+ TestStruct(TMaybe<int> value)
+ : Value_(value.Defined() ? value.GetRef() : 0)
+ , From_(FromMaybe)
+ {
+ }
+ TestStruct& operator=(int value) {
+ Value_ = value;
+ From_ = FromValue;
+ return *this;
+ }
+ TestStruct& operator=(TMaybe<int> value) {
+ Value_ = value.Defined() ? value.GetRef() : 0;
+ From_ = FromMaybe;
+ return *this;
+ }
+ int Value_;
+ int From_;
+ };
+
+ TMaybe<TestStruct> testStructMaybe(Nothing());
+ testStructMaybe = TMaybe<int>(42);
+ UNIT_ASSERT(testStructMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().From_, TestStruct::FromMaybe);
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().Value_, 42);
+
+ testStructMaybe = TMaybe<int>();
+ UNIT_ASSERT(testStructMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().From_, TestStruct::FromMaybe);
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().Value_, 0);
+
+ testStructMaybe = 23;
+ UNIT_ASSERT(testStructMaybe.Defined());
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().From_, TestStruct::FromValue);
+ UNIT_ASSERT_EQUAL(testStructMaybe.GetRef().Value_, 23);
+ }
+
+ Y_UNIT_TEST(TestMaybeConvertion) {
+ struct TSrc {};
+ struct TDst {
+ bool FromMaybeConstructorApplied;
+
+ explicit TDst(TSrc)
+ : FromMaybeConstructorApplied(false)
+ {
+ }
+
+ explicit TDst(TMaybe<TSrc>)
+ : FromMaybeConstructorApplied(true)
+ {
+ }
+
+ TDst& operator=(TSrc) {
+ FromMaybeConstructorApplied = false;
+ return *this;
+ }
+ TDst& operator=(TMaybe<TSrc>) {
+ FromMaybeConstructorApplied = true;
+ return *this;
+ }
+ };
+
+ auto m = TMaybe<TDst>(TMaybe<TSrc>());
+ UNIT_ASSERT(m.Defined());
+ UNIT_ASSERT(m->FromMaybeConstructorApplied);
+
+ m = TMaybe<TSrc>();
+ UNIT_ASSERT(m.Defined());
+ UNIT_ASSERT(m->FromMaybeConstructorApplied);
+ }
+
+ Y_UNIT_TEST(TestOnEmptyException) {
+ TMaybe<TStringBuf> v;
+ UNIT_ASSERT_EXCEPTION_CONTAINS(v.GetRef(), yexception, "StringBuf");
+ }
+}
diff --git a/util/generic/maybe_ut.pyx b/util/generic/maybe_ut.pyx
new file mode 100644
index 0000000000..2de185c807
--- /dev/null
+++ b/util/generic/maybe_ut.pyx
@@ -0,0 +1,185 @@
+from util.generic.maybe cimport TMaybe, Nothing
+
+import pytest
+import unittest
+
+
+def _check_from_py(TMaybe[int] x):
+ return x.Defined()
+
+
+def _check_to_py_value():
+ cdef TMaybe[int] tmp = TMaybe[int](42)
+ return tmp
+
+
+def _check_to_py_nothing():
+ cdef TMaybe[int] tmp = Nothing()
+ return tmp
+
+
+class TestMaybe(unittest.TestCase):
+
+ def test_ctor1(self):
+ cdef TMaybe[int] tmp = TMaybe[int]()
+ self.assertFalse(tmp.Defined())
+
+ def test_ctor2(self):
+ cdef TMaybe[int] tmp = TMaybe[int](42)
+ self.assertTrue(tmp.Defined())
+ self.assertEquals(tmp.GetRef(), 42)
+
+ def test_ctor3(self):
+ cdef TMaybe[int] tmp = Nothing()
+ self.assertFalse(tmp.Defined())
+
+ def test_operator_assign(self):
+ cdef TMaybe[int] tmp
+ tmp = 42
+ self.assertTrue(tmp.Defined())
+ self.assertEquals(tmp.GetRef(), 42)
+
+ def test_compare(self):
+ cdef TMaybe[int] tmp1 = 17
+ cdef TMaybe[int] tmp2 = 42
+ cdef TMaybe[int] nothing
+
+ # ==
+ self.assertTrue(tmp1 == 17)
+ self.assertTrue(tmp1 == tmp1)
+ self.assertTrue(nothing == nothing)
+
+ self.assertFalse(tmp1 == 16)
+ self.assertFalse(tmp1 == tmp2)
+ self.assertFalse(tmp1 == nothing)
+
+ # !=
+ self.assertTrue(tmp1 != 16)
+ self.assertTrue(tmp1 != tmp2)
+ self.assertTrue(tmp1 != nothing)
+
+ self.assertFalse(tmp1 != 17)
+ self.assertFalse(tmp1 != tmp1)
+ self.assertFalse(nothing != nothing)
+
+ # <
+ self.assertTrue(nothing < tmp1)
+ self.assertTrue(nothing < tmp2)
+ self.assertTrue(tmp1 < tmp2)
+ self.assertTrue(nothing < 0)
+ self.assertTrue(tmp1 < 18)
+
+ self.assertFalse(nothing < nothing)
+ self.assertFalse(tmp1 < tmp1)
+ self.assertFalse(tmp2 < tmp1)
+ self.assertFalse(tmp1 < 16)
+
+ # <=
+ self.assertTrue(nothing <= nothing)
+ self.assertTrue(nothing <= tmp1)
+ self.assertTrue(nothing <= tmp2)
+ self.assertTrue(tmp1 <= tmp1)
+ self.assertTrue(tmp1 <= tmp2)
+ self.assertTrue(nothing <= 0)
+ self.assertTrue(tmp1 <= 18)
+
+ self.assertFalse(tmp2 <= tmp1)
+ self.assertFalse(tmp1 <= 16)
+
+ # >
+ self.assertTrue(tmp1 > nothing)
+ self.assertTrue(tmp2 > nothing)
+ self.assertTrue(tmp2 > tmp1)
+ self.assertTrue(tmp1 > 16)
+
+ self.assertFalse(nothing > nothing)
+ self.assertFalse(nothing > 0)
+ self.assertFalse(tmp1 > tmp1)
+ self.assertFalse(tmp1 > tmp2)
+ self.assertFalse(tmp1 > 18)
+
+ # >=
+ self.assertTrue(nothing >= nothing)
+ self.assertTrue(tmp1 >= nothing)
+ self.assertTrue(tmp2 >= nothing)
+ self.assertTrue(tmp2 >= tmp1)
+ self.assertTrue(tmp1 >= tmp1)
+ self.assertTrue(tmp1 >= 16)
+
+ self.assertFalse(nothing >= 0)
+ self.assertFalse(tmp1 >= tmp2)
+ self.assertFalse(tmp1 >= 18)
+
+ def test_construct_in_place(self):
+ cdef TMaybe[int] tmp
+ tmp.ConstructInPlace(42)
+ self.assertTrue(tmp.Defined())
+ self.assertEquals(tmp.GetRef(), 42)
+
+ def test_clear(self):
+ cdef TMaybe[int] tmp = 42
+ tmp.Clear()
+ self.assertFalse(tmp.Defined())
+
+ def test_defined(self):
+ cdef TMaybe[int] tmp
+ self.assertFalse(tmp.Defined())
+ self.assertTrue(tmp.Empty())
+ tmp = 42
+ self.assertTrue(tmp.Defined())
+ self.assertFalse(tmp.Empty())
+
+ def test_check_defined(self):
+ cdef TMaybe[int] tmp
+ with pytest.raises(RuntimeError):
+ tmp.CheckDefined()
+ tmp = 42
+ tmp.CheckDefined()
+
+ def test_get(self):
+ cdef TMaybe[int] tmp = 42
+ cdef int* p = tmp.Get()
+ self.assertTrue(p != NULL)
+ self.assertEquals(p[0], 42)
+
+ def test_get_ref(self):
+ cdef TMaybe[int] tmp = 42
+ self.assertTrue(tmp.Defined())
+ self.assertEquals(tmp.GetRef(), 42)
+
+ def test_get_or_else(self):
+ cdef TMaybe[int] tmp = 42
+ self.assertEquals(tmp.GetOrElse(13), 42)
+ tmp.Clear()
+ self.assertEquals(tmp.GetOrElse(13), 13)
+
+ def test_or_else(self):
+ cdef TMaybe[int] tmp = 42
+ cdef TMaybe[int] nothing
+ self.assertFalse(nothing.OrElse(nothing).Defined())
+ self.assertEquals(tmp.OrElse(nothing).GetRef(), 42)
+ self.assertEquals(nothing.OrElse(tmp).GetRef(), 42)
+ self.assertEquals(tmp.OrElse(tmp).GetRef(), 42)
+
+ def test_cast(self):
+ cdef TMaybe[int] tmp = 42
+ cdef TMaybe[char] tmp2 = tmp.Cast[char]()
+ self.assertEquals(tmp2.GetRef(), 42)
+
+ def test_swap(self):
+ cdef TMaybe[int] tmp1 = 42
+ cdef TMaybe[int] tmp2
+ tmp2.Swap(tmp1)
+ self.assertFalse(tmp1.Defined())
+ self.assertEquals(tmp2.GetRef(), 42)
+
+ def test_from_py(self):
+ self.assertTrue(_check_from_py(42))
+ self.assertFalse(_check_from_py(None))
+
+ with self.assertRaises(TypeError):
+ _check_from_py("ttt")
+
+ def test_to_py(self):
+ self.assertEquals(_check_to_py_value(), 42)
+ self.assertEquals(_check_to_py_nothing(), None)
diff --git a/util/generic/mem_copy.cpp b/util/generic/mem_copy.cpp
new file mode 100644
index 0000000000..dd96c6fbcf
--- /dev/null
+++ b/util/generic/mem_copy.cpp
@@ -0,0 +1 @@
+#include "mem_copy.h"
diff --git a/util/generic/mem_copy.h b/util/generic/mem_copy.h
new file mode 100644
index 0000000000..b68c852953
--- /dev/null
+++ b/util/generic/mem_copy.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "typetraits.h"
+
+#include <util/system/types.h>
+
+#include <cstring>
+
+template <class T>
+using TIfPOD = std::enable_if_t<TTypeTraits<T>::IsPod, T*>;
+
+template <class T>
+using TIfNotPOD = std::enable_if_t<!TTypeTraits<T>::IsPod, T*>;
+
+template <class T>
+static inline TIfPOD<T> MemCopy(T* to, const T* from, size_t n) noexcept {
+ if (n) {
+ memcpy(to, from, n * sizeof(T));
+ }
+
+ return to;
+}
+
+template <class T>
+static inline TIfNotPOD<T> MemCopy(T* to, const T* from, size_t n) {
+ for (size_t i = 0; i < n; ++i) {
+ to[i] = from[i];
+ }
+
+ return to;
+}
+
+template <class T>
+static inline TIfPOD<T> MemMove(T* to, const T* from, size_t n) noexcept {
+ if (n) {
+ memmove(to, from, n * sizeof(T));
+ }
+
+ return to;
+}
+
+template <class T>
+static inline TIfNotPOD<T> MemMove(T* to, const T* from, size_t n) {
+ if (to <= from || to >= from + n) {
+ return MemCopy(to, from, n);
+ }
+
+ //copy backwards
+ while (n) {
+ to[n - 1] = from[n - 1];
+ --n;
+ }
+
+ return to;
+}
diff --git a/util/generic/mem_copy_ut.cpp b/util/generic/mem_copy_ut.cpp
new file mode 100644
index 0000000000..8b55a11cf6
--- /dev/null
+++ b/util/generic/mem_copy_ut.cpp
@@ -0,0 +1,113 @@
+#include "mem_copy.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+namespace {
+ class TAssignBCalled: public yexception {
+ };
+
+ struct TB {
+ inline TB& operator=(const TB&) {
+ throw TAssignBCalled();
+
+ return *this;
+ }
+ };
+
+ struct TC: public TB {
+ };
+}
+
+Y_DECLARE_PODTYPE(TB);
+
+Y_UNIT_TEST_SUITE(TestMemCopy) {
+ Y_UNIT_TEST(Test1) {
+ char buf[] = "123";
+ char buf1[sizeof(buf)];
+
+ UNIT_ASSERT_EQUAL(MemCopy(buf1, buf, sizeof(buf)), buf1);
+
+ for (size_t i = 0; i < sizeof(buf); ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(buf[i], buf1[i]);
+ }
+ }
+
+ static int x = 0;
+
+ struct TA {
+ inline TA() {
+ X = ++x;
+ }
+
+ int X;
+ };
+
+ Y_UNIT_TEST(Test2) {
+ x = 0;
+
+ TA a1[5];
+ TA a2[5];
+
+ UNIT_ASSERT_VALUES_EQUAL(a1[0].X, 1);
+ UNIT_ASSERT_VALUES_EQUAL(a2[0].X, 6);
+
+ MemCopy(a2, a1, 5);
+
+ for (size_t i = 0; i < 5; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(a1[i].X, a2[i].X);
+ }
+ }
+
+ Y_UNIT_TEST(Test3) {
+ TB b1[5];
+ TB b2[5];
+
+ MemCopy(b2, b1, 5);
+ }
+
+ Y_UNIT_TEST(Test4) {
+ TC c1[5];
+ TC c2[5];
+
+ UNIT_ASSERT_EXCEPTION(MemCopy(c2, c1, 5), TAssignBCalled);
+ }
+
+ template <class T>
+ inline void FillX(T* b, T* e) {
+ int tmp = 0;
+
+ while (b != e) {
+ (b++)->X = ++tmp;
+ }
+ }
+
+ Y_UNIT_TEST(Test5) {
+ struct TD {
+ int X;
+ };
+
+ TD orig[50];
+
+ for (ssize_t i = -15; i < 15; ++i) {
+ TD* b = orig + 20;
+ TD* e = b + 10;
+
+ FillX(b, e);
+
+ TD* to = b + i;
+
+ MemMove(to, b, e - b - 1);
+
+ for (size_t j = 0; j < (e - b) - (size_t)1; ++j) {
+ UNIT_ASSERT_VALUES_EQUAL(to[j].X, j + 1);
+ }
+ }
+ }
+
+ Y_UNIT_TEST(TestEmpty) {
+ char* tmp = nullptr;
+
+ UNIT_ASSERT(MemCopy(tmp, tmp, 0) == nullptr);
+ UNIT_ASSERT(MemMove(tmp, tmp, 0) == nullptr);
+ }
+}
diff --git a/util/generic/noncopyable.cpp b/util/generic/noncopyable.cpp
new file mode 100644
index 0000000000..a614dca34f
--- /dev/null
+++ b/util/generic/noncopyable.cpp
@@ -0,0 +1 @@
+#include "noncopyable.h"
diff --git a/util/generic/noncopyable.h b/util/generic/noncopyable.h
new file mode 100644
index 0000000000..c007934133
--- /dev/null
+++ b/util/generic/noncopyable.h
@@ -0,0 +1,38 @@
+#pragma once
+
+/**
+ * @class TNonCopyable
+ *
+ * Inherit your class from `TNonCopyable` if you want to make it noncopyable.
+ *
+ * Example usage:
+ * @code
+ * class Foo: private TNonCopyable {
+ * // ...
+ * };
+ * @endcode
+ */
+
+namespace NNonCopyable { // protection from unintended ADL
+ struct TNonCopyable {
+ TNonCopyable(const TNonCopyable&) = delete;
+ TNonCopyable& operator=(const TNonCopyable&) = delete;
+
+ TNonCopyable() = default;
+ ~TNonCopyable() = default;
+ };
+
+ struct TMoveOnly {
+ TMoveOnly(TMoveOnly&&) noexcept = default;
+ TMoveOnly& operator=(TMoveOnly&&) noexcept = default;
+
+ TMoveOnly(const TMoveOnly&) = delete;
+ TMoveOnly& operator=(const TMoveOnly&) = delete;
+
+ TMoveOnly() = default;
+ ~TMoveOnly() = default;
+ };
+}
+
+using TNonCopyable = NNonCopyable::TNonCopyable;
+using TMoveOnly = NNonCopyable::TMoveOnly;
diff --git a/util/generic/object_counter.cpp b/util/generic/object_counter.cpp
new file mode 100644
index 0000000000..09f4b272b5
--- /dev/null
+++ b/util/generic/object_counter.cpp
@@ -0,0 +1 @@
+#include "object_counter.h"
diff --git a/util/generic/object_counter.h b/util/generic/object_counter.h
new file mode 100644
index 0000000000..5257afa2e6
--- /dev/null
+++ b/util/generic/object_counter.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <util/system/atomic.h>
+
+/**
+ * Simple thread-safe per-class counter that can be used to make sure you don't
+ * have any leaks in your code, or for statistical purposes.
+ *
+ * Example usage:
+ * \code
+ * class TMyClass: public TObjectCounter<TMyClass> {
+ * // ...
+ * };
+ *
+ * // In your code:
+ * Cerr << "TMyClass instances in use: " << TMyClass::ObjectCount() << Endl;
+ * \endcode
+ */
+template <class T>
+class TObjectCounter {
+public:
+ inline TObjectCounter() noexcept {
+ AtomicIncrement(Count_);
+ }
+
+ inline TObjectCounter(const TObjectCounter& /*item*/) noexcept {
+ AtomicIncrement(Count_);
+ }
+
+ inline ~TObjectCounter() {
+ AtomicDecrement(Count_);
+ }
+
+ static inline long ObjectCount() noexcept {
+ return AtomicGet(Count_);
+ }
+
+ /**
+ * Resets object count. Mainly for tests, as you don't want to do this in
+ * your code and then end up with negative counts.
+ *
+ * \returns Current object count.
+ */
+ static inline long ResetObjectCount() noexcept {
+ return AtomicSwap(&Count_, 0);
+ }
+
+private:
+ static TAtomic Count_;
+};
+
+template <class T>
+TAtomic TObjectCounter<T>::Count_ = 0;
diff --git a/util/generic/objects_counter_ut.cpp b/util/generic/objects_counter_ut.cpp
new file mode 100644
index 0000000000..4d5da37a56
--- /dev/null
+++ b/util/generic/objects_counter_ut.cpp
@@ -0,0 +1,36 @@
+#include "object_counter.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(ObjectsCounter) {
+ struct TObject: public TObjectCounter<TObject> {
+ };
+
+ Y_UNIT_TEST(Test1) {
+ TObject obj;
+ TVector<TObject> objects;
+ for (ui32 i = 0; i < 100; ++i) {
+ objects.push_back(obj);
+ }
+ UNIT_ASSERT_EQUAL(TObjectCounter<TObject>::ObjectCount(), 101);
+ }
+
+ Y_UNIT_TEST(TestEq) {
+ TObject obj;
+ {
+ TObject obj1 = obj;
+ UNIT_ASSERT_EQUAL(TObjectCounter<TObject>::ObjectCount(), 2);
+ }
+ UNIT_ASSERT_EQUAL(TObjectCounter<TObject>::ObjectCount(), 1);
+ }
+
+ Y_UNIT_TEST(TestMove) {
+ TObject obj;
+ UNIT_ASSERT_EQUAL(TObjectCounter<TObject>::ObjectCount(), 1);
+ {
+ TObject obj1 = std::move(obj);
+ UNIT_ASSERT_EQUAL(TObjectCounter<TObject>::ObjectCount(), 2);
+ }
+ UNIT_ASSERT_EQUAL(TObjectCounter<TObject>::ObjectCount(), 1);
+ }
+}
diff --git a/util/generic/overloaded.cpp b/util/generic/overloaded.cpp
new file mode 100644
index 0000000000..fb6ff88d31
--- /dev/null
+++ b/util/generic/overloaded.cpp
@@ -0,0 +1 @@
+#include "overloaded.h"
diff --git a/util/generic/overloaded.h b/util/generic/overloaded.h
new file mode 100644
index 0000000000..96a97e44bc
--- /dev/null
+++ b/util/generic/overloaded.h
@@ -0,0 +1,56 @@
+#pragma once
+
+/**
+ * Construct an ad-hoc object with an overloaded `operator()`.
+ *
+ * Typically used with lambdas to construct type-matching visitors for e.g. std::variant:
+ * ```
+ * std::variant<int, void*, TString> var;
+ * Visit(TOverloaded{
+ * [](int val) { Cerr << "int: " << val; },
+ * [](void* val) { Cerr << "ptr: " << val; },
+ * [](const TString& val) { Cerr << "str: " << val; },
+ * }, var);
+ * ```
+ *
+ * *** IMPORTANT NOTE (IMPLICIT ARGUMENT CONVERSIONS) ***
+ *
+ * Since the resulting objects use regular overloaded method resolution rules,
+ * methods may be called by inexact matches (causing implicit casts), hence this
+ * implementation does not guarantee exhaustiveness of cases.
+ *
+ * For example, the following code will compile and run by casting all values to
+ * double:
+ * ```
+ * std::variant<int, double, char> var;
+ * Visit(TOverloaded{
+ * [](double val) { Cerr << "dbl: " << val; },
+ * }, var);
+ * ```
+ *
+ * If cases may be ambigous or specific type-matching logic is required,
+ * a verbose `if constexpr`-based version would be preferred:
+ * ```
+ * std::variant<int, double, char> var;
+ * Visit([](auto&& val) {
+ * using T = std::decay_t<decltype(val)>;
+ * if constexpr (std::is_same_v<T, int>) {
+ * Cerr << "int: " << val;
+ * } else if constexpr (std::is_same_v<T, double>) {
+ * Cerr << "dbl: " << val;
+ * } else if constexpr (std::is_same_v<T, char>) {
+ * Cerr << "chr: " << val;
+ * } else {
+ * static_assert(TDependentFalse<T>, "unexpected type");
+ * }
+ * }, var);
+ * ```
+ */
+
+template <class... Fs>
+struct TOverloaded: Fs... {
+ using Fs::operator()...;
+};
+
+template <class... Fs>
+TOverloaded(Fs...) -> TOverloaded<Fs...>;
diff --git a/util/generic/overloaded_ut.cpp b/util/generic/overloaded_ut.cpp
new file mode 100644
index 0000000000..f3d73895ad
--- /dev/null
+++ b/util/generic/overloaded_ut.cpp
@@ -0,0 +1,82 @@
+#include <util/generic/overloaded.h>
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/variant.h>
+#include <util/generic/algorithm.h>
+
+#include <tuple>
+
+namespace {
+ struct TType1 {};
+ struct TType2 {};
+ struct TType3 {};
+}
+
+Y_UNIT_TEST_SUITE(TOverloadedTest) {
+ Y_UNIT_TEST(StaticTest) {
+ auto f = TOverloaded{
+ [](const TType1&) {},
+ [](const TType2&) {},
+ [](const TType3&) {}};
+ using F = decltype(f);
+ static_assert(std::is_invocable_v<F, TType1>);
+ static_assert(std::is_invocable_v<F, TType2>);
+ static_assert(std::is_invocable_v<F, TType3>);
+ static_assert(!std::is_invocable_v<F, int>);
+ static_assert(!std::is_invocable_v<F, double>);
+ }
+
+ Y_UNIT_TEST(VariantTest) {
+ std::variant<int, double, TType1> v = 5;
+ int res = 0;
+ std::visit(TOverloaded{
+ [&](int val) { res = val; },
+ [&](double) { res = -1; },
+ [&](TType1) { res = -1; }},
+ v);
+ UNIT_ASSERT_VALUES_EQUAL(res, 5);
+ }
+
+ Y_UNIT_TEST(TupleTest) {
+ std::tuple<int, double, bool, int> t{5, 3.14, true, 20};
+ TString res;
+
+ ForEach(t, TOverloaded{
+ [&](int val) { res += "(int) " + ToString(val) + ' '; },
+ [&](double val) { res += "(double) " + ToString(val) + ' '; },
+ [&](bool val) { res += "(bool) " + ToString(val) + ' '; },
+ });
+
+ UNIT_ASSERT_VALUES_EQUAL(res, "(int) 5 (double) 3.14 (bool) 1 (int) 20 ");
+ }
+
+ Y_UNIT_TEST(ImplicitConversionsTest) {
+ using TTestVariant = std::variant<int, double, char>;
+
+ // Purposefully exhibit inexact overload matched with implicit type
+ // conversions
+
+ // All cases implicitly cast to int
+ auto matchAsInt = [](TTestVariant var) {
+ return std::visit(TOverloaded{
+ [](int val) { return val; },
+ }, var);
+ };
+
+ UNIT_ASSERT_VALUES_EQUAL(matchAsInt(TTestVariant{17.77}), 17);
+ UNIT_ASSERT_VALUES_EQUAL(matchAsInt(TTestVariant{12345}), 12345);
+ UNIT_ASSERT_VALUES_EQUAL(matchAsInt(TTestVariant{'X'}), 88);
+
+ // All cases implicitly cast to double
+ auto matchAsDouble = [](TTestVariant var) {
+ return std::visit(TOverloaded{
+ [](double val) { return val; },
+ }, var);
+ };
+
+ UNIT_ASSERT_VALUES_EQUAL(matchAsDouble(TTestVariant{17.77}), 17.77);
+ UNIT_ASSERT_VALUES_EQUAL(matchAsDouble(TTestVariant{12345}), 12345.0);
+ UNIT_ASSERT_VALUES_EQUAL(matchAsDouble(TTestVariant{'X'}), 88.0);
+ }
+}
diff --git a/util/generic/ptr.cpp b/util/generic/ptr.cpp
new file mode 100644
index 0000000000..b29baebc17
--- /dev/null
+++ b/util/generic/ptr.cpp
@@ -0,0 +1,17 @@
+#include "ptr.h"
+
+#include <util/system/defaults.h>
+#include <util/memory/alloc.h>
+
+#include <new>
+#include <cstdlib>
+
+void TFree::DoDestroy(void* t) noexcept {
+ free(t);
+}
+
+void TDelete::Destroy(void* t) noexcept {
+ ::operator delete(t);
+}
+
+TThrRefBase::~TThrRefBase() = default;
diff --git a/util/generic/ptr.h b/util/generic/ptr.h
new file mode 100644
index 0000000000..19db0e3ec5
--- /dev/null
+++ b/util/generic/ptr.h
@@ -0,0 +1,1145 @@
+#pragma once
+
+#include "fwd.h"
+#include "utility.h"
+#include "intrlist.h"
+#include "refcount.h"
+#include "typetraits.h"
+#include "singleton.h"
+
+#include <utility>
+
+#include <util/system/yassert.h>
+#include <util/system/defaults.h>
+
+template <class T, class U>
+using TGuardConversion = typename std::enable_if_t<std::is_convertible<U*, T*>::value>;
+
+template <class T>
+inline void AssertTypeComplete() {
+ // If compiler triggers this error from destructor of your class with
+ // smart pointer, then may be you should move the destructor definition
+ // to the .cpp file, where type T have full definition.
+ //
+ // 'delete' called on pointer to incomplete type is
+ // undefined behavior (missing destructor call/corrupted memory manager).
+ // 'sizeof' is used to trigger compile-time error.
+ static_assert(sizeof(T) != 0, "Type must be complete");
+}
+
+template <class T>
+inline void CheckedDelete(T* t) {
+ AssertTypeComplete<T>();
+
+ delete t;
+}
+
+template <class T>
+inline void CheckedArrayDelete(T* t) {
+ AssertTypeComplete<T>();
+
+ delete[] t;
+}
+
+class TNoAction {
+public:
+ template <class T>
+ static inline void Destroy(T*) noexcept {
+ }
+};
+
+class TDelete {
+public:
+ template <class T>
+ static inline void Destroy(T* t) noexcept {
+ CheckedDelete(t);
+ }
+
+ /*
+ * special handling for nullptr - call nothing
+ */
+ static inline void Destroy(std::nullptr_t) noexcept {
+ }
+
+ /*
+ * special handling for void* - call ::operator delete()
+ */
+ static void Destroy(void* t) noexcept;
+};
+
+class TDeleteArray {
+public:
+ template <class T>
+ static inline void Destroy(T* t) noexcept {
+ CheckedArrayDelete(t);
+ }
+};
+
+class TDestructor {
+public:
+ template <class T>
+ static inline void Destroy(T* t) noexcept {
+ (void)t;
+ t->~T();
+ }
+};
+
+class TFree {
+public:
+ template <class T>
+ static inline void Destroy(T* t) noexcept {
+ DoDestroy((void*)t);
+ }
+
+private:
+ /*
+ * we do not want dependancy on cstdlib here...
+ */
+ static void DoDestroy(void* t) noexcept;
+};
+
+template <class Base, class T>
+class TPointerCommon {
+public:
+ using TValueType = T;
+
+ inline T* operator->() const noexcept {
+ T* ptr = AsT();
+ Y_ASSERT(ptr);
+ return ptr;
+ }
+
+#ifndef __cpp_impl_three_way_comparison
+ template <class C>
+ inline bool operator==(const C& p) const noexcept {
+ return (p == AsT());
+ }
+
+ template <class C>
+ inline bool operator!=(const C& p) const noexcept {
+ return (p != AsT());
+ }
+#endif
+
+ inline explicit operator bool() const noexcept {
+ return nullptr != AsT();
+ }
+
+protected:
+ inline T* AsT() const noexcept {
+ return (static_cast<const Base*>(this))->Get();
+ }
+
+ static inline T* DoRelease(T*& t) noexcept {
+ T* ret = t;
+ t = nullptr;
+ return ret;
+ }
+};
+
+template <class Base, class T>
+class TPointerBase: public TPointerCommon<Base, T> {
+public:
+ inline T& operator*() const noexcept {
+ Y_ASSERT(this->AsT());
+
+ return *(this->AsT());
+ }
+
+ inline T& operator[](size_t n) const noexcept {
+ Y_ASSERT(this->AsT());
+
+ return (this->AsT())[n];
+ }
+};
+
+/*
+ * void*-like pointers does not have operator*
+ */
+template <class Base>
+class TPointerBase<Base, void>: public TPointerCommon<Base, void> {
+};
+
+template <class T, class D>
+class TAutoPtr: public TPointerBase<TAutoPtr<T, D>, T> {
+public:
+ inline TAutoPtr(T* t = nullptr) noexcept
+ : T_(t)
+ {
+ }
+
+ inline TAutoPtr(const TAutoPtr& t) noexcept
+ : T_(t.Release())
+ {
+ }
+
+ inline ~TAutoPtr() {
+ DoDestroy();
+ }
+
+ inline TAutoPtr& operator=(const TAutoPtr& t) noexcept {
+ if (this != &t) {
+ Reset(t.Release());
+ }
+
+ return *this;
+ }
+
+ inline T* Release() const noexcept Y_WARN_UNUSED_RESULT {
+ return this->DoRelease(T_);
+ }
+
+ inline void Reset(T* t) noexcept {
+ if (T_ != t) {
+ DoDestroy();
+ T_ = t;
+ }
+ }
+
+ inline void Reset() noexcept {
+ Destroy();
+ }
+
+ inline void Destroy() noexcept {
+ Reset(nullptr);
+ }
+
+ inline void Swap(TAutoPtr& r) noexcept {
+ DoSwap(T_, r.T_);
+ }
+
+ inline T* Get() const noexcept {
+ return T_;
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ inline void DoDestroy() noexcept {
+ if (T_) {
+ D::Destroy(T_);
+ }
+ }
+
+private:
+ mutable T* T_;
+};
+
+template <class T, class D>
+class THolder: public TPointerBase<THolder<T, D>, T> {
+public:
+ constexpr THolder() noexcept
+ : T_(nullptr)
+ {
+ }
+
+ constexpr THolder(std::nullptr_t) noexcept
+ : T_(nullptr)
+ {
+ }
+
+ explicit THolder(T* t) noexcept
+ : T_(t)
+ {
+ }
+
+ inline THolder(TAutoPtr<T, D> t) noexcept
+ : T_(t.Release())
+ {
+ }
+
+ template <class U, class = TGuardConversion<T, U>>
+ inline THolder(TAutoPtr<U, D> t) noexcept
+ : T_(t.Release())
+ {
+ }
+
+ inline THolder(THolder&& that) noexcept
+ : T_(that.Release())
+ {
+ }
+
+ template <class U, class = TGuardConversion<T, U>>
+ inline THolder(THolder<U, D>&& that) noexcept
+ : T_(that.Release())
+ {
+ }
+
+ THolder(const THolder&) = delete;
+ THolder& operator=(const THolder&) = delete;
+
+ inline ~THolder() {
+ DoDestroy();
+ }
+
+ inline void Destroy() noexcept {
+ Reset(nullptr);
+ }
+
+ inline T* Release() noexcept Y_WARN_UNUSED_RESULT {
+ return this->DoRelease(T_);
+ }
+
+ inline void Reset(T* t) noexcept {
+ if (T_ != t) {
+ DoDestroy();
+ T_ = t;
+ }
+ }
+
+ inline void Reset(TAutoPtr<T, D> t) noexcept {
+ Reset(t.Release());
+ }
+
+ inline void Reset() noexcept {
+ Destroy();
+ }
+
+ inline void Swap(THolder& r) noexcept {
+ DoSwap(T_, r.T_);
+ }
+
+ inline T* Get() const noexcept {
+ return T_;
+ }
+
+ inline operator TAutoPtr<T, D>() noexcept {
+ return Release();
+ }
+
+ THolder& operator=(std::nullptr_t) noexcept {
+ this->Reset(nullptr);
+ return *this;
+ }
+
+ THolder& operator=(THolder&& that) noexcept {
+ this->Reset(that.Release());
+ return *this;
+ }
+
+ template <class U>
+ THolder& operator=(THolder<U, D>&& that) noexcept {
+ this->Reset(that.Release());
+ return *this;
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ inline void DoDestroy() noexcept {
+ if (T_) {
+ D::Destroy(T_);
+ }
+ }
+
+private:
+ T* T_;
+};
+
+template <typename T, typename... Args>
+[[nodiscard]] THolder<T> MakeHolder(Args&&... args) {
+ return THolder<T>(new T(std::forward<Args>(args)...));
+}
+
+/*
+ * usage:
+ * class T: public TRefCounted<T>
+ * and we get methods Ref() && UnRef() with
+ * proper destruction of last UnRef()
+ */
+template <class T, class C, class D>
+class TRefCounted {
+public:
+ inline TRefCounted(long initval = 0) noexcept
+ : Counter_(initval)
+ {
+ }
+
+ inline ~TRefCounted() = default;
+
+ inline void Ref(TAtomicBase d) noexcept {
+ auto resultCount = Counter_.Add(d);
+ Y_ASSERT(resultCount >= d);
+ (void)resultCount;
+ }
+
+ inline void Ref() noexcept {
+ auto resultCount = Counter_.Inc();
+ Y_ASSERT(resultCount != 0);
+ (void)resultCount;
+ }
+
+ inline void UnRef(TAtomicBase d) noexcept {
+ auto resultCount = Counter_.Sub(d);
+ Y_ASSERT(resultCount >= 0);
+ if (resultCount == 0) {
+ D::Destroy(static_cast<T*>(this));
+ }
+ }
+
+ inline void UnRef() noexcept {
+ UnRef(1);
+ }
+
+ inline TAtomicBase RefCount() const noexcept {
+ return Counter_.Val();
+ }
+
+ inline void DecRef() noexcept {
+ auto resultCount = Counter_.Dec();
+ Y_ASSERT(resultCount >= 0);
+ (void)resultCount;
+ }
+
+ TRefCounted(const TRefCounted&)
+ : Counter_(0)
+ {
+ }
+
+ void operator=(const TRefCounted&) {
+ }
+
+private:
+ C Counter_;
+};
+
+/**
+ * Atomically reference-counted base with a virtual destructor.
+ *
+ * @note Plays well with inheritance, should be used for refcounted base classes.
+ */
+struct TThrRefBase: public TRefCounted<TThrRefBase, TAtomicCounter> {
+ virtual ~TThrRefBase();
+};
+
+/**
+ * Atomically reference-counted base.
+ *
+ * Deletes refcounted object as type T.
+ *
+ * @warning Additional care should be taken with regard to inheritance. If used
+ * as a base class, @p T should either declare a virtual destructor, or be
+ * derived from @p TThrRefBase instead. Otherwise, only destructor of class @p T
+ * would be called, potentially slicing the object and creating memory leaks.
+ *
+ * @note To avoid accidental inheritance when it is not originally intended,
+ * class @p T should be marked as final.
+ */
+template <class T, class D = TDelete>
+using TAtomicRefCount = TRefCounted<T, TAtomicCounter, D>;
+
+/**
+ * Non-atomically reference-counted base.
+ *
+ * @warning Not thread-safe. Use with great care. If in doubt, use @p ThrRefBase
+ * or @p TAtomicRefCount instead.
+ */
+template <class T, class D = TDelete>
+using TSimpleRefCount = TRefCounted<T, TSimpleCounter, D>;
+
+template <class T>
+class TDefaultIntrusivePtrOps {
+public:
+ static inline void Ref(T* t) noexcept {
+ Y_ASSERT(t);
+
+ t->Ref();
+ }
+
+ static inline void UnRef(T* t) noexcept {
+ Y_ASSERT(t);
+
+ t->UnRef();
+ }
+
+ static inline void DecRef(T* t) noexcept {
+ Y_ASSERT(t);
+
+ t->DecRef();
+ }
+
+ static inline long RefCount(const T* t) noexcept {
+ Y_ASSERT(t);
+
+ return t->RefCount();
+ }
+};
+
+template <class T, class Ops>
+class TIntrusivePtr: public TPointerBase<TIntrusivePtr<T, Ops>, T> {
+ template <class U, class O>
+ friend class TIntrusivePtr;
+
+ template <class U, class O>
+ friend class TIntrusiveConstPtr;
+
+public:
+ struct TNoIncrement {
+ };
+
+ inline TIntrusivePtr(T* t = nullptr) noexcept
+ : T_(t)
+ {
+ Ops();
+ Ref();
+ }
+
+ inline TIntrusivePtr(T* t, TNoIncrement) noexcept
+ : T_(t)
+ {
+ Ops();
+ }
+
+ inline ~TIntrusivePtr() {
+ UnRef();
+ }
+
+ inline TIntrusivePtr(const TIntrusivePtr& p) noexcept
+ : T_(p.T_)
+ {
+ Ref();
+ }
+
+ // NOTE:
+ // without std::enable_if_t compiler sometimes tries to use this constructor inappropriately
+ // e.g.
+ // struct A {};
+ // struct B {};
+ // void Func(TIntrusivePtr<A>);
+ // void Func(TIntrusivePtr<B>);
+ // ...
+ // Func(TIntrusivePtr<A>(new A)); // <--- compiler can't decide which version of Func to use
+ template <class U, class = TGuardConversion<T, U>>
+ inline TIntrusivePtr(const TIntrusivePtr<U>& p) noexcept
+ : T_(p.Get())
+ {
+ Ref();
+ }
+
+ template <class U, class = TGuardConversion<T, U>>
+ inline TIntrusivePtr(TIntrusivePtr<U>&& p) noexcept
+ : T_(p.T_)
+ {
+ p.T_ = nullptr;
+ }
+
+ inline TIntrusivePtr(TIntrusivePtr&& p) noexcept
+ : T_(nullptr)
+ {
+ Swap(p);
+ }
+
+ inline TIntrusivePtr& operator=(TIntrusivePtr p) noexcept {
+ p.Swap(*this);
+
+ return *this;
+ }
+
+ // Effectively replace both:
+ // Reset(const TIntrusivePtr&)
+ // Reset(TIntrusivePtr&&)
+ inline void Reset(TIntrusivePtr t) noexcept {
+ Swap(t);
+ }
+
+ inline void Reset() noexcept {
+ Drop();
+ }
+
+ inline T* Get() const noexcept {
+ return T_;
+ }
+
+ inline void Swap(TIntrusivePtr& r) noexcept {
+ DoSwap(T_, r.T_);
+ }
+
+ inline void Drop() noexcept {
+ TIntrusivePtr(nullptr).Swap(*this);
+ }
+
+ inline T* Release() const noexcept Y_WARN_UNUSED_RESULT {
+ T* res = T_;
+ if (T_) {
+ Ops::DecRef(T_);
+ T_ = nullptr;
+ }
+ return res;
+ }
+
+ inline long RefCount() const noexcept {
+ return T_ ? Ops::RefCount(T_) : 0;
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ inline void Ref() noexcept {
+ if (T_) {
+ Ops::Ref(T_);
+ }
+ }
+
+ inline void UnRef() noexcept {
+ if (T_) {
+ Ops::UnRef(T_);
+ }
+ }
+
+private:
+ mutable T* T_;
+};
+
+template <class T, class Ops>
+struct THash<TIntrusivePtr<T, Ops>>: THash<const T*> {
+ using THash<const T*>::operator();
+ inline size_t operator()(const TIntrusivePtr<T, Ops>& ptr) const {
+ return THash<const T*>::operator()(ptr.Get());
+ }
+};
+
+// Behaves like TIntrusivePtr but returns const T* to prevent user from accidentally modifying the referenced object.
+template <class T, class Ops>
+class TIntrusiveConstPtr: public TPointerBase<TIntrusiveConstPtr<T, Ops>, const T> {
+public:
+ inline TIntrusiveConstPtr(T* t = nullptr) noexcept // we need a non-const pointer to Ref(), UnRef() and eventually delete it.
+ : T_(t)
+ {
+ Ops();
+ Ref();
+ }
+
+ inline ~TIntrusiveConstPtr() {
+ UnRef();
+ }
+
+ inline TIntrusiveConstPtr(const TIntrusiveConstPtr& p) noexcept
+ : T_(p.T_)
+ {
+ Ref();
+ }
+
+ inline TIntrusiveConstPtr(TIntrusiveConstPtr&& p) noexcept
+ : T_(nullptr)
+ {
+ Swap(p);
+ }
+
+ inline TIntrusiveConstPtr(TIntrusivePtr<T> p) noexcept
+ : T_(p.T_)
+ {
+ p.T_ = nullptr;
+ }
+
+ template <class U, class = TGuardConversion<T, U>>
+ inline TIntrusiveConstPtr(const TIntrusiveConstPtr<U>& p) noexcept
+ : T_(p.T_)
+ {
+ Ref();
+ }
+
+ template <class U, class = TGuardConversion<T, U>>
+ inline TIntrusiveConstPtr(TIntrusiveConstPtr<U>&& p) noexcept
+ : T_(p.T_)
+ {
+ p.T_ = nullptr;
+ }
+
+ inline TIntrusiveConstPtr& operator=(TIntrusiveConstPtr p) noexcept {
+ p.Swap(*this);
+
+ return *this;
+ }
+
+ // Effectively replace both:
+ // Reset(const TIntrusiveConstPtr&)
+ // Reset(TIntrusiveConstPtr&&)
+ inline void Reset(TIntrusiveConstPtr t) noexcept {
+ Swap(t);
+ }
+
+ inline void Reset() noexcept {
+ Drop();
+ }
+
+ inline const T* Get() const noexcept {
+ return T_;
+ }
+
+ inline void Swap(TIntrusiveConstPtr& r) noexcept {
+ DoSwap(T_, r.T_);
+ }
+
+ inline void Drop() noexcept {
+ TIntrusiveConstPtr(nullptr).Swap(*this);
+ }
+
+ inline long RefCount() const noexcept {
+ return T_ ? Ops::RefCount(T_) : 0;
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ inline void Ref() noexcept {
+ if (T_ != nullptr) {
+ Ops::Ref(T_);
+ }
+ }
+
+ inline void UnRef() noexcept {
+ if (T_ != nullptr) {
+ Ops::UnRef(T_);
+ }
+ }
+
+private:
+ T* T_;
+
+ template <class U, class O>
+ friend class TIntrusiveConstPtr;
+};
+
+template <class T, class Ops>
+struct THash<TIntrusiveConstPtr<T, Ops>>: THash<const T*> {
+ using THash<const T*>::operator();
+ inline size_t operator()(const TIntrusiveConstPtr<T, Ops>& ptr) const {
+ return THash<const T*>::operator()(ptr.Get());
+ }
+};
+
+template <class T, class Ops>
+class TSimpleIntrusiveOps {
+ using TFunc = void (*)(T*)
+#if __cplusplus >= 201703
+ noexcept
+#endif
+ ;
+
+ static void DoRef(T* t) noexcept {
+ Ops::Ref(t);
+ }
+
+ static void DoUnRef(T* t) noexcept {
+ Ops::UnRef(t);
+ }
+
+public:
+ inline TSimpleIntrusiveOps() noexcept {
+ InitStaticOps();
+ }
+
+ inline ~TSimpleIntrusiveOps() = default;
+
+ static inline void Ref(T* t) noexcept {
+ Ref_(t);
+ }
+
+ static inline void UnRef(T* t) noexcept {
+ UnRef_(t);
+ }
+
+private:
+ static inline void InitStaticOps() noexcept {
+ struct TInit {
+ inline TInit() noexcept {
+ Ref_ = DoRef;
+ UnRef_ = DoUnRef;
+ }
+ };
+
+ Singleton<TInit>();
+ }
+
+private:
+ static TFunc Ref_;
+ static TFunc UnRef_;
+};
+
+template <class T, class Ops>
+typename TSimpleIntrusiveOps<T, Ops>::TFunc TSimpleIntrusiveOps<T, Ops>::Ref_ = nullptr;
+
+template <class T, class Ops>
+typename TSimpleIntrusiveOps<T, Ops>::TFunc TSimpleIntrusiveOps<T, Ops>::UnRef_ = nullptr;
+
+template <typename T, class Ops = TDefaultIntrusivePtrOps<T>, typename... Args>
+[[nodiscard]] TIntrusivePtr<T, Ops> MakeIntrusive(Args&&... args) {
+ return new T{std::forward<Args>(args)...};
+}
+
+template <typename T, class Ops = TDefaultIntrusivePtrOps<T>, typename... Args>
+[[nodiscard]] TIntrusiveConstPtr<T, Ops> MakeIntrusiveConst(Args&&... args) {
+ return new T{std::forward<Args>(args)...};
+}
+
+template <class T, class C, class D>
+class TSharedPtr: public TPointerBase<TSharedPtr<T, C, D>, T> {
+ template <class TT, class CC, class DD>
+ friend class TSharedPtr;
+
+public:
+ inline TSharedPtr() noexcept
+ : T_(nullptr)
+ , C_(nullptr)
+ {
+ }
+
+ inline TSharedPtr(T* t) {
+ THolder<T, D> h(t);
+
+ Init(h);
+ }
+
+ inline TSharedPtr(TAutoPtr<T, D> t) {
+ Init(t);
+ }
+
+ inline TSharedPtr(T* t, C* c) noexcept
+ : T_(t)
+ , C_(c)
+ {
+ }
+
+ template <class TT, class = TGuardConversion<T, TT>>
+ inline TSharedPtr(THolder<TT>&& t) {
+ Init(t);
+ }
+
+ inline ~TSharedPtr() {
+ UnRef();
+ }
+
+ inline TSharedPtr(const TSharedPtr& t) noexcept
+ : T_(t.T_)
+ , C_(t.C_)
+ {
+ Ref();
+ }
+
+ inline TSharedPtr(TSharedPtr&& t) noexcept
+ : T_(nullptr)
+ , C_(nullptr)
+ {
+ Swap(t);
+ }
+
+ template <class TT, class = TGuardConversion<T, TT>>
+ inline TSharedPtr(const TSharedPtr<TT, C, D>& t) noexcept
+ : T_(t.T_)
+ , C_(t.C_)
+ {
+ Ref();
+ }
+
+ template <class TT, class = TGuardConversion<T, TT>>
+ inline TSharedPtr(TSharedPtr<TT, C, D>&& t) noexcept
+ : T_(t.T_)
+ , C_(t.C_)
+ {
+ t.T_ = nullptr;
+ t.C_ = nullptr;
+ }
+
+ inline TSharedPtr& operator=(TSharedPtr t) noexcept {
+ t.Swap(*this);
+
+ return *this;
+ }
+
+ // Effectively replace both:
+ // Reset(const TSharedPtr& t)
+ // Reset(TSharedPtr&& t)
+ inline void Reset(TSharedPtr t) noexcept {
+ Swap(t);
+ }
+
+ inline void Reset() noexcept {
+ Drop();
+ }
+
+ inline void Drop() noexcept {
+ TSharedPtr().Swap(*this);
+ }
+
+ inline T* Get() const noexcept {
+ return T_;
+ }
+
+ inline C* ReferenceCounter() const noexcept {
+ return C_;
+ }
+
+ inline void Swap(TSharedPtr& r) noexcept {
+ DoSwap(T_, r.T_);
+ DoSwap(C_, r.C_);
+ }
+
+ inline long RefCount() const noexcept {
+ return C_ ? C_->Val() : 0;
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ template <class X>
+ inline void Init(X& t) {
+ C_ = !!t ? new C(1) : nullptr;
+ T_ = t.Release();
+ }
+
+ inline void Ref() noexcept {
+ if (C_) {
+ C_->Inc();
+ }
+ }
+
+ inline void UnRef() noexcept {
+ if (C_ && !C_->Dec()) {
+ DoDestroy();
+ }
+ }
+
+ inline void DoDestroy() noexcept {
+ if (T_) {
+ D::Destroy(T_);
+ }
+
+ delete C_;
+ }
+
+private:
+ T* T_;
+ C* C_;
+};
+
+template <class T, class C, class D>
+struct THash<TSharedPtr<T, C, D>>: THash<const T*> {
+ using THash<const T*>::operator();
+ inline size_t operator()(const TSharedPtr<T, C, D>& ptr) const {
+ return THash<const T*>::operator()(ptr.Get());
+ }
+};
+
+template <class T, class D = TDelete>
+using TAtomicSharedPtr = TSharedPtr<T, TAtomicCounter, D>;
+
+// use with great care. if in doubt, use TAtomicSharedPtr instead
+template <class T, class D = TDelete>
+using TSimpleSharedPtr = TSharedPtr<T, TSimpleCounter, D>;
+
+template <typename T, typename C, typename... Args>
+[[nodiscard]] TSharedPtr<T, C> MakeShared(Args&&... args) {
+ return new T{std::forward<Args>(args)...};
+}
+
+template <typename T, typename... Args>
+[[nodiscard]] inline TAtomicSharedPtr<T> MakeAtomicShared(Args&&... args) {
+ return MakeShared<T, TAtomicCounter>(std::forward<Args>(args)...);
+}
+
+template <typename T, typename... Args>
+[[nodiscard]] inline TSimpleSharedPtr<T> MakeSimpleShared(Args&&... args) {
+ return MakeShared<T, TSimpleCounter>(std::forward<Args>(args)...);
+}
+
+class TCopyClone {
+public:
+ template <class T>
+ static inline T* Copy(T* t) {
+ if (t)
+ return t->Clone();
+ return nullptr;
+ }
+};
+
+class TCopyNew {
+public:
+ template <class T>
+ static inline T* Copy(T* t) {
+ if (t)
+ return new T(*t);
+ return nullptr;
+ }
+};
+
+template <class T, class C, class D>
+class TCopyPtr: public TPointerBase<TCopyPtr<T, C, D>, T> {
+public:
+ inline TCopyPtr(T* t = nullptr) noexcept
+ : T_(t)
+ {
+ }
+
+ inline TCopyPtr(const TCopyPtr& t)
+ : T_(C::Copy(t.Get()))
+ {
+ }
+
+ inline TCopyPtr(TCopyPtr&& t) noexcept
+ : T_(nullptr)
+ {
+ Swap(t);
+ }
+
+ inline ~TCopyPtr() {
+ DoDestroy();
+ }
+
+ inline TCopyPtr& operator=(TCopyPtr t) noexcept {
+ t.Swap(*this);
+
+ return *this;
+ }
+
+ inline T* Release() noexcept Y_WARN_UNUSED_RESULT {
+ return DoRelease(T_);
+ }
+
+ inline void Reset(T* t) noexcept {
+ if (T_ != t) {
+ DoDestroy();
+ T_ = t;
+ }
+ }
+
+ inline void Reset() noexcept {
+ Destroy();
+ }
+
+ inline void Destroy() noexcept {
+ Reset(nullptr);
+ }
+
+ inline void Swap(TCopyPtr& r) noexcept {
+ DoSwap(T_, r.T_);
+ }
+
+ inline T* Get() const noexcept {
+ return T_;
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ inline void DoDestroy() noexcept {
+ if (T_)
+ D::Destroy(T_);
+ }
+
+private:
+ T* T_;
+};
+
+// Copy-on-write pointer
+template <class TPtr, class TCopy>
+class TCowPtr: public TPointerBase<TCowPtr<TPtr, TCopy>, const typename TPtr::TValueType> {
+ using T = typename TPtr::TValueType;
+
+public:
+ inline TCowPtr() = default;
+
+ inline TCowPtr(const TPtr& p)
+ : T_(p)
+ {
+ }
+
+ inline TCowPtr(T* p)
+ : T_(p)
+ {
+ }
+
+ inline const T* Get() const noexcept {
+ return Const();
+ }
+
+ inline const T* Const() const noexcept {
+ return T_.Get();
+ }
+
+ inline T* Mutable() {
+ Unshare();
+
+ return T_.Get();
+ }
+
+ inline bool Shared() const noexcept {
+ return T_.RefCount() > 1;
+ }
+
+ inline void Swap(TCowPtr& r) noexcept {
+ T_.Swap(r.T_);
+ }
+
+ inline void Reset(TCowPtr p) {
+ p.Swap(*this);
+ }
+
+ inline void Reset() {
+ T_.Reset();
+ }
+
+#ifdef __cpp_impl_three_way_comparison
+ template <class Other>
+ inline bool operator==(const Other& p) const noexcept {
+ return (p == Get());
+ }
+#endif
+private:
+ inline void Unshare() {
+ if (Shared()) {
+ Reset(TCopy::Copy(T_.Get()));
+ }
+ }
+
+private:
+ TPtr T_;
+};
+
+// saves .Get() on argument passing. Intended usage: Func(TPtrArg<X> p); ... TIntrusivePtr<X> p2; Func(p2);
+template <class T>
+class TPtrArg {
+ T* Ptr;
+
+public:
+ TPtrArg(T* p)
+ : Ptr(p)
+ {
+ }
+ TPtrArg(const TIntrusivePtr<T>& p)
+ : Ptr(p.Get())
+ {
+ }
+ operator T*() const {
+ return Ptr;
+ }
+ T* operator->() const {
+ return Ptr;
+ }
+ T* Get() const {
+ return Ptr;
+ }
+};
diff --git a/util/generic/ptr.pxd b/util/generic/ptr.pxd
new file mode 100644
index 0000000000..16e8d19144
--- /dev/null
+++ b/util/generic/ptr.pxd
@@ -0,0 +1,42 @@
+cdef extern from "<util/generic/ptr.h>" nogil:
+ cdef cppclass THolder[T]:
+ THolder(...)
+ T* Get()
+ void Destroy()
+ T* Release()
+ void Reset()
+ void Reset(T*)
+ void Swap(THolder[T])
+
+
+ cdef THolder[T] MakeHolder[T](...)
+
+
+ cdef cppclass TIntrusivePtr[T]:
+ TIntrusivePtr()
+ TIntrusivePtr(T*)
+ TIntrusivePtr& operator=(...)
+ void Reset(T*)
+ T* Get()
+ T* Release()
+ void Drop()
+
+
+ cdef cppclass TIntrusiveConstPtr[T]:
+ TIntrusiveConstPtr()
+ TIntrusiveConstPtr(T*)
+ TIntrusiveConstPtr& operator=(...)
+ void Reset(T*)
+ const T* Get()
+ void Drop()
+
+
+ cdef cppclass TAtomicSharedPtr[T]:
+ TAtomicSharedPtr()
+ TAtomicSharedPtr(T*)
+ T& operator*()
+ T* Get()
+ void Reset(T*)
+
+
+ cdef TAtomicSharedPtr[T] MakeAtomicShared[T](...)
diff --git a/util/generic/ptr_ut.cpp b/util/generic/ptr_ut.cpp
new file mode 100644
index 0000000000..c2dcff23f6
--- /dev/null
+++ b/util/generic/ptr_ut.cpp
@@ -0,0 +1,835 @@
+#include "ptr.h"
+#include "vector.h"
+#include "noncopyable.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/hash_set.h>
+#include <util/generic/is_in.h>
+#include <util/stream/output.h>
+#include <util/system/thread.h>
+
+class TPointerTest: public TTestBase {
+ UNIT_TEST_SUITE(TPointerTest);
+ UNIT_TEST(TestTypedefs);
+ UNIT_TEST(TestSimpleIntrPtr);
+ UNIT_TEST(TestHolderPtr);
+ UNIT_TEST(TestHolderPtrMoveConstructor);
+ UNIT_TEST(TestHolderPtrMoveConstructorInheritance);
+ UNIT_TEST(TestHolderPtrMoveAssignment);
+ UNIT_TEST(TestHolderPtrMoveAssignmentInheritance);
+ UNIT_TEST(TestMakeHolder);
+ UNIT_TEST(TestTrulePtr);
+ UNIT_TEST(TestAutoToHolder);
+ UNIT_TEST(TestCopyPtr);
+ UNIT_TEST(TestIntrPtr);
+ UNIT_TEST(TestIntrusiveConvertion);
+ UNIT_TEST(TestIntrusiveConstConvertion);
+ UNIT_TEST(TestIntrusiveConstConstruction);
+ UNIT_TEST(TestMakeIntrusive);
+ UNIT_TEST(TestCopyOnWritePtr1);
+ UNIT_TEST(TestCopyOnWritePtr2);
+ UNIT_TEST(TestOperatorBool);
+ UNIT_TEST(TestMakeShared);
+ UNIT_TEST(TestComparison);
+ UNIT_TEST(TestSimpleIntrusivePtrCtorTsan);
+ UNIT_TEST(TestRefCountedPtrsInHashSet)
+ UNIT_TEST_SUITE_END();
+
+private:
+ void TestSimpleIntrusivePtrCtorTsan() {
+ struct S: public TAtomicRefCount<S> {
+ };
+
+ struct TLocalThread: public ISimpleThread {
+ void* ThreadProc() override {
+ TSimpleIntrusivePtr<S> ptr;
+ return nullptr;
+ }
+ };
+
+ // Create TSimpleIntrusivePtr in different threads
+ // Its constructor should be thread-safe
+
+ TLocalThread t1, t2;
+
+ t1.Start();
+ t2.Start();
+ t1.Join();
+ t2.Join();
+ }
+
+ inline void TestTypedefs() {
+ TAtomicSharedPtr<int>(new int(1));
+ TSimpleSharedPtr<int>(new int(1));
+ }
+
+ void TestSimpleIntrPtr();
+ void TestHolderPtr();
+ void TestHolderPtrMoveConstructor();
+ void TestHolderPtrMoveConstructorInheritance();
+ void TestHolderPtrMoveAssignment();
+ void TestHolderPtrMoveAssignmentInheritance();
+ void TestMakeHolder();
+ void TestTrulePtr();
+ void TestAutoToHolder();
+ void TestCopyPtr();
+ void TestIntrPtr();
+ void TestIntrusiveConvertion();
+ void TestIntrusiveConstConvertion();
+ void TestIntrusiveConstConstruction();
+ void TestMakeIntrusive();
+ void TestCopyOnWritePtr1();
+ void TestCopyOnWritePtr2();
+ void TestOperatorBool();
+ void TestMakeShared();
+ void TestComparison();
+ template <class T, class TRefCountedPtr>
+ void TestRefCountedPtrsInHashSetImpl();
+ void TestRefCountedPtrsInHashSet();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TPointerTest);
+
+static int cnt = 0;
+
+class A: public TAtomicRefCount<A> {
+public:
+ inline A() {
+ ++cnt;
+ }
+
+ inline A(const A&)
+ : TAtomicRefCount<A>(*this)
+ {
+ ++cnt;
+ }
+
+ inline ~A() {
+ --cnt;
+ }
+};
+
+static A* MakeA() {
+ return new A();
+}
+
+/*
+ * test compileability
+ */
+class B;
+static TSimpleIntrusivePtr<B> GetB() {
+ throw 1;
+}
+
+void Func() {
+ TSimpleIntrusivePtr<B> b = GetB();
+}
+
+void TPointerTest::TestSimpleIntrPtr() {
+ {
+ TSimpleIntrusivePtr<A> a1(MakeA());
+ TSimpleIntrusivePtr<A> a2(MakeA());
+ TSimpleIntrusivePtr<A> a3 = a2;
+
+ a1 = a2;
+ a2 = a3;
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+}
+
+void TPointerTest::TestHolderPtr() {
+ {
+ THolder<A> a1(MakeA());
+ THolder<A> a2(a1.Release());
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+}
+
+THolder<int> CreateInt(int value) {
+ THolder<int> res(new int);
+ *res = value;
+ return res;
+}
+
+void TPointerTest::TestHolderPtrMoveConstructor() {
+ THolder<int> h = CreateInt(42);
+ UNIT_ASSERT_VALUES_EQUAL(*h, 42);
+}
+
+void TPointerTest::TestHolderPtrMoveAssignment() {
+ THolder<int> h(new int);
+ h = CreateInt(42);
+ UNIT_ASSERT_VALUES_EQUAL(*h, 42);
+}
+
+struct TBase {
+ virtual ~TBase() = default;
+};
+
+struct TDerived: public TBase {
+};
+
+void TPointerTest::TestHolderPtrMoveConstructorInheritance() {
+ // compileability test
+ THolder<TBase> basePtr(THolder<TDerived>(new TDerived));
+}
+
+void TPointerTest::TestHolderPtrMoveAssignmentInheritance() {
+ // compileability test
+ THolder<TBase> basePtr;
+ basePtr = THolder<TDerived>(new TDerived);
+}
+
+void TPointerTest::TestMakeHolder() {
+ {
+ auto ptr = MakeHolder<int>(5);
+ UNIT_ASSERT_VALUES_EQUAL(*ptr, 5);
+ }
+ {
+ struct TRec {
+ int X, Y;
+ TRec()
+ : X(1)
+ , Y(2)
+ {
+ }
+ };
+ THolder<TRec> ptr = MakeHolder<TRec>();
+ UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
+ }
+ {
+ struct TRec {
+ int X, Y;
+ TRec(int x, int y)
+ : X(x)
+ , Y(y)
+ {
+ }
+ };
+ auto ptr = MakeHolder<TRec>(1, 2);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
+ }
+ {
+ class TRec {
+ private:
+ int X_, Y_;
+
+ public:
+ TRec(int x, int y)
+ : X_(x)
+ , Y_(y)
+ {
+ }
+
+ int GetX() const {
+ return X_;
+ }
+ int GetY() const {
+ return Y_;
+ }
+ };
+ auto ptr = MakeHolder<TRec>(1, 2);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->GetX(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->GetY(), 2);
+ }
+}
+
+void TPointerTest::TestTrulePtr() {
+ {
+ TAutoPtr<A> a1(MakeA());
+ TAutoPtr<A> a2(a1);
+ a1 = a2;
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+}
+
+void TPointerTest::TestAutoToHolder() {
+ {
+ TAutoPtr<A> a1(MakeA());
+ THolder<A> a2(a1);
+
+ UNIT_ASSERT_EQUAL(a1.Get(), nullptr);
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 1);
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+
+ {
+ TAutoPtr<A> x(new A());
+ THolder<const A> y = x;
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+
+ {
+ class B1: public A {
+ };
+
+ TAutoPtr<B1> x(new B1());
+ THolder<A> y = x;
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+}
+
+void TPointerTest::TestCopyPtr() {
+ TCopyPtr<A> a1(MakeA());
+ {
+ TCopyPtr<A> a2(MakeA());
+ TCopyPtr<A> a3 = a2;
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 3);
+
+ a1 = a2;
+ a2 = a3;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 1);
+ a1.Destroy();
+
+ UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
+}
+
+class TOp: public TSimpleRefCount<TOp>, public TNonCopyable {
+public:
+ static int Cnt;
+
+public:
+ TOp() {
+ ++Cnt;
+ }
+ virtual ~TOp() {
+ --Cnt;
+ }
+};
+
+int TOp::Cnt = 0;
+
+class TOp2: public TOp {
+public:
+ TIntrusivePtr<TOp> Op;
+
+public:
+ TOp2(const TIntrusivePtr<TOp>& op)
+ : Op(op)
+ {
+ ++Cnt;
+ }
+ ~TOp2() override {
+ --Cnt;
+ }
+};
+
+class TOp3 {
+public:
+ TIntrusivePtr<TOp2> Op2;
+};
+
+void Attach(TOp3* op3, TIntrusivePtr<TOp>* op) {
+ TIntrusivePtr<TOp2> op2 = new TOp2(*op);
+ op3->Op2 = op2.Get();
+ *op = op2.Get();
+}
+
+void TPointerTest::TestIntrPtr() {
+ {
+ TIntrusivePtr<TOp> p, p2;
+ TOp3 op3;
+ {
+ TVector<TIntrusivePtr<TOp>> f1;
+ {
+ TVector<TIntrusivePtr<TOp>> f2;
+ f2.push_back(new TOp);
+ p = new TOp;
+ f2.push_back(p);
+ Attach(&op3, &f2[1]);
+ f1 = f2;
+ UNIT_ASSERT_VALUES_EQUAL(f1[0]->RefCount(), 2);
+ UNIT_ASSERT_VALUES_EQUAL(f1[1]->RefCount(), 3);
+ UNIT_ASSERT_EQUAL(f1[1].Get(), op3.Op2.Get());
+ UNIT_ASSERT_VALUES_EQUAL(op3.Op2->RefCount(), 3);
+ UNIT_ASSERT_VALUES_EQUAL(op3.Op2->Op->RefCount(), 2);
+ UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 4);
+ }
+ p2 = p;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(op3.Op2->RefCount(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(op3.Op2->Op->RefCount(), 3);
+ UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 3);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 0);
+}
+
+namespace NTestIntrusiveConvertion {
+ struct TA: public TSimpleRefCount<TA> {
+ };
+ struct TAA: public TA {
+ };
+ struct TB: public TSimpleRefCount<TB> {
+ };
+
+ void Func(TIntrusivePtr<TA>) {
+ }
+
+ void Func(TIntrusivePtr<TB>) {
+ }
+
+ void Func(TIntrusiveConstPtr<TA>) {
+ }
+
+ void Func(TIntrusiveConstPtr<TB>) {
+ }
+}
+
+void TPointerTest::TestIntrusiveConvertion() {
+ using namespace NTestIntrusiveConvertion;
+
+ TIntrusivePtr<TAA> aa = new TAA;
+
+ UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 1);
+ TIntrusivePtr<TA> a = aa;
+ UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 2);
+ UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 2);
+ aa.Reset();
+ UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 1);
+
+ // test that Func(TIntrusivePtr<TB>) doesn't participate in overload resolution
+ Func(aa);
+}
+
+void TPointerTest::TestIntrusiveConstConvertion() {
+ using namespace NTestIntrusiveConvertion;
+
+ TIntrusiveConstPtr<TAA> aa = new TAA;
+
+ UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 1);
+ TIntrusiveConstPtr<TA> a = aa;
+ UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 2);
+ UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 2);
+ aa.Reset();
+ UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 1);
+
+ // test that Func(TIntrusiveConstPtr<TB>) doesn't participate in overload resolution
+ Func(aa);
+}
+
+void TPointerTest::TestMakeIntrusive() {
+ {
+ UNIT_ASSERT_VALUES_EQUAL(0, TOp::Cnt);
+ auto p = MakeIntrusive<TOp>();
+ UNIT_ASSERT_VALUES_EQUAL(1, p->RefCount());
+ UNIT_ASSERT_VALUES_EQUAL(1, TOp::Cnt);
+ }
+ UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 0);
+}
+
+void TPointerTest::TestCopyOnWritePtr1() {
+ using TPtr = TCowPtr<TSimpleSharedPtr<int>>;
+ TPtr p1;
+ UNIT_ASSERT(!p1.Shared());
+
+ p1.Reset(new int(123));
+ UNIT_ASSERT(!p1.Shared());
+
+ {
+ TPtr pTmp = p1;
+
+ UNIT_ASSERT(p1.Shared());
+ UNIT_ASSERT(pTmp.Shared());
+ UNIT_ASSERT_EQUAL(p1.Get(), pTmp.Get());
+ }
+
+ UNIT_ASSERT(!p1.Shared());
+
+ TPtr p2 = p1;
+ TPtr p3;
+ p3 = p2;
+
+ UNIT_ASSERT(p2.Shared());
+ UNIT_ASSERT(p3.Shared());
+ UNIT_ASSERT_EQUAL(p1.Get(), p2.Get());
+ UNIT_ASSERT_EQUAL(p1.Get(), p3.Get());
+
+ *(p1.Mutable()) = 456;
+
+ UNIT_ASSERT(!p1.Shared());
+ UNIT_ASSERT(p2.Shared());
+ UNIT_ASSERT(p3.Shared());
+ UNIT_ASSERT_EQUAL(*p1, 456);
+ UNIT_ASSERT_EQUAL(*p2, 123);
+ UNIT_ASSERT_EQUAL(*p3, 123);
+ UNIT_ASSERT_UNEQUAL(p1.Get(), p2.Get());
+ UNIT_ASSERT_EQUAL(p2.Get(), p3.Get());
+
+ p2.Mutable();
+
+ UNIT_ASSERT(!p2.Shared());
+ UNIT_ASSERT(!p3.Shared());
+ UNIT_ASSERT_EQUAL(*p2, 123);
+ UNIT_ASSERT_EQUAL(*p3, 123);
+ UNIT_ASSERT_UNEQUAL(p2.Get(), p3.Get());
+}
+
+struct X: public TSimpleRefCount<X> {
+ inline X(int v = 0)
+ : V(v)
+ {
+ }
+
+ int V;
+};
+
+void TPointerTest::TestCopyOnWritePtr2() {
+ using TPtr = TCowPtr<TIntrusivePtr<X>>;
+ TPtr p1;
+ UNIT_ASSERT(!p1.Shared());
+
+ p1.Reset(new X(123));
+ UNIT_ASSERT(!p1.Shared());
+
+ {
+ TPtr pTmp = p1;
+
+ UNIT_ASSERT(p1.Shared());
+ UNIT_ASSERT(pTmp.Shared());
+ UNIT_ASSERT_EQUAL(p1.Get(), pTmp.Get());
+ }
+
+ UNIT_ASSERT(!p1.Shared());
+
+ TPtr p2 = p1;
+ TPtr p3;
+ p3 = p2;
+
+ UNIT_ASSERT(p2.Shared());
+ UNIT_ASSERT(p3.Shared());
+ UNIT_ASSERT_EQUAL(p1.Get(), p2.Get());
+ UNIT_ASSERT_EQUAL(p1.Get(), p3.Get());
+
+ p1.Mutable()->V = 456;
+
+ UNIT_ASSERT(!p1.Shared());
+ UNIT_ASSERT(p2.Shared());
+ UNIT_ASSERT(p3.Shared());
+ UNIT_ASSERT_EQUAL(p1->V, 456);
+ UNIT_ASSERT_EQUAL(p2->V, 123);
+ UNIT_ASSERT_EQUAL(p3->V, 123);
+ UNIT_ASSERT_UNEQUAL(p1.Get(), p2.Get());
+ UNIT_ASSERT_EQUAL(p2.Get(), p3.Get());
+
+ p2.Mutable();
+
+ UNIT_ASSERT(!p2.Shared());
+ UNIT_ASSERT(!p3.Shared());
+ UNIT_ASSERT_EQUAL(p2->V, 123);
+ UNIT_ASSERT_EQUAL(p3->V, 123);
+ UNIT_ASSERT_UNEQUAL(p2.Get(), p3.Get());
+}
+
+namespace {
+ template <class TFrom, class TTo>
+ struct TImplicitlyCastable {
+ struct RTYes {
+ char t[2];
+ };
+
+ using RTNo = char;
+
+ static RTYes Func(TTo);
+ static RTNo Func(...);
+ static TFrom Get();
+
+ /*
+ * Result == (TFrom could be converted to TTo implicitly)
+ */
+ enum {
+ Result = (sizeof(Func(Get())) != sizeof(RTNo))
+ };
+ };
+
+ struct TImplicitlyCastableToBool {
+ inline operator bool() const {
+ return true;
+ }
+ };
+
+}
+
+void TPointerTest::TestOperatorBool() {
+ using TVec = TVector<ui32>;
+
+ // to be sure TImplicitlyCastable works as expected
+ UNIT_ASSERT((TImplicitlyCastable<int, bool>::Result));
+ UNIT_ASSERT((TImplicitlyCastable<double, int>::Result));
+ UNIT_ASSERT((TImplicitlyCastable<int*, void*>::Result));
+ UNIT_ASSERT(!(TImplicitlyCastable<void*, int*>::Result));
+ UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, bool>::Result));
+ UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, int>::Result));
+ UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, ui64>::Result));
+ UNIT_ASSERT(!(TImplicitlyCastable<TImplicitlyCastableToBool, void*>::Result));
+
+ // pointers
+ UNIT_ASSERT(!(TImplicitlyCastable<TSimpleSharedPtr<TVec>, int>::Result));
+ UNIT_ASSERT(!(TImplicitlyCastable<TAutoPtr<ui64>, ui64>::Result));
+ UNIT_ASSERT(!(TImplicitlyCastable<THolder<TVec>, bool>::Result)); // even this
+
+ {
+ // mostly a compilability test
+
+ THolder<TVec> a;
+ UNIT_ASSERT(!a);
+ UNIT_ASSERT(!bool(a));
+ if (a) {
+ UNIT_ASSERT(false);
+ }
+ if (!a) {
+ UNIT_ASSERT(true);
+ }
+
+ a.Reset(new TVec);
+ UNIT_ASSERT(a);
+ UNIT_ASSERT(bool(a));
+ if (a) {
+ UNIT_ASSERT(true);
+ }
+ if (!a) {
+ UNIT_ASSERT(false);
+ }
+
+ THolder<TVec> b(new TVec);
+ UNIT_ASSERT(a.Get() != b.Get());
+ UNIT_ASSERT(a != b);
+ if (a == b) {
+ UNIT_ASSERT(false);
+ }
+ if (a != b) {
+ UNIT_ASSERT(true);
+ }
+ if (!(a && b)) {
+ UNIT_ASSERT(false);
+ }
+ if (a && b) {
+ UNIT_ASSERT(true);
+ }
+
+ // int i = a; // does not compile
+ // bool c = (a < b); // does not compile
+ }
+}
+
+void TPointerTest::TestMakeShared() {
+ {
+ TSimpleSharedPtr<int> ptr = MakeSimpleShared<int>(5);
+ UNIT_ASSERT_VALUES_EQUAL(*ptr, 5);
+ }
+ {
+ struct TRec {
+ int X, Y;
+ TRec()
+ : X(1)
+ , Y(2)
+ {
+ }
+ };
+ auto ptr = MakeAtomicShared<TRec>();
+ UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
+ }
+ {
+ struct TRec {
+ int X, Y;
+ };
+ TAtomicSharedPtr<TRec> ptr = MakeAtomicShared<TRec>(1, 2);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
+ }
+ {
+ class TRec {
+ private:
+ int X_, Y_;
+
+ public:
+ TRec(int x, int y)
+ : X_(x)
+ , Y_(y)
+ {
+ }
+
+ int GetX() const {
+ return X_;
+ }
+ int GetY() const {
+ return Y_;
+ }
+ };
+ TSimpleSharedPtr<TRec> ptr = MakeSimpleShared<TRec>(1, 2);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->GetX(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(ptr->GetY(), 2);
+ }
+ {
+ enum EObjectState {
+ OS_NOT_CREATED,
+ OS_CREATED,
+ OS_DESTROYED,
+ };
+
+ struct TObject {
+ EObjectState& State;
+
+ TObject(EObjectState& state)
+ : State(state)
+ {
+ State = OS_CREATED;
+ }
+
+ ~TObject() {
+ State = OS_DESTROYED;
+ }
+ };
+
+ auto throwsException = []() {
+ throw yexception();
+ return 5;
+ };
+
+ auto testFunction = [](TSimpleSharedPtr<TObject>, int) {
+ };
+
+ EObjectState state = OS_NOT_CREATED;
+ try {
+ testFunction(MakeSimpleShared<TObject>(state), throwsException());
+ } catch (yexception&) {
+ }
+
+ UNIT_ASSERT(state == OS_NOT_CREATED || state == OS_DESTROYED);
+ }
+}
+
+template <class TPtr>
+void TestPtrComparison(const TPtr& ptr) {
+ UNIT_ASSERT(ptr == ptr);
+ UNIT_ASSERT(!(ptr != ptr));
+ UNIT_ASSERT(ptr == ptr.Get());
+ UNIT_ASSERT(!(ptr != ptr.Get()));
+}
+
+void TPointerTest::TestComparison() {
+ THolder<A> ptr1(new A);
+ TAutoPtr<A> ptr2;
+ TSimpleSharedPtr<int> ptr3(new int(6));
+ TIntrusivePtr<A> ptr4;
+ TIntrusiveConstPtr<A> ptr5 = ptr4;
+
+ UNIT_ASSERT(ptr1 != nullptr);
+ UNIT_ASSERT(ptr2 == nullptr);
+ UNIT_ASSERT(ptr3 != nullptr);
+ UNIT_ASSERT(ptr4 == nullptr);
+ UNIT_ASSERT(ptr5 == nullptr);
+
+ TestPtrComparison(ptr1);
+ TestPtrComparison(ptr2);
+ TestPtrComparison(ptr3);
+ TestPtrComparison(ptr4);
+ TestPtrComparison(ptr5);
+}
+
+template <class T, class TRefCountedPtr>
+void TPointerTest::TestRefCountedPtrsInHashSetImpl() {
+ THashSet<TRefCountedPtr> hashSet;
+ TRefCountedPtr p1(new T());
+ UNIT_ASSERT(!IsIn(hashSet, p1));
+ UNIT_ASSERT(hashSet.insert(p1).second);
+ UNIT_ASSERT(IsIn(hashSet, p1));
+ UNIT_ASSERT_VALUES_EQUAL(hashSet.size(), 1);
+ UNIT_ASSERT(!hashSet.insert(p1).second);
+
+ TRefCountedPtr p2(new T());
+ UNIT_ASSERT(!IsIn(hashSet, p2));
+ UNIT_ASSERT(hashSet.insert(p2).second);
+ UNIT_ASSERT(IsIn(hashSet, p2));
+ UNIT_ASSERT_VALUES_EQUAL(hashSet.size(), 2);
+}
+
+struct TCustomIntrusivePtrOps: TDefaultIntrusivePtrOps<A> {
+};
+
+struct TCustomDeleter: TDelete {
+};
+
+struct TCustomCounter: TSimpleCounter {
+ using TSimpleCounterTemplate::TSimpleCounterTemplate;
+};
+
+void TPointerTest::TestRefCountedPtrsInHashSet() {
+ // test common case
+ TestRefCountedPtrsInHashSetImpl<TString, TSimpleSharedPtr<TString>>();
+ TestRefCountedPtrsInHashSetImpl<TString, TAtomicSharedPtr<TString>>();
+ TestRefCountedPtrsInHashSetImpl<A, TIntrusivePtr<A>>();
+ TestRefCountedPtrsInHashSetImpl<A, TIntrusiveConstPtr<A>>();
+
+ // test with custom ops
+ TestRefCountedPtrsInHashSetImpl<TString, TSharedPtr<TString, TCustomCounter, TCustomDeleter>>();
+ TestRefCountedPtrsInHashSetImpl<A, TIntrusivePtr<A, TCustomIntrusivePtrOps>>();
+ TestRefCountedPtrsInHashSetImpl<A, TIntrusiveConstPtr<A, TCustomIntrusivePtrOps>>();
+}
+
+class TRefCountedWithStatistics: public TNonCopyable {
+public:
+ struct TExternalCounter {
+ TAtomic Counter{0};
+ TAtomic Increments{0};
+ };
+
+ TRefCountedWithStatistics(TExternalCounter& cnt)
+ : ExternalCounter_(cnt)
+ {
+ ExternalCounter_ = {}; // reset counters
+ }
+
+ void Ref() noexcept {
+ AtomicIncrement(ExternalCounter_.Counter);
+ AtomicIncrement(ExternalCounter_.Increments);
+ }
+
+ void UnRef() noexcept {
+ if (AtomicDecrement(ExternalCounter_.Counter) == 0) {
+ TDelete::Destroy(this);
+ }
+ }
+
+ void DecRef() noexcept {
+ Y_VERIFY(AtomicDecrement(ExternalCounter_.Counter) != 0);
+ }
+
+private:
+ TExternalCounter& ExternalCounter_;
+};
+
+void TPointerTest::TestIntrusiveConstConstruction() {
+ {
+ TRefCountedWithStatistics::TExternalCounter cnt;
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 0);
+ TIntrusivePtr<TRefCountedWithStatistics> i{MakeIntrusive<TRefCountedWithStatistics>(cnt)};
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 1);
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
+ i.Reset();
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
+ }
+ {
+ TRefCountedWithStatistics::TExternalCounter cnt;
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 0);
+ TIntrusiveConstPtr<TRefCountedWithStatistics> c{MakeIntrusive<TRefCountedWithStatistics>(cnt)};
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 1);
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
+ c.Reset();
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
+ UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
+ }
+}
diff --git a/util/generic/ptr_ut.pyx b/util/generic/ptr_ut.pyx
new file mode 100644
index 0000000000..759681a2cb
--- /dev/null
+++ b/util/generic/ptr_ut.pyx
@@ -0,0 +1,26 @@
+from libcpp.utility cimport pair
+from util.generic.ptr cimport MakeAtomicShared, TAtomicSharedPtr, THolder
+from util.generic.string cimport TString
+from util.system.types cimport ui64
+
+import pytest
+import unittest
+
+
+class TestHolder(unittest.TestCase):
+
+ def test_basic(self):
+ cdef THolder[TString] holder
+ holder.Reset(new TString("aaa"))
+ assert holder.Get()[0] == "aaa"
+ holder.Destroy()
+ assert holder.Get() == NULL
+ holder.Reset(new TString("bbb"))
+ assert holder.Get()[0] == "bbb"
+ holder.Reset(new TString("ccc"))
+ assert holder.Get()[0] == "ccc"
+
+ def test_make_atomic_shared(self):
+ cdef TAtomicSharedPtr[pair[ui64, TString]] atomic_shared_ptr = MakeAtomicShared[pair[ui64, TString]](15, "Some string")
+ assert atomic_shared_ptr.Get().first == 15
+ assert atomic_shared_ptr.Get().second == "Some string"
diff --git a/util/generic/queue.cpp b/util/generic/queue.cpp
new file mode 100644
index 0000000000..4ebd3f3205
--- /dev/null
+++ b/util/generic/queue.cpp
@@ -0,0 +1 @@
+#include "queue.h"
diff --git a/util/generic/queue.h b/util/generic/queue.h
new file mode 100644
index 0000000000..f5959f68f2
--- /dev/null
+++ b/util/generic/queue.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "fwd.h"
+#include "deque.h"
+#include "vector.h"
+#include "utility.h"
+
+#include <util/str_stl.h>
+
+#include <queue>
+
+template <class T, class S>
+class TQueue: public std::queue<T, S> {
+ using TBase = std::queue<T, S>;
+
+public:
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ inline void clear() {
+ this->c.clear();
+ }
+
+ inline S& Container() {
+ return this->c;
+ }
+
+ inline const S& Container() const {
+ return this->c;
+ }
+};
+
+template <class T, class S, class C>
+class TPriorityQueue: public std::priority_queue<T, S, C> {
+ using TBase = std::priority_queue<T, S, C>;
+
+public:
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ inline void clear() {
+ this->c.clear();
+ }
+
+ inline S& Container() {
+ return this->c;
+ }
+
+ inline const S& Container() const {
+ return this->c;
+ }
+};
diff --git a/util/generic/queue_ut.cpp b/util/generic/queue_ut.cpp
new file mode 100644
index 0000000000..a33399e104
--- /dev/null
+++ b/util/generic/queue_ut.cpp
@@ -0,0 +1,210 @@
+#include "queue.h"
+#include "list.h"
+#include "vector.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <utility>
+
+Y_UNIT_TEST_SUITE(TYQueueTest) {
+ Y_UNIT_TEST(ConstructorsAndAssignments) {
+ {
+ using container = TQueue<int>;
+
+ container c1;
+ UNIT_ASSERT(!c1);
+ c1.push(100);
+ c1.push(200);
+ UNIT_ASSERT(c1);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+
+ c2.push(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+
+ c2.push(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ }
+
+ {
+ using container = TPriorityQueue<int>;
+
+ container c1;
+ UNIT_ASSERT(!c1);
+ c1.push(100);
+ c1.push(200);
+ UNIT_ASSERT(c1);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+
+ c2.push(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+
+ c2.push(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ }
+ }
+
+ Y_UNIT_TEST(pqueue1) {
+ TPriorityQueue<int, TDeque<int>, TLess<int>> q;
+
+ q.push(42);
+ q.push(101);
+ q.push(69);
+ UNIT_ASSERT(q.top() == 101);
+
+ q.pop();
+ UNIT_ASSERT(q.top() == 69);
+
+ q.pop();
+ UNIT_ASSERT(q.top() == 42);
+
+ q.pop();
+ UNIT_ASSERT(q.empty());
+ }
+
+ Y_UNIT_TEST(pqueue2) {
+ using TPQueue = TPriorityQueue<int, TDeque<int>, TLess<int>>;
+ TPQueue q;
+
+ {
+ TPQueue qq;
+
+ qq.push(42);
+ qq.push(101);
+ qq.push(69);
+
+ qq.swap(q);
+ }
+
+ UNIT_ASSERT(q.top() == 101);
+
+ q.pop();
+ UNIT_ASSERT(q.top() == 69);
+
+ q.pop();
+ UNIT_ASSERT(q.top() == 42);
+
+ q.pop();
+ UNIT_ASSERT(q.empty());
+ }
+
+ Y_UNIT_TEST(pqueue3) {
+ TPriorityQueue<int, TDeque<int>, TLess<int>> q;
+
+ q.push(42);
+ q.push(101);
+ q.push(69);
+ q.clear();
+
+ UNIT_ASSERT(q.empty());
+ }
+
+ Y_UNIT_TEST(pqueue4) {
+ TDeque<int> c;
+ c.push_back(42);
+ c.push_back(101);
+ c.push_back(69);
+
+ TPriorityQueue<int, TDeque<int>, TLess<int>> q(TLess<int>(), std::move(c));
+
+ UNIT_ASSERT(c.empty());
+
+ UNIT_ASSERT_EQUAL(q.size(), 3);
+
+ UNIT_ASSERT(q.top() == 101);
+
+ q.pop();
+ UNIT_ASSERT(q.top() == 69);
+
+ q.pop();
+ UNIT_ASSERT(q.top() == 42);
+
+ q.pop();
+ UNIT_ASSERT(q.empty());
+ }
+
+ Y_UNIT_TEST(queue1) {
+ TQueue<int, TList<int>> q;
+
+ q.push(42);
+ q.push(101);
+ q.push(69);
+ UNIT_ASSERT(q.front() == 42);
+
+ q.pop();
+ UNIT_ASSERT(q.front() == 101);
+
+ q.pop();
+ UNIT_ASSERT(q.front() == 69);
+
+ q.pop();
+ UNIT_ASSERT(q.empty());
+ }
+
+ Y_UNIT_TEST(queue2) {
+ using TQueueType = TQueue<int>;
+ TQueueType q;
+
+ {
+ TQueueType qq;
+
+ qq.push(42);
+ qq.push(101);
+ qq.push(69);
+
+ qq.swap(q);
+ }
+
+ UNIT_ASSERT(q.front() == 42);
+
+ q.pop();
+ UNIT_ASSERT(q.front() == 101);
+
+ q.pop();
+ UNIT_ASSERT(q.front() == 69);
+
+ q.pop();
+ UNIT_ASSERT(q.empty());
+ }
+
+ Y_UNIT_TEST(queue3) {
+ using TQueueType = TQueue<int>;
+ TQueueType q;
+
+ q.push(42);
+ q.push(101);
+ q.push(69);
+ q.clear();
+
+ UNIT_ASSERT(q.empty());
+ }
+}
diff --git a/util/generic/refcount.cpp b/util/generic/refcount.cpp
new file mode 100644
index 0000000000..4174858ff9
--- /dev/null
+++ b/util/generic/refcount.cpp
@@ -0,0 +1 @@
+#include "refcount.h"
diff --git a/util/generic/refcount.h b/util/generic/refcount.h
new file mode 100644
index 0000000000..966e853b77
--- /dev/null
+++ b/util/generic/refcount.h
@@ -0,0 +1,162 @@
+#pragma once
+
+#include <util/system/guard.h>
+#include <util/system/atomic.h>
+#include <util/system/defaults.h>
+#include <util/system/yassert.h>
+
+template <class TCounterCheckPolicy>
+class TSimpleCounterTemplate: public TCounterCheckPolicy {
+ using TCounterCheckPolicy::Check;
+
+public:
+ inline TSimpleCounterTemplate(long initial = 0) noexcept
+ : Counter_(initial)
+ {
+ }
+
+ inline ~TSimpleCounterTemplate() {
+ Check();
+ }
+
+ inline TAtomicBase Add(TAtomicBase d) noexcept {
+ Check();
+ return Counter_ += d;
+ }
+
+ inline TAtomicBase Inc() noexcept {
+ return Add(1);
+ }
+
+ inline TAtomicBase Sub(TAtomicBase d) noexcept {
+ Check();
+ return Counter_ -= d;
+ }
+
+ inline TAtomicBase Dec() noexcept {
+ return Sub(1);
+ }
+
+ inline bool TryWeakInc() noexcept {
+ if (!Counter_) {
+ return false;
+ }
+
+ Inc();
+ Y_ASSERT(Counter_ != 0);
+
+ return true;
+ }
+
+ inline TAtomicBase Val() const noexcept {
+ return Counter_;
+ }
+
+private:
+ TAtomicBase Counter_;
+};
+
+class TNoCheckPolicy {
+protected:
+ inline void Check() const {
+ }
+};
+
+#if defined(SIMPLE_COUNTER_THREAD_CHECK)
+
+ #include <util/system/thread.i>
+
+class TCheckPolicy {
+public:
+ inline TCheckPolicy() {
+ ThreadId = SystemCurrentThreadId();
+ }
+
+protected:
+ inline void Check() const {
+ Y_VERIFY(ThreadId == SystemCurrentThreadId(), "incorrect usage of TSimpleCounter");
+ }
+
+private:
+ size_t ThreadId;
+};
+#else
+using TCheckPolicy = TNoCheckPolicy;
+#endif
+
+// Use this one if access from multiple threads to your pointer is an error and you want to enforce thread checks
+using TSimpleCounter = TSimpleCounterTemplate<TCheckPolicy>;
+// Use this one if you do want to share the pointer between threads, omit thread checks and do the synchronization yourself
+using TExplicitSimpleCounter = TSimpleCounterTemplate<TNoCheckPolicy>;
+
+template <class TCounterCheckPolicy>
+struct TCommonLockOps<TSimpleCounterTemplate<TCounterCheckPolicy>> {
+ static inline void Acquire(TSimpleCounterTemplate<TCounterCheckPolicy>* t) noexcept {
+ t->Inc();
+ }
+
+ static inline void Release(TSimpleCounterTemplate<TCounterCheckPolicy>* t) noexcept {
+ t->Dec();
+ }
+};
+
+class TAtomicCounter {
+public:
+ inline TAtomicCounter(long initial = 0) noexcept
+ : Counter_(initial)
+ {
+ }
+
+ inline ~TAtomicCounter() = default;
+
+ inline TAtomicBase Add(TAtomicBase d) noexcept {
+ return AtomicAdd(Counter_, d);
+ }
+
+ inline TAtomicBase Inc() noexcept {
+ return Add(1);
+ }
+
+ inline TAtomicBase Sub(TAtomicBase d) noexcept {
+ return AtomicSub(Counter_, d);
+ }
+
+ inline TAtomicBase Dec() noexcept {
+ return Sub(1);
+ }
+
+ inline TAtomicBase Val() const noexcept {
+ return AtomicGet(Counter_);
+ }
+
+ inline bool TryWeakInc() noexcept {
+ while (true) {
+ intptr_t curValue = Counter_;
+
+ if (!curValue) {
+ return false;
+ }
+
+ intptr_t newValue = curValue + 1;
+ Y_ASSERT(newValue != 0);
+
+ if (AtomicCas(&Counter_, newValue, curValue)) {
+ return true;
+ }
+ }
+ }
+
+private:
+ TAtomic Counter_;
+};
+
+template <>
+struct TCommonLockOps<TAtomicCounter> {
+ static inline void Acquire(TAtomicCounter* t) noexcept {
+ t->Inc();
+ }
+
+ static inline void Release(TAtomicCounter* t) noexcept {
+ t->Dec();
+ }
+};
diff --git a/util/generic/reserve.h b/util/generic/reserve.h
new file mode 100644
index 0000000000..81ceed19dc
--- /dev/null
+++ b/util/generic/reserve.h
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace NDetail {
+ struct TReserveTag {
+ size_t Capacity;
+ };
+}
+
+constexpr ::NDetail::TReserveTag Reserve(size_t capacity) {
+ return ::NDetail::TReserveTag{capacity};
+}
diff --git a/util/generic/scope.cpp b/util/generic/scope.cpp
new file mode 100644
index 0000000000..c70d695c1b
--- /dev/null
+++ b/util/generic/scope.cpp
@@ -0,0 +1 @@
+#include "scope.h"
diff --git a/util/generic/scope.h b/util/generic/scope.h
new file mode 100644
index 0000000000..b2c33af61e
--- /dev/null
+++ b/util/generic/scope.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <util/system/compiler.h>
+#include <util/system/defaults.h>
+
+#include <utility>
+
+namespace NPrivate {
+ template <typename F>
+ class TScopeGuard {
+ public:
+ TScopeGuard(const F& function)
+ : Function_{function}
+ {
+ }
+
+ TScopeGuard(F&& function)
+ : Function_{std::move(function)}
+ {
+ }
+
+ TScopeGuard(TScopeGuard&&) = default;
+ TScopeGuard(const TScopeGuard&) = default;
+
+ ~TScopeGuard() {
+ Function_();
+ }
+
+ private:
+ F Function_;
+ };
+
+ struct TMakeGuardHelper {
+ template <class F>
+ TScopeGuard<F> operator|(F&& function) const {
+ return std::forward<F>(function);
+ }
+ };
+}
+
+// \brief `Y_SCOPE_EXIT(captures) { body };`
+//
+// General implementaion of RAII idiom (resource acquisition is initialization). Executes
+// function upon return from the current scope.
+//
+// @note expects `body` to provide no-throw guarantee, otherwise whenever an exception
+// is thrown and leaves the outermost block of `body`, the function `std::terminate` is called.
+// @see http://drdobbs.com/184403758 for detailed motivation.
+#define Y_SCOPE_EXIT(...) const auto Y_GENERATE_UNIQUE_ID(scopeGuard) Y_DECLARE_UNUSED = ::NPrivate::TMakeGuardHelper{} | [__VA_ARGS__]() mutable -> void
+
+// \brief `Y_DEFER { body };`
+//
+// Same as `Y_SCOPE_EXIT` but doesn't require user to provide capture-list explicitly (it
+// implicitly uses `[&]` capture). Have same requirements for `body`.
+//
+// Inspired by `defer` statement in languages like Swift and Go.
+//
+// \code
+// auto item = s.pop();
+// bool ok = false;
+// Y_DEFER { if (!ok) { s.push(std::move(item)); } };
+// ... try handle `item` ...
+// ok = true;
+// \endcode
+#define Y_DEFER Y_SCOPE_EXIT(&)
diff --git a/util/generic/scope_ut.cpp b/util/generic/scope_ut.cpp
new file mode 100644
index 0000000000..bdb434d487
--- /dev/null
+++ b/util/generic/scope_ut.cpp
@@ -0,0 +1,47 @@
+#include "scope.h"
+
+#include <util/generic/ptr.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(ScopeToolsTest) {
+ Y_UNIT_TEST(OnScopeExitTest) {
+ int i = 0;
+
+ {
+ Y_SCOPE_EXIT(&i) {
+ i = i * 2;
+ };
+
+ Y_SCOPE_EXIT(&i) {
+ i = i + 1;
+ };
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(2, i);
+ }
+
+ Y_UNIT_TEST(OnScopeExitMoveTest) {
+ THolder<int> i{new int{10}};
+ int p = 0;
+
+ {
+ Y_SCOPE_EXIT(i = std::move(i), &p) {
+ p = *i * 2;
+ };
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(20, p);
+ }
+
+ Y_UNIT_TEST(TestDefer) {
+ int i = 0;
+
+ {
+ Y_DEFER {
+ i = 20;
+ };
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(i, 20);
+ }
+}
diff --git a/util/generic/serialized_enum.cpp b/util/generic/serialized_enum.cpp
new file mode 100644
index 0000000000..1478c50b22
--- /dev/null
+++ b/util/generic/serialized_enum.cpp
@@ -0,0 +1 @@
+#include "serialized_enum.h"
diff --git a/util/generic/serialized_enum.h b/util/generic/serialized_enum.h
new file mode 100644
index 0000000000..79df2bac22
--- /dev/null
+++ b/util/generic/serialized_enum.h
@@ -0,0 +1,399 @@
+#pragma once
+
+#include <util/generic/fwd.h>
+#include <util/generic/vector.h>
+#include <util/generic/map.h>
+
+#include <cstddef>
+#include <type_traits>
+
+/*
+
+A file with declarations of enumeration-related functions.
+It doesn't contains definitions. To generate them you have to add
+
+ GENERATE_ENUM_SERIALIZATION_WITH_HEADER(your_header_with_your_enum.h)
+or
+ GENERATE_ENUM_SERIALIZATION(your_header_with_your_enum.h)
+
+in your ya.make
+
+@see https://st.yandex-team.ru/IGNIETFERRO-333
+@see https://wiki.yandex-team.ru/PoiskovajaPlatforma/Build/WritingCmakefiles/#generate-enum-with-header
+
+*/
+
+/**
+ * Returns number of distinct items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+template <typename EnumT>
+Y_CONST_FUNCTION constexpr size_t GetEnumItemsCount();
+
+namespace NEnumSerializationRuntime {
+ namespace NDetail {
+ template <typename EEnum>
+ struct TSelectEnumRepresentationType;
+
+ template <typename TEnumType, typename TRepresentationType, class TStorage = TVector<TRepresentationType>>
+ class TMappedArrayView;
+
+ template <typename TEnumType, typename TRepresentationType, typename TValueType, class TStorage = TMap<TRepresentationType, TValueType>>
+ class TMappedDictView;
+ }
+
+ /// Class with behaviour similar to TMap<EnumT, TValueType>
+ template <typename EnumT, typename TValueType>
+ using TMappedDictView = NDetail::TMappedDictView<EnumT, typename NDetail::TSelectEnumRepresentationType<EnumT>::TType, TValueType>;
+
+ /// Class with behaviour similar to TVector<EnumT>
+ template <typename EnumT>
+ using TMappedArrayView = NDetail::TMappedArrayView<EnumT, typename NDetail::TSelectEnumRepresentationType<EnumT>::TType>;
+
+ /**
+ * Returns names for items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+ template <typename EnumT>
+ TMappedDictView<EnumT, TString> GetEnumNamesImpl();
+ /**
+ * Returns unique items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+ template <typename EnumT>
+ ::NEnumSerializationRuntime::TMappedArrayView<EnumT> GetEnumAllValuesImpl();
+
+ /**
+ * Returns human-readable comma-separated list of names in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+ template <typename EnumT>
+ const TString& GetEnumAllNamesImpl();
+
+ /**
+ * Returns C++ identifiers for items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+ template <typename EnumT>
+ const TVector<TString>& GetEnumAllCppNamesImpl();
+}
+
+/**
+ * Returns names for items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+template <typename EnumT>
+Y_CONST_FUNCTION ::NEnumSerializationRuntime::TMappedDictView<EnumT, TString> GetEnumNames() {
+ return ::NEnumSerializationRuntime::GetEnumNamesImpl<EnumT>();
+}
+
+/**
+ * Returns unique items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+template <typename EnumT>
+Y_CONST_FUNCTION ::NEnumSerializationRuntime::TMappedArrayView<EnumT> GetEnumAllValues() {
+ return ::NEnumSerializationRuntime::GetEnumAllValuesImpl<EnumT>();
+}
+
+/**
+ * Returns human-readable comma-separated list of names in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+template <typename EnumT>
+Y_CONST_FUNCTION const TString& GetEnumAllNames() {
+ return ::NEnumSerializationRuntime::GetEnumAllNamesImpl<EnumT>();
+}
+
+/**
+ * Returns C++ identifiers for items in enum or enum class
+ *
+ * @tparam EnumT enum type
+ */
+template <typename EnumT>
+Y_CONST_FUNCTION const TVector<TString>& GetEnumAllCppNames() {
+ return ::NEnumSerializationRuntime::GetEnumAllCppNamesImpl<EnumT>();
+}
+
+namespace NEnumSerializationRuntime {
+ namespace NDetail {
+ /// Checks that the `From` type can be promoted up to the `To` type without losses
+ template <typename From, typename To>
+ struct TIsPromotable: public std::is_same<std::common_type_t<From, To>, To> {
+ static_assert(std::is_integral<From>::value, "`From` type has to be an integer");
+ static_assert(std::is_integral<To>::value, "`To` type has to be an integer");
+ };
+
+ /// Selects enum representation type. Works like std::underlying_type_t<>, but promotes small types up to `int`
+ template <typename EEnum>
+ struct TSelectEnumRepresentationType {
+ using TUnderlyingType = std::underlying_type_t<EEnum>;
+ using TIsSigned = std::is_signed<TUnderlyingType>;
+ using TRepresentationType = std::conditional_t<
+ TIsSigned::value,
+ std::conditional_t<
+ TIsPromotable<TUnderlyingType, int>::value,
+ int,
+ long long>,
+ std::conditional_t<
+ TIsPromotable<TUnderlyingType, unsigned>::value,
+ unsigned,
+ unsigned long long>>;
+ using TType = TRepresentationType;
+ static_assert(sizeof(TUnderlyingType) <= sizeof(TType), "size of `TType` is not smaller than the size of `TUnderlyingType`");
+ };
+
+ template <typename TEnumType, typename TRepresentationType>
+ class TMappedViewBase {
+ static_assert(sizeof(std::underlying_type_t<TEnumType>) <= sizeof(TRepresentationType), "Internal type is probably too small to represent all possible values");
+
+ public:
+ static constexpr TEnumType CastFromRepresentationType(const TRepresentationType key) noexcept {
+ return static_cast<TEnumType>(key);
+ }
+
+ static constexpr TRepresentationType CastToRepresentationType(const TEnumType key) noexcept {
+ return static_cast<TRepresentationType>(key);
+ }
+ };
+
+ /// Wrapper class with behaviour similar to TVector<EnumT>
+ ///
+ /// @tparam TEnumType enum type at the external interface
+ /// @tparam TRepresentationType designated underlying type of enum
+ /// @tparam TStorage internal container type
+ template <typename TEnumType, typename TRepresentationType, class TStorage>
+ class TMappedArrayView: public TMappedViewBase<TEnumType, TRepresentationType> {
+ public:
+ using value_type = TEnumType;
+
+ public:
+ TMappedArrayView(const TStorage& a) noexcept
+ : Ref(a)
+ {
+ }
+
+ class TIterator {
+ public:
+ using TSlaveIteratorType = typename TStorage::const_iterator;
+
+ using difference_type = std::ptrdiff_t;
+ using value_type = TEnumType;
+ using pointer = const TEnumType*;
+ using reference = const TEnumType&;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ public:
+ TIterator(TSlaveIteratorType it)
+ : Slave(std::move(it))
+ {
+ }
+
+ bool operator==(const TIterator& it) const {
+ return Slave == it.Slave;
+ }
+
+ bool operator!=(const TIterator& it) const {
+ return !(*this == it);
+ }
+
+ TEnumType operator*() const {
+ return TMappedArrayView::CastFromRepresentationType(*Slave);
+ }
+
+ TIterator& operator++() {
+ ++Slave;
+ return *this;
+ }
+
+ TIterator& operator--() {
+ --Slave;
+ return *this;
+ }
+
+ TIterator operator++(int) {
+ auto temp = Slave;
+ ++Slave;
+ return temp;
+ }
+
+ TIterator operator--(int) {
+ auto temp = Slave;
+ --Slave;
+ return temp;
+ }
+
+ private:
+ TSlaveIteratorType Slave;
+ };
+
+ TIterator begin() const {
+ return Ref.begin();
+ }
+
+ TIterator end() const {
+ return Ref.end();
+ }
+
+ size_t size() const {
+ return Ref.size();
+ }
+
+ Y_PURE_FUNCTION bool empty() const {
+ return Ref.empty();
+ }
+
+ TEnumType at(size_t index) const {
+ return this->CastFromRepresentationType(Ref.at(index));
+ }
+
+ TEnumType operator[](size_t index) const {
+ return this->CastFromRepresentationType(Ref[index]);
+ }
+
+ // Allocate container and copy view's content into it
+ template <template <class...> class TContainer = TVector>
+ TContainer<TEnumType> Materialize() const {
+ return {begin(), end()};
+ }
+
+ private:
+ const TStorage& Ref;
+ };
+
+ /// Wrapper class with behaviour similar to TMap<EnumT, TValueType>
+ ///
+ /// @tparam TEnumType enum type at the external interface
+ /// @tparam TRepresentationType designated underlying type of enum
+ /// @tparam TValueType mapped value
+ /// @tparam TStorage internal container type
+ template <typename TEnumType, typename TRepresentationType, typename TValueType, class TStorage>
+ class TMappedDictView: public TMappedViewBase<TEnumType, TRepresentationType> {
+ public:
+ using TMappedItemType = std::pair<const TEnumType, const TValueType&>;
+
+ class TDereferenceResultHolder {
+ public:
+ TDereferenceResultHolder(const TRepresentationType enumValue, const TValueType& payload) noexcept
+ : Data(TMappedDictView::CastFromRepresentationType(enumValue), payload)
+ {
+ }
+
+ const TMappedItemType* operator->() const noexcept {
+ return &Data;
+ }
+
+ private:
+ TMappedItemType Data;
+ };
+
+ TMappedDictView(const TStorage& m) noexcept
+ : Ref(m)
+ {
+ }
+
+ class TIterator {
+ public:
+ using TSlaveIteratorType = typename TStorage::const_iterator;
+
+ using difference_type = std::ptrdiff_t;
+ using value_type = TMappedItemType;
+ using pointer = const TMappedItemType*;
+ using reference = const TMappedItemType&;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ public:
+ TIterator(TSlaveIteratorType it)
+ : Slave(std::move(it))
+ {
+ }
+
+ bool operator==(const TIterator& it) const {
+ return Slave == it.Slave;
+ }
+
+ bool operator!=(const TIterator& it) const {
+ return !(*this == it);
+ }
+
+ TDereferenceResultHolder operator->() const {
+ return {Slave->first, Slave->second};
+ }
+
+ TMappedItemType operator*() const {
+ return {TMappedDictView::CastFromRepresentationType(Slave->first), Slave->second};
+ }
+
+ TIterator& operator++() {
+ ++Slave;
+ return *this;
+ }
+
+ TIterator& operator--() {
+ --Slave;
+ return *this;
+ }
+
+ TIterator operator++(int) {
+ auto temp = Slave;
+ ++Slave;
+ return temp;
+ }
+
+ TIterator operator--(int) {
+ auto temp = Slave;
+ --Slave;
+ return temp;
+ }
+
+ private:
+ TSlaveIteratorType Slave;
+ };
+
+ TIterator begin() const {
+ return Ref.begin();
+ }
+
+ TIterator end() const {
+ return Ref.end();
+ }
+
+ size_t size() const {
+ return Ref.size();
+ }
+
+ Y_PURE_FUNCTION bool empty() const {
+ return Ref.empty();
+ }
+
+ bool contains(const TEnumType key) const {
+ return Ref.contains(this->CastToRepresentationType(key));
+ }
+
+ TIterator find(const TEnumType key) const {
+ return Ref.find(this->CastToRepresentationType(key));
+ }
+
+ const TValueType& at(const TEnumType key) const {
+ return Ref.at(this->CastToRepresentationType(key));
+ }
+
+ // Allocate container and copy view's content into it
+ template <template <class...> class TContainer = TMap>
+ TContainer<TEnumType, TValueType> Materialize() const {
+ return {begin(), end()};
+ }
+
+ private:
+ const TStorage& Ref;
+ };
+ }
+}
diff --git a/util/generic/serialized_enum_ut.cpp b/util/generic/serialized_enum_ut.cpp
new file mode 100644
index 0000000000..3a94e1d471
--- /dev/null
+++ b/util/generic/serialized_enum_ut.cpp
@@ -0,0 +1,120 @@
+#include "serialized_enum.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/deque.h>
+#include <util/generic/map.h>
+#include <util/generic/typelist.h>
+#include <util/generic/vector.h>
+
+Y_UNIT_TEST_SUITE(TestSerializedEnum) {
+ Y_UNIT_TEST(RepresentationTypes) {
+ using namespace NEnumSerializationRuntime::NDetail;
+
+ static_assert(TIsPromotable<int, int>::value, "int -> int");
+ static_assert(TIsPromotable<char, int>::value, "char -> int");
+ static_assert(TIsPromotable<unsigned short, unsigned long>::value, "unsigned short -> unsigned long");
+ static_assert(TIsPromotable<i64, long long>::value, "i64 -> long long");
+ static_assert(!TIsPromotable<ui64, ui8>::value, "ui64 -> ui8");
+ static_assert(!TIsPromotable<i64, short>::value, "i64 -> short");
+
+ enum EEmpty {
+ };
+ UNIT_ASSERT_C((TTypeList<int, unsigned>::THave<typename TSelectEnumRepresentationType<EEmpty>::TType>::value), "empty enum using signed or unsigned integer underlying type");
+
+ using TRepresentationTypeList = TTypeList<int, unsigned, long long, unsigned long long>;
+
+ enum class ERegular {
+ One = 1,
+ Two = 2,
+ Five = 5,
+ };
+ UNIT_ASSERT(TRepresentationTypeList::THave<typename TSelectEnumRepresentationType<ERegular>::TType>::value);
+
+ enum class ESmall: unsigned char {
+ Six = 6,
+ };
+ UNIT_ASSERT(TRepresentationTypeList::THave<typename TSelectEnumRepresentationType<ESmall>::TType>::value);
+
+ enum class EHugeUnsigned: ui64 {
+ Value = 0,
+ };
+ UNIT_ASSERT(TRepresentationTypeList::THave<typename TSelectEnumRepresentationType<EHugeUnsigned>::TType>::value);
+
+ enum class EHugeSigned: i64 {
+ Value = -2,
+ };
+ UNIT_ASSERT(TRepresentationTypeList::THave<typename TSelectEnumRepresentationType<EHugeSigned>::TType>::value);
+ }
+
+ Y_UNIT_TEST(MappedArrayView) {
+ enum class ETestEnum: signed char {
+ Zero = 0,
+ One = 1,
+ Two = 2,
+ Three = 3,
+ Four = 4,
+ Eleven = 11,
+ };
+ const TVector<int> values = {1, 2, 3, 0, 0, 0, 11, 0, 0, 0, 0, 0, 2};
+ const auto view = ::NEnumSerializationRuntime::TMappedArrayView<ETestEnum>{values};
+
+ UNIT_ASSERT_VALUES_EQUAL(view.size(), values.size());
+ UNIT_ASSERT_VALUES_EQUAL(view.empty(), false);
+ UNIT_ASSERT_EQUAL(*view.begin(), ETestEnum::One);
+ UNIT_ASSERT_EQUAL(view[6], ETestEnum::Eleven);
+ UNIT_ASSERT_EXCEPTION(view.at(-1), std::out_of_range);
+ UNIT_ASSERT_VALUES_EQUAL(sizeof(view[4]), sizeof(signed char));
+ UNIT_ASSERT_VALUES_EQUAL(sizeof(values[4]), sizeof(int));
+ for (const ETestEnum e : view) {
+ UNIT_ASSERT_UNEQUAL(e, ETestEnum::Four);
+ }
+
+ const TVector<ETestEnum> typedValues = {ETestEnum::One, ETestEnum::Two, ETestEnum::Three, ETestEnum::Zero, ETestEnum::Zero, ETestEnum::Zero, ETestEnum::Eleven, ETestEnum::Zero, ETestEnum::Zero, ETestEnum::Zero, ETestEnum::Zero, ETestEnum::Zero, ETestEnum::Two};
+ UNIT_ASSERT_EQUAL(typedValues, view.Materialize());
+
+ const TDeque<ETestEnum> typedValuesDeque{typedValues.begin(), typedValues.end()};
+ UNIT_ASSERT_EQUAL(typedValuesDeque, view.Materialize<TDeque>());
+ }
+
+ Y_UNIT_TEST(MappedDictView) {
+ enum class ETestEnum: unsigned short {
+ Zero = 0,
+ One = 1,
+ Two = 2,
+ Three = 3,
+ Four = 4,
+ Eleven = 11,
+ Fake = (unsigned short)(-1),
+ };
+ const TMap<unsigned, unsigned> map = {{0, 1}, {1, 2}, {2, 4}, {3, 8}, {4, 16}, {11, 2048}};
+ const auto view = ::NEnumSerializationRuntime::NDetail::TMappedDictView<ETestEnum, unsigned, unsigned, decltype(map)>{map};
+
+ UNIT_ASSERT_VALUES_EQUAL(view.size(), map.size());
+ UNIT_ASSERT_VALUES_EQUAL(map.empty(), false);
+
+ UNIT_ASSERT_EQUAL(view.begin()->first, ETestEnum::Zero);
+ UNIT_ASSERT_VALUES_EQUAL(view.begin()->second, 1u);
+
+ UNIT_ASSERT_VALUES_EQUAL(view.contains(ETestEnum::Fake), false);
+ UNIT_ASSERT_VALUES_EQUAL(view.contains(ETestEnum::Four), true);
+
+ UNIT_ASSERT_EXCEPTION(view.at(ETestEnum::Fake), std::out_of_range);
+ UNIT_ASSERT_NO_EXCEPTION(view.at(ETestEnum::Eleven));
+
+ UNIT_ASSERT_VALUES_EQUAL(view.at(ETestEnum::Three), 8u);
+
+ unsigned mask = 0;
+ unsigned sum = 0;
+ for (const auto e : view) {
+ mask |= e.second;
+ sum += e.second;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(mask, 2079);
+ UNIT_ASSERT_VALUES_EQUAL(sum, 2079);
+
+ const TMap<ETestEnum, unsigned> materialized = view.Materialize<TMap>();
+ UNIT_ASSERT_VALUES_EQUAL(materialized.size(), map.size());
+ UNIT_ASSERT_VALUES_EQUAL(materialized.at(ETestEnum::Four), 16);
+ }
+}
diff --git a/util/generic/set.cpp b/util/generic/set.cpp
new file mode 100644
index 0000000000..aa2f9c58e1
--- /dev/null
+++ b/util/generic/set.cpp
@@ -0,0 +1 @@
+#include "set.h"
diff --git a/util/generic/set.h b/util/generic/set.h
new file mode 100644
index 0000000000..4c437ca26f
--- /dev/null
+++ b/util/generic/set.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "fwd.h"
+
+#include <util/str_stl.h>
+#include <util/memory/alloc.h>
+
+#include <initializer_list>
+#include <memory>
+#include <set>
+
+template <class K, class L, class A>
+class TSet: public std::set<K, L, TReboundAllocator<A, K>> {
+public:
+ using TBase = std::set<K, L, TReboundAllocator<A, K>>;
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ template <class TheKey>
+ inline bool contains(const TheKey& key) const {
+ return this->find(key) != this->end();
+ }
+};
+
+template <class K, class L, class A>
+class TMultiSet: public std::multiset<K, L, TReboundAllocator<A, K>> {
+public:
+ using TBase = std::multiset<K, L, TReboundAllocator<A, K>>;
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ template <class TheKey>
+ inline bool contains(const TheKey& key) const {
+ return this->find(key) != this->end();
+ }
+};
diff --git a/util/generic/set_ut.cpp b/util/generic/set_ut.cpp
new file mode 100644
index 0000000000..d2769d327f
--- /dev/null
+++ b/util/generic/set_ut.cpp
@@ -0,0 +1,408 @@
+#include "set.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <utility>
+
+#include <algorithm>
+
+Y_UNIT_TEST_SUITE(YSetTest) {
+ Y_UNIT_TEST(TestSet1) {
+ TSet<int, TLess<int>> s;
+ UNIT_ASSERT(!s);
+ UNIT_ASSERT(s.count(42) == 0);
+ s.insert(42);
+ UNIT_ASSERT(s);
+ UNIT_ASSERT(s.count(42) == 1);
+ s.insert(42);
+ UNIT_ASSERT(s.count(42) == 1);
+ size_t count = s.erase(42);
+ UNIT_ASSERT(count == 1);
+ }
+
+ Y_UNIT_TEST(TestSet2) {
+ using int_set = TSet<int, TLess<int>>;
+ int_set s;
+ std::pair<int_set::iterator, bool> p = s.insert(42);
+ UNIT_ASSERT(p.second == true);
+ p = s.insert(42);
+ UNIT_ASSERT(p.second == false);
+
+ int array1[] = {1, 3, 6, 7};
+ s.insert(array1, array1 + 4);
+ UNIT_ASSERT(distance(s.begin(), s.end()) == 5);
+
+ int_set s2;
+ s2.swap(s);
+ UNIT_ASSERT(distance(s2.begin(), s2.end()) == 5);
+ UNIT_ASSERT(distance(s.begin(), s.end()) == 0);
+
+ int_set s3;
+ s3.swap(s);
+ s3.swap(s2);
+ UNIT_ASSERT(distance(s.begin(), s.end()) == 0);
+ UNIT_ASSERT(distance(s2.begin(), s2.end()) == 0);
+ UNIT_ASSERT(distance(s3.begin(), s3.end()) == 5);
+ }
+
+ Y_UNIT_TEST(TestErase) {
+ TSet<int, TLess<int>> s;
+ s.insert(1);
+ s.erase(s.begin());
+ UNIT_ASSERT(s.empty());
+
+ size_t nb = s.erase(1);
+ UNIT_ASSERT(nb == 0);
+ }
+
+ Y_UNIT_TEST(TestInsert) {
+ TSet<int> s;
+ TSet<int>::iterator i = s.insert(s.end(), 0);
+ UNIT_ASSERT(*i == 0);
+ }
+
+ Y_UNIT_TEST(TestFind) {
+ TSet<int> s;
+
+ UNIT_ASSERT(s.find(0) == s.end());
+
+ TSet<int> const& crs = s;
+
+ UNIT_ASSERT(crs.find(0) == crs.end());
+ }
+
+ Y_UNIT_TEST(TestHas) {
+ TSet<int> s;
+ UNIT_ASSERT(!s.contains(0));
+
+ TSet<int> const& crs = s;
+ UNIT_ASSERT(!crs.contains(0));
+
+ s.insert(1);
+ s.insert(42);
+ s.insert(100);
+ s.insert(2);
+
+ UNIT_ASSERT(s.contains(1));
+ UNIT_ASSERT(s.contains(2));
+ UNIT_ASSERT(s.contains(42));
+ UNIT_ASSERT(s.contains(100));
+ }
+
+ Y_UNIT_TEST(TestBounds) {
+ int array1[] = {1, 3, 6, 7};
+ TSet<int> s(array1, array1 + sizeof(array1) / sizeof(array1[0]));
+ TSet<int> const& crs = s;
+
+ TSet<int>::iterator sit;
+ TSet<int>::const_iterator scit;
+ std::pair<TSet<int>::iterator, TSet<int>::iterator> pit;
+ std::pair<TSet<int>::const_iterator, TSet<int>::const_iterator> pcit;
+
+ //Check iterator on mutable set
+ sit = s.lower_bound(2);
+ UNIT_ASSERT(sit != s.end());
+ UNIT_ASSERT(*sit == 3);
+
+ sit = s.upper_bound(5);
+ UNIT_ASSERT(sit != s.end());
+ UNIT_ASSERT(*sit == 6);
+
+ pit = s.equal_range(6);
+ UNIT_ASSERT(pit.first != pit.second);
+ UNIT_ASSERT(pit.first != s.end());
+ UNIT_ASSERT(*pit.first == 6);
+ UNIT_ASSERT(pit.second != s.end());
+ UNIT_ASSERT(*pit.second == 7);
+
+ pit = s.equal_range(4);
+ UNIT_ASSERT(pit.first == pit.second);
+ UNIT_ASSERT(pit.first != s.end());
+ UNIT_ASSERT(*pit.first == 6);
+ UNIT_ASSERT(pit.second != s.end());
+ UNIT_ASSERT(*pit.second == 6);
+
+ //Check const_iterator on mutable set
+ scit = s.lower_bound(2);
+ UNIT_ASSERT(scit != s.end());
+ UNIT_ASSERT(*scit == 3);
+
+ scit = s.upper_bound(5);
+ UNIT_ASSERT(scit != s.end());
+ UNIT_ASSERT(*scit == 6);
+
+ pcit = s.equal_range(6);
+ UNIT_ASSERT(pcit.first != pcit.second);
+ UNIT_ASSERT(pcit.first != s.end());
+ UNIT_ASSERT(*pcit.first == 6);
+ UNIT_ASSERT(pcit.second != s.end());
+ UNIT_ASSERT(*pcit.second == 7);
+
+ //Check const_iterator on const set
+ scit = crs.lower_bound(2);
+ UNIT_ASSERT(scit != crs.end());
+ UNIT_ASSERT(*scit == 3);
+
+ scit = crs.upper_bound(5);
+ UNIT_ASSERT(scit != crs.end());
+ UNIT_ASSERT(*scit == 6);
+
+ pcit = crs.equal_range(6);
+ UNIT_ASSERT(pcit.first != pcit.second);
+ UNIT_ASSERT(pcit.first != crs.end());
+ UNIT_ASSERT(*pcit.first == 6);
+ UNIT_ASSERT(pcit.second != crs.end());
+ UNIT_ASSERT(*pcit.second == 7);
+ }
+
+ Y_UNIT_TEST(TestImplementationCheck) {
+ TSet<int> tree;
+ tree.insert(1);
+ TSet<int>::iterator it = tree.begin();
+ int const& int_ref = *it++;
+ UNIT_ASSERT(int_ref == 1);
+
+ UNIT_ASSERT(it == tree.end());
+ UNIT_ASSERT(it != tree.begin());
+
+ TSet<int>::const_iterator cit = tree.begin();
+ int const& int_cref = *cit++;
+ UNIT_ASSERT(int_cref == 1);
+ }
+
+ Y_UNIT_TEST(TestReverseIteratorTest) {
+ TSet<int> tree;
+ tree.insert(1);
+ tree.insert(2);
+
+ {
+ TSet<int>::reverse_iterator rit(tree.rbegin());
+ UNIT_ASSERT(*(rit++) == 2);
+ UNIT_ASSERT(*(rit++) == 1);
+ UNIT_ASSERT(rit == tree.rend());
+ }
+
+ {
+ TSet<int> const& ctree = tree;
+ TSet<int>::const_reverse_iterator rit(ctree.rbegin());
+ UNIT_ASSERT(*(rit++) == 2);
+ UNIT_ASSERT(*(rit++) == 1);
+ UNIT_ASSERT(rit == ctree.rend());
+ }
+ }
+
+ Y_UNIT_TEST(TestConstructorsAndAssignments) {
+ {
+ using container = TSet<int>;
+
+ container c1;
+ c1.insert(100);
+ c1.insert(200);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT(c1.contains(100));
+ UNIT_ASSERT(c2.contains(200));
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT(c3.contains(100));
+
+ c2.insert(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT(c3.contains(300));
+
+ c2.insert(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT(c3.contains(400));
+ }
+
+ {
+ using container = TMultiSet<int>;
+
+ container c1;
+ c1.insert(100);
+ c1.insert(200);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT(c1.find(100) != c1.end());
+ UNIT_ASSERT(c2.find(200) != c2.end());
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT(c3.find(100) != c3.end());
+
+ c2.insert(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT(c3.find(300) != c3.end());
+
+ c2.insert(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT(c3.find(400) != c3.end());
+ }
+ }
+
+ struct TKey {
+ TKey()
+ : m_data(0)
+ {
+ }
+
+ explicit TKey(int data)
+ : m_data(data)
+ {
+ }
+
+ int m_data;
+ };
+
+ struct TKeyCmp {
+ bool operator()(TKey lhs, TKey rhs) const {
+ return lhs.m_data < rhs.m_data;
+ }
+
+ bool operator()(TKey lhs, int rhs) const {
+ return lhs.m_data < rhs;
+ }
+
+ bool operator()(int lhs, TKey rhs) const {
+ return lhs < rhs.m_data;
+ }
+
+ using is_transparent = void;
+ };
+
+ struct TKeyCmpPtr {
+ bool operator()(TKey const volatile* lhs, TKey const volatile* rhs) const {
+ return (*lhs).m_data < (*rhs).m_data;
+ }
+
+ bool operator()(TKey const volatile* lhs, int rhs) const {
+ return (*lhs).m_data < rhs;
+ }
+
+ bool operator()(int lhs, TKey const volatile* rhs) const {
+ return lhs < (*rhs).m_data;
+ }
+
+ using is_transparent = void;
+ };
+
+ Y_UNIT_TEST(TestTemplateMethods) {
+ {
+ using KeySet = TSet<TKey, TKeyCmp>;
+ KeySet keySet;
+ keySet.insert(TKey(1));
+ keySet.insert(TKey(2));
+ keySet.insert(TKey(3));
+ keySet.insert(TKey(4));
+
+ UNIT_ASSERT(keySet.count(TKey(1)) == 1);
+ UNIT_ASSERT(keySet.count(1) == 1);
+ UNIT_ASSERT(keySet.count(5) == 0);
+
+ UNIT_ASSERT(keySet.find(2) != keySet.end());
+ UNIT_ASSERT(keySet.lower_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.upper_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.equal_range(2) != std::make_pair(keySet.begin(), keySet.end()));
+
+ KeySet const& ckeySet = keySet;
+ UNIT_ASSERT(ckeySet.find(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.lower_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.upper_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.equal_range(2) != std::make_pair(ckeySet.begin(), ckeySet.end()));
+ }
+
+ {
+ using KeySet = TSet<TKey*, TKeyCmpPtr>;
+ KeySet keySet;
+ TKey key1(1), key2(2), key3(3), key4(4);
+ keySet.insert(&key1);
+ keySet.insert(&key2);
+ keySet.insert(&key3);
+ keySet.insert(&key4);
+
+ UNIT_ASSERT(keySet.count(1) == 1);
+ UNIT_ASSERT(keySet.count(5) == 0);
+
+ UNIT_ASSERT(keySet.find(2) != keySet.end());
+ UNIT_ASSERT(keySet.lower_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.upper_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.equal_range(2) != std::make_pair(keySet.begin(), keySet.end()));
+
+ KeySet const& ckeySet = keySet;
+ UNIT_ASSERT(ckeySet.find(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.lower_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.upper_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.equal_range(2) != std::make_pair(ckeySet.begin(), ckeySet.end()));
+ }
+ {
+ using KeySet = TMultiSet<TKey, TKeyCmp>;
+ KeySet keySet;
+ keySet.insert(TKey(1));
+ keySet.insert(TKey(2));
+ keySet.insert(TKey(3));
+ keySet.insert(TKey(4));
+
+ UNIT_ASSERT(keySet.count(TKey(1)) == 1);
+ UNIT_ASSERT(keySet.count(1) == 1);
+ UNIT_ASSERT(keySet.count(5) == 0);
+
+ UNIT_ASSERT(keySet.find(2) != keySet.end());
+ UNIT_ASSERT(keySet.lower_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.upper_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.equal_range(2) != std::make_pair(keySet.begin(), keySet.end()));
+
+ KeySet const& ckeySet = keySet;
+ UNIT_ASSERT(ckeySet.find(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.lower_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.upper_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.equal_range(2) != std::make_pair(ckeySet.begin(), ckeySet.end()));
+ }
+
+ {
+ using KeySet = TMultiSet<TKey const volatile*, TKeyCmpPtr>;
+ KeySet keySet;
+ TKey key1(1), key2(2), key3(3), key4(4);
+ keySet.insert(&key1);
+ keySet.insert(&key2);
+ keySet.insert(&key3);
+ keySet.insert(&key4);
+
+ UNIT_ASSERT(keySet.count(1) == 1);
+ UNIT_ASSERT(keySet.count(5) == 0);
+
+ UNIT_ASSERT(keySet.find(2) != keySet.end());
+ UNIT_ASSERT(keySet.lower_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.upper_bound(2) != keySet.end());
+ UNIT_ASSERT(keySet.equal_range(2) != std::make_pair(keySet.begin(), keySet.end()));
+
+ KeySet const& ckeySet = keySet;
+ UNIT_ASSERT(ckeySet.find(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.lower_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.upper_bound(2) != ckeySet.end());
+ UNIT_ASSERT(ckeySet.equal_range(2) != std::make_pair(ckeySet.begin(), ckeySet.end()));
+ }
+ }
+}
diff --git a/util/generic/singleton.cpp b/util/generic/singleton.cpp
new file mode 100644
index 0000000000..eb5a0662f8
--- /dev/null
+++ b/util/generic/singleton.cpp
@@ -0,0 +1,61 @@
+#include "singleton.h"
+
+#include <util/system/spinlock.h>
+#include <util/system/thread.h>
+#include <util/system/sanitizers.h>
+
+#include <cstring>
+
+namespace {
+ static inline bool MyAtomicTryLock(TAtomic& a, TAtomicBase v) noexcept {
+ return AtomicCas(&a, v, 0);
+ }
+
+ static inline bool MyAtomicTryAndTryLock(TAtomic& a, TAtomicBase v) noexcept {
+ return (AtomicGet(a) == 0) && MyAtomicTryLock(a, v);
+ }
+
+ static inline TAtomicBase MyThreadId() noexcept {
+ const TAtomicBase ret = TThread::CurrentThreadId();
+
+ if (ret) {
+ return ret;
+ }
+
+ //clash almost impossible, ONLY if we have threads with ids 0 and 1!
+ return 1;
+ }
+}
+
+void NPrivate::FillWithTrash(void* ptr, size_t len) {
+#if defined(NDEBUG)
+ Y_UNUSED(ptr);
+ Y_UNUSED(len);
+#else
+ if constexpr (NSan::TSanIsOn()) {
+ Y_UNUSED(ptr);
+ Y_UNUSED(len);
+ } else {
+ memset(ptr, 0xBA, len);
+ }
+#endif
+}
+
+void NPrivate::LockRecursive(TAtomic& lock) noexcept {
+ const TAtomicBase id = MyThreadId();
+
+ Y_VERIFY(AtomicGet(lock) != id, "recursive singleton initialization");
+
+ if (!MyAtomicTryLock(lock, id)) {
+ TSpinWait sw;
+
+ do {
+ sw.Sleep();
+ } while (!MyAtomicTryAndTryLock(lock, id));
+ }
+}
+
+void NPrivate::UnlockRecursive(TAtomic& lock) noexcept {
+ Y_VERIFY(AtomicGet(lock) == MyThreadId(), "unlock from another thread?!?!");
+ AtomicUnlock(&lock);
+}
diff --git a/util/generic/singleton.h b/util/generic/singleton.h
new file mode 100644
index 0000000000..f5fa047f5c
--- /dev/null
+++ b/util/generic/singleton.h
@@ -0,0 +1,136 @@
+#pragma once
+
+#include <util/system/atexit.h>
+#include <util/system/atomic.h>
+
+#include <new>
+#include <utility>
+
+template <class T>
+struct TSingletonTraits {
+ static constexpr size_t Priority = 65536;
+};
+
+namespace NPrivate {
+ void FillWithTrash(void* ptr, size_t len);
+
+ void LockRecursive(TAtomic& lock) noexcept;
+ void UnlockRecursive(TAtomic& lock) noexcept;
+
+ template <class T>
+ void Destroyer(void* ptr) {
+ ((T*)ptr)->~T();
+ FillWithTrash(ptr, sizeof(T));
+ }
+
+ template <class T, size_t P, class... TArgs>
+ Y_NO_INLINE T* SingletonBase(T*& ptr, TArgs&&... args) {
+ alignas(T) static char buf[sizeof(T)];
+ static TAtomic lock;
+
+ LockRecursive(lock);
+
+ auto ret = AtomicGet(ptr);
+
+ try {
+ if (!ret) {
+ ret = ::new (buf) T(std::forward<TArgs>(args)...);
+
+ try {
+ AtExit(Destroyer<T>, ret, P);
+ } catch (...) {
+ Destroyer<T>(ret);
+
+ throw;
+ }
+
+ AtomicSet(ptr, ret);
+ }
+ } catch (...) {
+ UnlockRecursive(lock);
+
+ throw;
+ }
+
+ UnlockRecursive(lock);
+
+ return ret;
+ }
+
+ template <class T, size_t P, class... TArgs>
+ T* SingletonInt(TArgs&&... args) {
+ static_assert(sizeof(T) < 32000, "use HugeSingleton instead");
+
+ static T* ptr;
+ auto ret = AtomicGet(ptr);
+
+ if (Y_UNLIKELY(!ret)) {
+ ret = SingletonBase<T, P>(ptr, std::forward<TArgs>(args)...);
+ }
+
+ return ret;
+ }
+
+ template <class T>
+ class TDefault {
+ public:
+ template <class... TArgs>
+ inline TDefault(TArgs&&... args)
+ : T_(std::forward<TArgs>(args)...)
+ {
+ }
+
+ inline const T* Get() const noexcept {
+ return &T_;
+ }
+
+ private:
+ T T_;
+ };
+
+ template <class T>
+ struct THeapStore {
+ template <class... TArgs>
+ inline THeapStore(TArgs&&... args)
+ : D(new T(std::forward<TArgs>(args)...))
+ {
+ }
+
+ inline ~THeapStore() {
+ delete D;
+ }
+
+ T* D;
+ };
+}
+
+#define Y_DECLARE_SINGLETON_FRIEND() \
+ template <class T, size_t P, class... TArgs> \
+ friend T* ::NPrivate::SingletonInt(TArgs&&...); \
+ template <class T, size_t P, class... TArgs> \
+ friend T* ::NPrivate::SingletonBase(T*&, TArgs&&...);
+
+template <class T, class... TArgs>
+T* Singleton(TArgs&&... args) {
+ return ::NPrivate::SingletonInt<T, TSingletonTraits<T>::Priority>(std::forward<TArgs>(args)...);
+}
+
+template <class T, class... TArgs>
+T* HugeSingleton(TArgs&&... args) {
+ return Singleton<::NPrivate::THeapStore<T>>(std::forward<TArgs>(args)...)->D;
+}
+
+template <class T, size_t P, class... TArgs>
+T* SingletonWithPriority(TArgs&&... args) {
+ return ::NPrivate::SingletonInt<T, P>(std::forward<TArgs>(args)...);
+}
+
+template <class T, size_t P, class... TArgs>
+T* HugeSingletonWithPriority(TArgs&&... args) {
+ return SingletonWithPriority<::NPrivate::THeapStore<T>, P>(std::forward<TArgs>(args)...)->D;
+}
+
+template <class T>
+const T& Default() {
+ return *(::NPrivate::SingletonInt<typename ::NPrivate::TDefault<T>, TSingletonTraits<T>::Priority>()->Get());
+}
diff --git a/util/generic/singleton_ut.cpp b/util/generic/singleton_ut.cpp
new file mode 100644
index 0000000000..35ba90cd76
--- /dev/null
+++ b/util/generic/singleton_ut.cpp
@@ -0,0 +1,46 @@
+#include "singleton.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TestSingleton) {
+ struct THuge {
+ char Buf[1000000];
+ int V = 1234;
+ };
+
+ Y_UNIT_TEST(TestHuge) {
+ UNIT_ASSERT_VALUES_EQUAL(*HugeSingleton<int>(), 0);
+ UNIT_ASSERT_VALUES_EQUAL(HugeSingleton<THuge>()->V, 1234);
+ }
+
+ struct TWithParams {
+ explicit TWithParams(const ui32 data1 = 0, const TString& data2 = TString())
+ : Data1(data1)
+ , Data2(data2)
+ {
+ }
+
+ ui32 Data1;
+ TString Data2;
+ };
+
+ Y_UNIT_TEST(TestConstructorParamsOrder) {
+ UNIT_ASSERT_VALUES_EQUAL(Singleton<TWithParams>(10, "123123")->Data1, 10);
+ UNIT_ASSERT_VALUES_EQUAL(Singleton<TWithParams>(20, "123123")->Data1, 10);
+ UNIT_ASSERT_VALUES_EQUAL(Singleton<TWithParams>(10, "456456")->Data2, "123123");
+ }
+
+ Y_UNIT_TEST(TestInstantiationWithConstructorParams) {
+ UNIT_ASSERT_VALUES_EQUAL(Singleton<TWithParams>(10)->Data1, 10);
+ UNIT_ASSERT_VALUES_EQUAL(HugeSingleton<TWithParams>(20, "123123")->Data2, "123123");
+ {
+ const auto value = SingletonWithPriority<TWithParams, 12312>(30, "456")->Data1;
+ UNIT_ASSERT_VALUES_EQUAL(value, 30);
+ }
+ {
+ const auto value = HugeSingletonWithPriority<TWithParams, 12311>(40, "789")->Data2;
+ UNIT_ASSERT_VALUES_EQUAL(value, "789");
+ }
+ UNIT_ASSERT_VALUES_EQUAL(Default<TWithParams>().Data1, 0);
+ }
+}
diff --git a/util/generic/size_literals.cpp b/util/generic/size_literals.cpp
new file mode 100644
index 0000000000..6f6714d0f4
--- /dev/null
+++ b/util/generic/size_literals.cpp
@@ -0,0 +1 @@
+#include "size_literals.h"
diff --git a/util/generic/size_literals.h b/util/generic/size_literals.h
new file mode 100644
index 0000000000..0b78b18687
--- /dev/null
+++ b/util/generic/size_literals.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "yexception.h"
+#include <util/system/types.h>
+#include <limits>
+
+// Unsigned literals
+
+constexpr ui64 operator"" _KB(unsigned long long value) noexcept {
+ return value * 1024;
+}
+
+constexpr ui64 operator"" _MB(unsigned long long value) noexcept {
+ return value * 1024_KB;
+}
+
+constexpr ui64 operator"" _GB(unsigned long long value) noexcept {
+ return value * 1024_MB;
+}
+
+constexpr ui64 operator"" _TB(unsigned long long value) noexcept {
+ return value * 1024_GB;
+}
+
+constexpr ui64 operator"" _PB(unsigned long long value) noexcept {
+ return value * 1024_TB;
+}
+
+constexpr ui64 operator"" _EB(unsigned long long value) noexcept {
+ return value * 1024_PB;
+}
+
+// Signed literals
+
+namespace NPrivate {
+ constexpr i64 SignedCast(ui64 value) {
+ return value <= static_cast<ui64>(std::numeric_limits<i64>::max())
+ ? static_cast<i64>(value)
+ : ythrow yexception() << "The resulting value " << value << " does not fit into the i64 type";
+ };
+}
+
+constexpr i64 operator"" _KBs(const unsigned long long value) noexcept {
+ return ::NPrivate::SignedCast(value * 1024);
+}
+
+constexpr i64 operator"" _MBs(unsigned long long value) noexcept {
+ return ::NPrivate::SignedCast(value * 1024_KB);
+}
+
+constexpr i64 operator"" _GBs(unsigned long long value) noexcept {
+ return ::NPrivate::SignedCast(value * 1024_MB);
+}
+
+constexpr i64 operator"" _TBs(unsigned long long value) noexcept {
+ return ::NPrivate::SignedCast(value * 1024_GB);
+}
+
+constexpr i64 operator"" _PBs(unsigned long long value) noexcept {
+ return ::NPrivate::SignedCast(value * 1024_TB);
+}
+
+constexpr i64 operator"" _EBs(unsigned long long value) noexcept {
+ return ::NPrivate::SignedCast(value * 1024_PB);
+}
diff --git a/util/generic/size_literals_ut.cpp b/util/generic/size_literals_ut.cpp
new file mode 100644
index 0000000000..410e22c39d
--- /dev/null
+++ b/util/generic/size_literals_ut.cpp
@@ -0,0 +1,68 @@
+#include "size_literals.h"
+
+void CompileTestUnsigned() {
+ static_assert(1_KB == 1024, "Wrong 1KB value");
+ static_assert(3_KB == 3 * 1024, "Wrong 3KB value");
+ static_assert(41_KB == 41 * 1024, "Wrong 41KB value");
+ static_assert(1023_KB == 1023 * 1024, "Wrong 1023KB value");
+
+ static_assert(1_MB == 1_KB * 1024, "Wrong 1MB value");
+ static_assert(5_MB == 5_KB * 1024, "Wrong 5MB value");
+ static_assert(71_MB == 71_KB * 1024, "Wrong 71MB value");
+ static_assert(1023_MB == 1023_KB * 1024, "Wrong 1023MB value");
+
+ static_assert(1_GB == 1_MB * 1024, "Wrong 1GB value");
+ static_assert(7_GB == 7_MB * 1024, "Wrong 7GB value");
+ static_assert(29_GB == 29_MB * 1024, "Wrong 29GB value");
+ static_assert(1023_GB == 1023_MB * 1024, "Wrong 1023GB value");
+
+ static_assert(1_TB == 1_GB * 1024, "Wrong 1TB value");
+ static_assert(9_TB == 9_GB * 1024, "Wrong 9TB value");
+ static_assert(57_TB == 57_GB * 1024, "Wrong 57TB value");
+ static_assert(1023_TB == 1023_GB * 1024, "Wrong 1023TB value");
+
+ static_assert(1_PB == 1_TB * 1024, "Wrong 1PB value");
+ static_assert(9_PB == 9_TB * 1024, "Wrong 9PB value");
+ static_assert(42_PB == 42_TB * 1024, "Wrong 42PB value");
+ static_assert(1023_PB == 1023_TB * 1024, "Wrong 1023PB value");
+
+ static_assert(1_EB == 1_PB * 1024, "Wrong 1EB value");
+ static_assert(9_EB == 9_PB * 1024, "Wrong 9EB value");
+
+ static_assert(9000000_TB == 9000000_GB * 1024, "Wrong 9000000TB value");
+}
+
+void CompileTestSigned() {
+ static_assert(1_KBs == 1024, "Wrong 1KBs value");
+ static_assert(3_KBs == 3 * 1024, "Wrong 3KBs value");
+ static_assert(41_KBs == 41 * 1024, "Wrong 41KBs value");
+ static_assert(1023_KBs == 1023 * 1024, "Wrong 1023KBs value");
+
+ static_assert(1_MBs == 1_KBs * 1024, "Wrong 1MBs value");
+ static_assert(5_MBs == 5_KBs * 1024, "Wrong 5MBs value");
+ static_assert(71_MBs == 71_KBs * 1024, "Wrong 71MBs value");
+ static_assert(1023_MBs == 1023_KBs * 1024, "Wrong 1023MBs value");
+
+ static_assert(1_GBs == 1_MBs * 1024, "Wrong 1GBs value");
+ static_assert(7_GBs == 7_MBs * 1024, "Wrong 7GBs value");
+ static_assert(29_GBs == 29_MBs * 1024, "Wrong 29GBs value");
+ static_assert(1023_GBs == 1023_MBs * 1024, "Wrong 1023GBs value");
+
+ static_assert(1_TBs == 1_GBs * 1024, "Wrong 1TBs value");
+ static_assert(9_TBs == 9_GBs * 1024, "Wrong 9TBs value");
+ static_assert(57_TBs == 57_GBs * 1024, "Wrong 57TBs value");
+ static_assert(1023_TBs == 1023_GBs * 1024, "Wrong 1023TBs value");
+
+ static_assert(1_PBs == 1_TBs * 1024, "Wrong 1PBs value");
+ static_assert(9_PBs == 9_TBs * 1024, "Wrong 9PBs value");
+ static_assert(42_PBs == 42_TBs * 1024, "Wrong 42PBs value");
+ static_assert(1023_PBs == 1023_TBs * 1024, "Wrong 1023PBs value");
+
+ static_assert(1_EBs == 1_PBs * 1024, "Wrong 1EBs value");
+ static_assert(7_EBs == 7_PBs * 1024, "Wrong 7EBs value");
+
+ static_assert(8388607_TBs == 8388607_GBs * 1024, "Wrong 8388607TBs value"); // 2**23 - 1 TB
+
+ // Should cause compilation error if uncommented
+ //static_assert(8388608_TBs == 8388608_GBs * 1024, "Wrong 8388608TBs value");
+}
diff --git a/util/generic/stack.cpp b/util/generic/stack.cpp
new file mode 100644
index 0000000000..7380615365
--- /dev/null
+++ b/util/generic/stack.cpp
@@ -0,0 +1 @@
+#include "stack.h"
diff --git a/util/generic/stack.h b/util/generic/stack.h
new file mode 100644
index 0000000000..dbcbf2b5c9
--- /dev/null
+++ b/util/generic/stack.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "fwd.h"
+#include "deque.h"
+
+#include <stack>
+
+template <class T, class S>
+class TStack: public std::stack<T, S> {
+ using TBase = std::stack<T, S>;
+
+public:
+ using TBase::TBase;
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+};
diff --git a/util/generic/stack_ut.cpp b/util/generic/stack_ut.cpp
new file mode 100644
index 0000000000..248127d326
--- /dev/null
+++ b/util/generic/stack_ut.cpp
@@ -0,0 +1,16 @@
+#include "stack.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TYStackTest) {
+ Y_UNIT_TEST(ExplicitBool) {
+ TStack<int> s;
+
+ UNIT_ASSERT(!s);
+ UNIT_ASSERT(s.empty());
+ s.push(100);
+ s.push(200);
+ UNIT_ASSERT(s);
+ UNIT_ASSERT(!s.empty());
+ }
+}
diff --git a/util/generic/store_policy.cpp b/util/generic/store_policy.cpp
new file mode 100644
index 0000000000..bd445c891f
--- /dev/null
+++ b/util/generic/store_policy.cpp
@@ -0,0 +1 @@
+#include "store_policy.h"
diff --git a/util/generic/store_policy.h b/util/generic/store_policy.h
new file mode 100644
index 0000000000..148821c70c
--- /dev/null
+++ b/util/generic/store_policy.h
@@ -0,0 +1,120 @@
+#pragma once
+
+#include <utility>
+#include "ptr.h"
+
+template <class TBase, class TCounter>
+struct TWithRefCount: public TBase, public TRefCounted<TWithRefCount<TBase, TCounter>, TCounter> {
+ template <typename... Args>
+ inline TWithRefCount(Args&&... args)
+ : TBase(std::forward<Args>(args)...)
+ {
+ }
+};
+
+template <class T>
+struct TPtrPolicy {
+ inline TPtrPolicy(T* t)
+ : T_(t)
+ {
+ }
+
+ inline T* Ptr() noexcept {
+ return T_;
+ }
+
+ inline const T* Ptr() const noexcept {
+ return T_;
+ }
+
+ T* T_;
+};
+
+template <class T>
+struct TEmbedPolicy {
+ template <typename... Args>
+ inline TEmbedPolicy(Args&&... args)
+ : T_(std::forward<Args>(args)...)
+ {
+ }
+
+ inline T* Ptr() noexcept {
+ return &T_;
+ }
+
+ inline const T* Ptr() const noexcept {
+ return &T_;
+ }
+
+ T T_;
+};
+
+template <class T, class TCounter>
+struct TRefPolicy {
+ using THelper = TWithRefCount<T, TCounter>;
+
+ template <typename... Args>
+ inline TRefPolicy(Args&&... args)
+ : T_(new THelper(std::forward<Args>(args)...))
+ {
+ }
+
+ inline T* Ptr() noexcept {
+ return T_.Get();
+ }
+
+ inline const T* Ptr() const noexcept {
+ return T_.Get();
+ }
+
+ TIntrusivePtr<THelper> T_;
+};
+
+/**
+ * Storage class that can be handy for implementing proxies / adaptors that can
+ * accept both lvalues and rvalues. In the latter case it's often required to
+ * extend the lifetime of the passed rvalue, and the only option is to store it
+ * in your proxy / adaptor.
+ *
+ * Example usage:
+ * \code
+ * template<class T>
+ * struct TProxy {
+ * TAutoEmbedOrPtrPolicy<T> Value_;
+ * // Your proxy code...
+ * };
+ *
+ * template<class T>
+ * TProxy<T> MakeProxy(T&& value) {
+ * // Rvalues are automagically moved-from, and stored inside the proxy.
+ * return {std::forward<T>(value)};
+ * }
+ * \endcode
+ *
+ * Look at `Reversed` in `adaptor.h` for real example.
+ */
+template <class T, bool IsReference = std::is_reference<T>::value>
+struct TAutoEmbedOrPtrPolicy: TPtrPolicy<std::remove_reference_t<T>> {
+ using TBase = TPtrPolicy<std::remove_reference_t<T>>;
+
+ TAutoEmbedOrPtrPolicy(T& reference)
+ : TBase(&reference)
+ {
+ }
+};
+
+template <class T>
+struct TAutoEmbedOrPtrPolicy<T, false>: TEmbedPolicy<T> {
+ using TBase = TEmbedPolicy<T>;
+
+ TAutoEmbedOrPtrPolicy(T&& object)
+ : TBase(std::move(object))
+ {
+ }
+};
+
+template <class T>
+using TAtomicRefPolicy = TRefPolicy<T, TAtomicCounter>;
+
+template <class T>
+using TSimpleRefPolicy = TRefPolicy<T, TSimpleCounter>;
diff --git a/util/generic/store_policy_ut.cpp b/util/generic/store_policy_ut.cpp
new file mode 100644
index 0000000000..c9722203aa
--- /dev/null
+++ b/util/generic/store_policy_ut.cpp
@@ -0,0 +1,87 @@
+#include "store_policy.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+#include <util/generic/algorithm.h>
+#include <util/generic/vector.h>
+
+Y_UNIT_TEST_SUITE(StorePolicy) {
+ Y_UNIT_TEST(Compileability) {
+ // construction
+ TAutoEmbedOrPtrPolicy<THolder<int>>(MakeHolder<int>(1));
+ TAutoEmbedOrPtrPolicy<TVector<int>>(TVector<int>{1, 2, 3});
+ auto a = MakeHolder<int>(42);
+ TAutoEmbedOrPtrPolicy<THolder<int>&>{a};
+
+ // const
+ (**TAutoEmbedOrPtrPolicy<THolder<int>>(MakeHolder<int>(1)).Ptr())++; // ok
+ (**TAutoEmbedOrPtrPolicy<THolder<int>&>(a).Ptr())++; // ok
+
+ const TVector<int> b = {0};
+ auto bValue = (*TAutoEmbedOrPtrPolicy<const TVector<int>&>(b).Ptr())[0]; // ok
+ // (*TAutoEmbedOrPtrPolicy<const TVector<int>&>(b).Ptr())[0]++; // not ok
+ Y_UNUSED(bValue);
+ }
+
+ template <typename T, typename TFunc>
+ void FunctionTakingRefDefaultIsObject(T&& a, TFunc func) {
+ TAutoEmbedOrPtrPolicy<T> refHolder(a);
+ func(refHolder);
+ }
+
+ Y_UNIT_TEST(Reference) {
+ {
+ TVector<ui32> a = {1, 2, 3};
+
+ FunctionTakingRefDefaultIsObject(a, [](auto& holder) {
+ holder.Ptr()->push_back(4);
+ auto secondHolder = holder;
+ secondHolder.Ptr()->push_back(5);
+ });
+
+ UNIT_ASSERT_VALUES_EQUAL(a.size(), 5);
+ }
+ {
+ const TVector<ui32> a = {1, 2, 3};
+
+ static_assert(std::is_const<decltype(a)>::value);
+
+ FunctionTakingRefDefaultIsObject(a, [](auto& holder) {
+ static_assert(std::is_const<std::remove_reference_t<decltype(*holder.Ptr())>>::value);
+ UNIT_ASSERT_VALUES_EQUAL(holder.Ptr()->size(), 3);
+ });
+ }
+ }
+
+ template <typename T, typename TFunc>
+ void FunctionTakingObjectDefaultObject(T&& a, TFunc func) {
+ TAutoEmbedOrPtrPolicy<T> objectHolder(std::forward<T>(a));
+ func(objectHolder);
+ }
+
+ Y_UNIT_TEST(Object) {
+ TVector<ui32> a = {1, 2, 3};
+
+ FunctionTakingObjectDefaultObject(std::move(a), [&a](auto& holder) {
+ static_assert(std::is_copy_assignable<decltype(holder)>::value);
+ UNIT_ASSERT_VALUES_EQUAL(a.size(), 0);
+ UNIT_ASSERT_VALUES_EQUAL(holder.Ptr()->size(), 3);
+ holder.Ptr()->push_back(4);
+ auto secondHolder = holder;
+ secondHolder.Ptr()->push_back(5);
+
+ UNIT_ASSERT_VALUES_EQUAL(holder.Ptr()->size(), 4);
+ UNIT_ASSERT_VALUES_EQUAL(secondHolder.Ptr()->size(), 5);
+ });
+
+ UNIT_ASSERT_VALUES_EQUAL(a.size(), 0);
+
+ THolder<int> b = MakeHolder<int>(42);
+ FunctionTakingObjectDefaultObject(std::move(b), [](auto& holder) {
+ static_assert(!std::is_copy_assignable<decltype(holder)>::value);
+ UNIT_ASSERT_VALUES_EQUAL(**holder.Ptr(), 42);
+ auto secondHolder = std::move(holder);
+ UNIT_ASSERT(!*holder.Ptr());
+ UNIT_ASSERT_VALUES_EQUAL(**secondHolder.Ptr(), 42);
+ });
+ }
+}
diff --git a/util/generic/strbase.h b/util/generic/strbase.h
new file mode 100644
index 0000000000..ab39fc7537
--- /dev/null
+++ b/util/generic/strbase.h
@@ -0,0 +1,607 @@
+#pragma once
+
+// Some of these includes are just a legacy from previous implementation.
+// We don't need them here, but removing them is tricky because it breaks all
+// kinds of builds downstream
+#include "mem_copy.h"
+#include "ptr.h"
+#include "utility.h"
+
+#include <util/charset/unidata.h>
+#include <util/system/platform.h>
+#include <util/system/yassert.h>
+
+#include <contrib/libs/libc_compat/string.h>
+
+#include <cctype>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+namespace NStringPrivate {
+ template <class TCharType>
+ size_t GetStringLengthWithLimit(const TCharType* s, size_t maxlen) {
+ Y_ASSERT(s);
+ size_t i = 0;
+ for (; i != maxlen && s[i]; ++i)
+ ;
+ return i;
+ }
+
+ inline size_t GetStringLengthWithLimit(const char* s, size_t maxlen) {
+ Y_ASSERT(s);
+ return strnlen(s, maxlen);
+ }
+}
+
+template <typename TDerived, typename TCharType, typename TTraitsType = std::char_traits<TCharType>>
+class TStringBase {
+ using TStringView = std::basic_string_view<TCharType>;
+ using TStringViewWithTraits = std::basic_string_view<TCharType, TTraitsType>;
+
+public:
+ using TChar = TCharType;
+ using TTraits = TTraitsType;
+ using TSelf = TStringBase<TDerived, TChar, TTraits>;
+
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ static constexpr size_t npos = size_t(-1);
+
+ using const_iterator = const TCharType*;
+ using const_reference = const TCharType&;
+
+ template <typename TBase>
+ struct TReverseIteratorBase {
+ constexpr TReverseIteratorBase() noexcept = default;
+ explicit constexpr TReverseIteratorBase(TBase p)
+ : P_(p)
+ {
+ }
+
+ TReverseIteratorBase operator++() noexcept {
+ --P_;
+ return *this;
+ }
+
+ TReverseIteratorBase operator++(int) noexcept {
+ TReverseIteratorBase old(*this);
+ --P_;
+ return old;
+ }
+
+ TReverseIteratorBase& operator--() noexcept {
+ ++P_;
+ return *this;
+ }
+
+ TReverseIteratorBase operator--(int) noexcept {
+ TReverseIteratorBase old(*this);
+ ++P_;
+ return old;
+ }
+
+ constexpr auto operator*() const noexcept -> std::remove_pointer_t<TBase>& {
+ return *TBase(*this);
+ }
+
+ explicit constexpr operator TBase() const noexcept {
+ return TBase(P_ - 1);
+ }
+
+ constexpr auto operator-(const TReverseIteratorBase o) const noexcept {
+ return o.P_ - P_;
+ }
+
+ constexpr bool operator==(const TReverseIteratorBase o) const noexcept {
+ return P_ == o.P_;
+ }
+
+ constexpr bool operator!=(const TReverseIteratorBase o) const noexcept {
+ return !(*this == o);
+ }
+
+ private:
+ TBase P_ = nullptr;
+ };
+ using const_reverse_iterator = TReverseIteratorBase<const_iterator>;
+
+ static constexpr size_t StrLen(const TCharType* s) noexcept {
+ if (Y_LIKELY(s)) {
+ return TTraits::length(s);
+ }
+ return 0;
+ }
+
+ template <class TCharTraits>
+ inline constexpr operator std::basic_string_view<TCharType, TCharTraits>() const {
+ return std::basic_string_view<TCharType, TCharTraits>(data(), size());
+ }
+
+ template <class TCharTraits, class Allocator>
+ inline explicit operator std::basic_string<TCharType, TCharTraits, Allocator>() const {
+ return std::basic_string<TCharType, TCharTraits, Allocator>(Ptr(), Len());
+ }
+
+ /**
+ * @param Pointer to character inside the string, or nullptr.
+ * @return Offset from string beginning (in chars), or npos on nullptr.
+ */
+ inline size_t off(const TCharType* ret) const noexcept {
+ return ret ? (size_t)(ret - Ptr()) : npos;
+ }
+
+ inline size_t IterOff(const_iterator it) const noexcept {
+ return begin() <= it && end() > it ? size_t(it - begin()) : npos;
+ }
+
+ inline const_iterator begin() const noexcept {
+ return Ptr();
+ }
+
+ inline const_iterator end() const noexcept {
+ return Ptr() + size();
+ }
+
+ inline const_iterator cbegin() const noexcept {
+ return begin();
+ }
+
+ inline const_iterator cend() const noexcept {
+ return end();
+ }
+
+ inline const_reverse_iterator rbegin() const noexcept {
+ return const_reverse_iterator(Ptr() + size());
+ }
+
+ inline const_reverse_iterator rend() const noexcept {
+ return const_reverse_iterator(Ptr());
+ }
+
+ inline const_reverse_iterator crbegin() const noexcept {
+ return rbegin();
+ }
+
+ inline const_reverse_iterator crend() const noexcept {
+ return rend();
+ }
+
+ inline TCharType back() const noexcept {
+ Y_ASSERT(!this->empty());
+ return Ptr()[Len() - 1];
+ }
+
+ inline TCharType front() const noexcept {
+ Y_ASSERT(!empty());
+ return Ptr()[0];
+ }
+
+ constexpr const TCharType* data() const noexcept {
+ return Ptr();
+ }
+
+ constexpr inline size_t size() const noexcept {
+ return Len();
+ }
+
+ constexpr inline bool is_null() const noexcept {
+ return *Ptr() == 0;
+ }
+
+ Y_PURE_FUNCTION constexpr inline bool empty() const noexcept {
+ return Len() == 0;
+ }
+
+ constexpr inline explicit operator bool() const noexcept {
+ return !empty();
+ }
+
+public: // style-guide compliant methods
+ constexpr const TCharType* Data() const noexcept {
+ return Ptr();
+ }
+
+ constexpr size_t Size() const noexcept {
+ return Len();
+ }
+
+ Y_PURE_FUNCTION constexpr bool Empty() const noexcept {
+ return 0 == Len();
+ }
+
+private:
+ static inline TStringView LegacySubString(const TStringView view, size_t p, size_t n) noexcept {
+ p = Min(p, view.length());
+ return view.substr(p, n);
+ }
+
+public:
+ // ~~~ Comparison ~~~ : FAMILY0(int, compare)
+ static int compare(const TSelf& s1, const TSelf& s2) noexcept {
+ return s1.AsStringView().compare(s2.AsStringView());
+ }
+
+ static int compare(const TCharType* p, const TSelf& s2) noexcept {
+ TCharType null{0};
+ return TStringViewWithTraits(p ? p : &null).compare(s2.AsStringView());
+ }
+
+ static int compare(const TSelf& s1, const TCharType* p) noexcept {
+ TCharType null{0};
+ return s1.AsStringView().compare(p ? p : &null);
+ }
+
+ static int compare(const TStringView s1, const TStringView s2) noexcept {
+ return TStringViewWithTraits(s1.data(), s1.size()).compare(TStringViewWithTraits(s2.data(), s2.size()));
+ }
+
+ template <class T>
+ inline int compare(const T& t) const noexcept {
+ return compare(*this, t);
+ }
+
+ inline int compare(size_t p, size_t n, const TStringView t) const noexcept {
+ return compare(LegacySubString(*this, p, n), t);
+ }
+
+ inline int compare(size_t p, size_t n, const TStringView t, size_t p1, size_t n1) const noexcept {
+ return compare(LegacySubString(*this, p, n), LegacySubString(t, p1, n1));
+ }
+
+ inline int compare(size_t p, size_t n, const TStringView t, size_t n1) const noexcept {
+ return compare(LegacySubString(*this, p, n), LegacySubString(t, 0, n1));
+ }
+
+ inline int compare(const TCharType* p, size_t len) const noexcept {
+ return compare(*this, TStringView(p, len));
+ }
+
+ static bool equal(const TSelf& s1, const TSelf& s2) noexcept {
+ return s1.AsStringView() == s2.AsStringView();
+ }
+
+ static bool equal(const TSelf& s1, const TCharType* p) noexcept {
+ if (p == nullptr) {
+ return s1.Len() == 0;
+ }
+
+ return s1.AsStringView() == p;
+ }
+
+ static bool equal(const TCharType* p, const TSelf& s2) noexcept {
+ return equal(s2, p);
+ }
+
+ static bool equal(const TStringView s1, const TStringView s2) noexcept {
+ return TStringViewWithTraits{s1.data(), s1.size()} == TStringViewWithTraits{s2.data(), s2.size()};
+ }
+
+ template <class T>
+ inline bool equal(const T& t) const noexcept {
+ return equal(*this, t);
+ }
+
+ inline bool equal(size_t p, size_t n, const TStringView t) const noexcept {
+ return equal(LegacySubString(*this, p, n), t);
+ }
+
+ inline bool equal(size_t p, size_t n, const TStringView t, size_t p1, size_t n1) const noexcept {
+ return equal(LegacySubString(*this, p, n), LegacySubString(t, p1, n1));
+ }
+
+ inline bool equal(size_t p, size_t n, const TStringView t, size_t n1) const noexcept {
+ return equal(LegacySubString(*this, p, n), LegacySubString(t, 0, n1));
+ }
+
+ static inline bool StartsWith(const TCharType* what, size_t whatLen, const TCharType* with, size_t withLen) noexcept {
+ return withLen <= whatLen && TStringViewWithTraits(what, withLen) == TStringViewWithTraits(with, withLen);
+ }
+
+ static inline bool EndsWith(const TCharType* what, size_t whatLen, const TCharType* with, size_t withLen) noexcept {
+ return withLen <= whatLen && TStringViewWithTraits(what + whatLen - withLen, withLen) == TStringViewWithTraits(with, withLen);
+ }
+
+ inline bool StartsWith(const TCharType* s, size_t n) const noexcept {
+ return StartsWith(Ptr(), Len(), s, n);
+ }
+
+ inline bool StartsWith(const TStringView s) const noexcept {
+ return StartsWith(s.data(), s.length());
+ }
+
+ inline bool StartsWith(TCharType ch) const noexcept {
+ return !empty() && TTraits::eq(*Ptr(), ch);
+ }
+
+ inline bool EndsWith(const TCharType* s, size_t n) const noexcept {
+ return EndsWith(Ptr(), Len(), s, n);
+ }
+
+ inline bool EndsWith(const TStringView s) const noexcept {
+ return EndsWith(s.data(), s.length());
+ }
+
+ inline bool EndsWith(TCharType ch) const noexcept {
+ return !empty() && TTraits::eq(Ptr()[Len() - 1], ch);
+ }
+
+ template <typename TDerived2, typename TTraits2>
+ bool operator==(const TStringBase<TDerived2, TChar, TTraits2>& s2) const noexcept {
+ return equal(*this, s2);
+ }
+
+ bool operator==(TStringView s2) const noexcept {
+ return equal(*this, s2);
+ }
+
+ bool operator==(const TCharType* pc) const noexcept {
+ return equal(*this, pc);
+ }
+
+#ifndef __cpp_impl_three_way_comparison
+ friend bool operator==(const TCharType* pc, const TSelf& s) noexcept {
+ return equal(pc, s);
+ }
+
+ template <typename TDerived2, typename TTraits2>
+ friend bool operator!=(const TSelf& s1, const TStringBase<TDerived2, TChar, TTraits2>& s2) noexcept {
+ return !(s1 == s2);
+ }
+
+ friend bool operator!=(const TSelf& s1, TStringView s2) noexcept {
+ return !(s1 == s2);
+ }
+
+ friend bool operator!=(const TSelf& s, const TCharType* pc) noexcept {
+ return !(s == pc);
+ }
+
+ friend bool operator!=(const TCharType* pc, const TSelf& s) noexcept {
+ return !(pc == s);
+ }
+#endif
+
+ template <typename TDerived2, typename TTraits2>
+ friend bool operator<(const TSelf& s1, const TStringBase<TDerived2, TChar, TTraits2>& s2) noexcept {
+ return compare(s1, s2) < 0;
+ }
+
+ friend bool operator<(const TSelf& s1, TStringView s2) noexcept {
+ return compare(s1, s2) < 0;
+ }
+
+ friend bool operator<(const TSelf& s, const TCharType* pc) noexcept {
+ return compare(s, pc) < 0;
+ }
+
+ friend bool operator<(const TCharType* pc, const TSelf& s) noexcept {
+ return compare(pc, s) < 0;
+ }
+
+ template <typename TDerived2, typename TTraits2>
+ friend bool operator<=(const TSelf& s1, const TStringBase<TDerived2, TChar, TTraits2>& s2) noexcept {
+ return compare(s1, s2) <= 0;
+ }
+
+ friend bool operator<=(const TSelf& s1, TStringView s2) noexcept {
+ return compare(s1, s2) <= 0;
+ }
+
+ friend bool operator<=(const TSelf& s, const TCharType* pc) noexcept {
+ return compare(s, pc) <= 0;
+ }
+
+ friend bool operator<=(const TCharType* pc, const TSelf& s) noexcept {
+ return compare(pc, s) <= 0;
+ }
+
+ template <typename TDerived2, typename TTraits2>
+ friend bool operator>(const TSelf& s1, const TStringBase<TDerived2, TChar, TTraits2>& s2) noexcept {
+ return compare(s1, s2) > 0;
+ }
+
+ friend bool operator>(const TSelf& s1, TStringView s2) noexcept {
+ return compare(s1, s2) > 0;
+ }
+
+ friend bool operator>(const TSelf& s, const TCharType* pc) noexcept {
+ return compare(s, pc) > 0;
+ }
+
+ friend bool operator>(const TCharType* pc, const TSelf& s) noexcept {
+ return compare(pc, s) > 0;
+ }
+
+ template <typename TDerived2, typename TTraits2>
+ friend bool operator>=(const TSelf& s1, const TStringBase<TDerived2, TChar, TTraits2>& s2) noexcept {
+ return compare(s1, s2) >= 0;
+ }
+
+ friend bool operator>=(const TSelf& s1, TStringView s2) noexcept {
+ return compare(s1, s2) >= 0;
+ }
+
+ friend bool operator>=(const TSelf& s, const TCharType* pc) noexcept {
+ return compare(s, pc) >= 0;
+ }
+
+ friend bool operator>=(const TCharType* pc, const TSelf& s) noexcept {
+ return compare(pc, s) >= 0;
+ }
+
+ // ~~ Read access ~~
+ inline TCharType at(size_t pos) const noexcept {
+ if (Y_LIKELY(pos < Len())) {
+ return (Ptr())[pos];
+ }
+ return 0;
+ }
+
+ inline TCharType operator[](size_t pos) const noexcept {
+ Y_ASSERT(pos < this->size());
+
+ return Ptr()[pos];
+ }
+
+ //~~~~Search~~~~
+ /**
+ * @return Position of the substring inside this string, or `npos` if not found.
+ */
+ inline size_t find(const TStringView s, size_t pos = 0) const noexcept {
+ return find(s.data(), pos, s.size());
+ }
+
+ inline size_t find(const TCharType* s, size_t pos, size_t count) const noexcept {
+ return AsStringView().find(s, pos, count);
+ }
+
+ inline size_t find(TCharType c, size_t pos = 0) const noexcept {
+ return AsStringView().find(c, pos);
+ }
+
+ inline size_t rfind(TCharType c) const noexcept {
+ return AsStringView().rfind(c);
+ }
+
+ inline size_t rfind(TCharType c, size_t pos) const noexcept {
+ if (pos == 0) {
+ return npos;
+ }
+ return AsStringView().rfind(c, pos - 1);
+ }
+
+ inline size_t rfind(const TStringView str, size_t pos = npos) const {
+ return AsStringView().rfind(str.data(), pos, str.size());
+ }
+
+ //~~~~Contains~~~~
+ /**
+ * @returns Whether this string contains the provided substring.
+ */
+ inline bool Contains(const TStringView s, size_t pos = 0) const noexcept {
+ return !s.length() || find(s, pos) != npos;
+ }
+
+ inline bool Contains(TChar c, size_t pos = 0) const noexcept {
+ return find(c, pos) != npos;
+ }
+
+ inline void Contains(std::enable_if<std::is_unsigned<TCharType>::value, char> c, size_t pos = 0) const noexcept {
+ return find(ui8(c), pos) != npos;
+ }
+
+ //~~~~Character Set Search~~~
+ inline size_t find_first_of(TCharType c) const noexcept {
+ return find_first_of(c, 0);
+ }
+
+ inline size_t find_first_of(TCharType c, size_t pos) const noexcept {
+ return find(c, pos);
+ }
+
+ inline size_t find_first_of(const TStringView set) const noexcept {
+ return find_first_of(set, 0);
+ }
+
+ inline size_t find_first_of(const TStringView set, size_t pos) const noexcept {
+ return AsStringView().find_first_of(set.data(), pos, set.size());
+ }
+
+ inline size_t find_first_not_of(TCharType c) const noexcept {
+ return find_first_not_of(c, 0);
+ }
+
+ inline size_t find_first_not_of(TCharType c, size_t pos) const noexcept {
+ return find_first_not_of(TStringView(&c, 1), pos);
+ }
+
+ inline size_t find_first_not_of(const TStringView set) const noexcept {
+ return find_first_not_of(set, 0);
+ }
+
+ inline size_t find_first_not_of(const TStringView set, size_t pos) const noexcept {
+ return AsStringView().find_first_not_of(set.data(), pos, set.size());
+ }
+
+ inline size_t find_last_of(TCharType c, size_t pos = npos) const noexcept {
+ return find_last_of(&c, pos, 1);
+ }
+
+ inline size_t find_last_of(const TStringView set, size_t pos = npos) const noexcept {
+ return find_last_of(set.data(), pos, set.length());
+ }
+
+ inline size_t find_last_of(const TCharType* set, size_t pos, size_t n) const noexcept {
+ return AsStringView().find_last_of(set, pos, n);
+ }
+
+ inline size_t find_last_not_of(TCharType c, size_t pos = npos) const noexcept {
+ return AsStringView().find_last_not_of(c, pos);
+ }
+
+ inline size_t find_last_not_of(const TStringView set, size_t pos = npos) const noexcept {
+ return find_last_not_of(set.data(), pos, set.length());
+ }
+
+ inline size_t find_last_not_of(const TCharType* set, size_t pos, size_t n) const noexcept {
+ return AsStringView().find_last_not_of(set, pos, n);
+ }
+
+ inline size_t copy(TCharType* pc, size_t n, size_t pos) const {
+ if (pos > Len()) {
+ throw std::out_of_range("TStringBase::copy");
+ }
+
+ return CopyImpl(pc, n, pos);
+ }
+
+ inline size_t copy(TCharType* pc, size_t n) const noexcept {
+ return CopyImpl(pc, n, 0);
+ }
+
+ inline size_t strcpy(TCharType* pc, size_t n) const noexcept {
+ if (n) {
+ n = copy(pc, n - 1);
+ pc[n] = 0;
+ }
+
+ return n;
+ }
+
+ inline TDerived copy() const Y_WARN_UNUSED_RESULT {
+ return TDerived(Ptr(), Len());
+ }
+
+ // ~~~ Partial copy ~~~~
+ TDerived substr(size_t pos, size_t n = npos) const Y_WARN_UNUSED_RESULT {
+ return TDerived(*This(), pos, n);
+ }
+
+private:
+ using GenericFinder = const TCharType* (*)(const TCharType*, size_t, const TCharType*, size_t);
+
+ TStringViewWithTraits AsStringView() const {
+ return static_cast<TStringViewWithTraits>(*this);
+ }
+
+ constexpr inline const TCharType* Ptr() const noexcept {
+ return This()->data();
+ }
+
+ constexpr inline size_t Len() const noexcept {
+ return This()->length();
+ }
+
+ constexpr inline const TDerived* This() const noexcept {
+ return static_cast<const TDerived*>(this);
+ }
+
+ inline size_t CopyImpl(TCharType* pc, size_t n, size_t pos) const noexcept {
+ const size_t toCopy = Min(Len() - pos, n);
+
+ TTraits::copy(pc, Ptr() + pos, toCopy);
+
+ return toCopy;
+ }
+};
diff --git a/util/generic/strbuf.cpp b/util/generic/strbuf.cpp
new file mode 100644
index 0000000000..668602ca16
--- /dev/null
+++ b/util/generic/strbuf.cpp
@@ -0,0 +1,9 @@
+#include "strbuf.h"
+
+#include <util/stream/output.h>
+#include <ostream>
+
+std::ostream& operator<<(std::ostream& os, TStringBuf buf) {
+ os.write(buf.data(), buf.size());
+ return os;
+}
diff --git a/util/generic/strbuf.h b/util/generic/strbuf.h
new file mode 100644
index 0000000000..70b9360d58
--- /dev/null
+++ b/util/generic/strbuf.h
@@ -0,0 +1,539 @@
+#pragma once
+
+#include "fwd.h"
+#include "strbase.h"
+#include "utility.h"
+#include "typetraits.h"
+
+#include <string_view>
+
+using namespace std::string_view_literals;
+
+template <typename TCharType, typename TTraits>
+class TBasicStringBuf: public std::basic_string_view<TCharType>,
+ public TStringBase<TBasicStringBuf<TCharType, TTraits>, TCharType, TTraits> {
+private:
+ using TdSelf = TBasicStringBuf;
+ using TBase = TStringBase<TdSelf, TCharType, TTraits>;
+ using TStringView = std::basic_string_view<TCharType>;
+
+public:
+ using char_type = TCharType; // TODO: DROP
+ using traits_type = TTraits;
+
+ //Resolving some ambiguity between TStringBase and std::basic_string_view
+ //for typenames
+ using typename TStringView::const_iterator;
+ using typename TStringView::const_reference;
+ using typename TStringView::const_reverse_iterator;
+ using typename TStringView::iterator;
+ using typename TStringView::reference;
+ using typename TStringView::reverse_iterator;
+ using typename TStringView::size_type;
+ using typename TStringView::value_type;
+
+ //for constants
+ using TStringView::npos;
+
+ //for methods and operators
+ using TStringView::begin;
+ using TStringView::cbegin;
+ using TStringView::cend;
+ using TStringView::crbegin;
+ using TStringView::crend;
+ using TStringView::end;
+ using TStringView::rbegin;
+ using TStringView::rend;
+
+ using TStringView::data;
+ using TStringView::empty;
+ using TStringView::size;
+
+ using TStringView::operator[];
+
+ /*
+ * WARN:
+ * TBase::at silently return 0 in case of range error,
+ * while std::string_view throws std::out_of_range.
+ */
+ using TBase::at;
+ using TStringView::back;
+ using TStringView::front;
+
+ using TStringView::find;
+ /*
+ * WARN:
+ * TBase::*find* methods take into account TCharTraits,
+ * while TTStringView::*find* would use default std::char_traits.
+ */
+ using TBase::find_first_not_of;
+ using TBase::find_first_of;
+ using TBase::find_last_not_of;
+ using TBase::find_last_of;
+ using TBase::rfind;
+
+ using TStringView::copy;
+ /*
+ * WARN:
+ * TBase::compare takes into account TCharTraits,
+ * thus making it possible to implement case-insensitive string buffers,
+ * if it is using TStringBase::compare
+ */
+ using TBase::compare;
+
+ /*
+ * WARN:
+ * TBase::substr properly checks boundary cases and clamps them with maximum valid values,
+ * while TStringView::substr throws std::out_of_range error.
+ */
+ using TBase::substr;
+
+ /*
+ * WARN:
+ * Constructing std::string_view(nullptr, non_zero_size) ctor
+ * results in undefined behavior according to the standard.
+ * In libc++ this UB results in runtime assertion, though it is better
+ * to generate compilation error instead.
+ */
+ constexpr inline TBasicStringBuf(std::nullptr_t begin, size_t size) = delete;
+
+ constexpr inline TBasicStringBuf(const TCharType* data, size_t size) noexcept
+ : TStringView(data, size)
+ {
+ }
+
+ constexpr TBasicStringBuf(const TCharType* data) noexcept
+ /*
+ * WARN: TBase::StrLen properly handles nullptr,
+ * while std::string_view (using std::char_traits) will abort in such case
+ */
+ : TStringView(data, TBase::StrLen(data))
+ {
+ }
+
+ constexpr inline TBasicStringBuf(const TCharType* beg, const TCharType* end) noexcept
+ : TStringView(beg, end - beg)
+ {
+ }
+
+ template <typename D, typename T>
+ inline TBasicStringBuf(const TStringBase<D, TCharType, T>& str) noexcept
+ : TStringView(str.data(), str.size())
+ {
+ }
+
+ template <typename T, typename A>
+ inline TBasicStringBuf(const std::basic_string<TCharType, T, A>& str) noexcept
+ : TStringView(str)
+ {
+ }
+
+ template <typename TCharTraits>
+ constexpr TBasicStringBuf(std::basic_string_view<TCharType, TCharTraits> view) noexcept
+ : TStringView(view)
+ {
+ }
+
+ constexpr inline TBasicStringBuf() noexcept {
+ /*
+ * WARN:
+ * This ctor can not be defaulted due to the following feature of default initialization:
+ * If T is a const-qualified type, it must be a class type with a user-provided default constructor.
+ * (see https://en.cppreference.com/w/cpp/language/default_initialization).
+ *
+ * This means, that a class with default ctor can not be a constant member of another class with default ctor.
+ */
+ }
+
+ inline TBasicStringBuf(const TBasicStringBuf& src, size_t pos, size_t n) noexcept
+ : TBasicStringBuf(src)
+ {
+ Skip(pos).Trunc(n);
+ }
+
+ inline TBasicStringBuf(const TBasicStringBuf& src, size_t pos) noexcept
+ : TBasicStringBuf(src, pos, TBase::npos)
+ {
+ }
+
+ Y_PURE_FUNCTION inline TBasicStringBuf SubString(size_t pos, size_t n) const noexcept {
+ pos = Min(pos, size());
+ n = Min(n, size() - pos);
+ return TBasicStringBuf(data() + pos, n);
+ }
+
+public:
+ void Clear() {
+ *this = TdSelf();
+ }
+
+ constexpr bool IsInited() const noexcept {
+ return data() != nullptr;
+ }
+
+public:
+ /**
+ * Tries to split string in two parts using given delimiter character.
+ * Searches for the delimiter, scanning string from the beginning.
+ * The delimiter is excluded from the result. Both out parameters are
+ * left unmodified if there was no delimiter character in string.
+ *
+ * @param[in] delim Delimiter character.
+ * @param[out] l The first part of split result.
+ * @param[out] r The second part of split result.
+ * @returns Whether the split was actually performed.
+ */
+ inline bool TrySplit(TCharType delim, TdSelf& l, TdSelf& r) const noexcept {
+ return TrySplitOn(TBase::find(delim), l, r);
+ }
+
+ /**
+ * Tries to split string in two parts using given delimiter character.
+ * Searches for the delimiter, scanning string from the end.
+ * The delimiter is excluded from the result. Both out parameters are
+ * left unmodified if there was no delimiter character in string.
+ *
+ * @param[in] delim Delimiter character.
+ * @param[out] l The first part of split result.
+ * @param[out] r The second part of split result.
+ * @returns Whether the split was actually performed.
+ */
+ inline bool TryRSplit(TCharType delim, TdSelf& l, TdSelf& r) const noexcept {
+ return TrySplitOn(TBase::rfind(delim), l, r);
+ }
+
+ /**
+ * Tries to split string in two parts using given delimiter sequence.
+ * Searches for the delimiter, scanning string from the beginning.
+ * The delimiter sequence is excluded from the result. Both out parameters
+ * are left unmodified if there was no delimiter character in string.
+ *
+ * @param[in] delim Delimiter sequence.
+ * @param[out] l The first part of split result.
+ * @param[out] r The second part of split result.
+ * @returns Whether the split was actually performed.
+ */
+ inline bool TrySplit(TdSelf delim, TdSelf& l, TdSelf& r) const noexcept {
+ return TrySplitOn(TBase::find(delim), l, r, delim.size());
+ }
+
+ /**
+ * Tries to split string in two parts using given delimiter sequence.
+ * Searches for the delimiter, scanning string from the end.
+ * The delimiter sequence is excluded from the result. Both out parameters
+ * are left unmodified if there was no delimiter character in string.
+ *
+ * @param[in] delim Delimiter sequence.
+ * @param[out] l The first part of split result.
+ * @param[out] r The second part of split result.
+ * @returns Whether the split was actually performed.
+ */
+ inline bool TryRSplit(TdSelf delim, TdSelf& l, TdSelf& r) const noexcept {
+ return TrySplitOn(TBase::rfind(delim), l, r, delim.size());
+ }
+
+ inline void Split(TCharType delim, TdSelf& l, TdSelf& r) const noexcept {
+ SplitTemplate(delim, l, r);
+ }
+
+ inline void RSplit(TCharType delim, TdSelf& l, TdSelf& r) const noexcept {
+ RSplitTemplate(delim, l, r);
+ }
+
+ inline void Split(TdSelf delim, TdSelf& l, TdSelf& r) const noexcept {
+ SplitTemplate(delim, l, r);
+ }
+
+ inline void RSplit(TdSelf delim, TdSelf& l, TdSelf& r) const noexcept {
+ RSplitTemplate(delim, l, r);
+ }
+
+private:
+ // splits on a delimiter at a given position; delimiter is excluded
+ void DoSplitOn(size_t pos, TdSelf& l, TdSelf& r, size_t len) const noexcept {
+ Y_ASSERT(pos != TBase::npos);
+
+ // make a copy in case one of l/r is really *this
+ const TdSelf tok = SubStr(pos + len);
+ l = Head(pos);
+ r = tok;
+ }
+
+public:
+ // In all methods below with @pos parameter, @pos is supposed to be
+ // a result of string find()/rfind()/find_first() or other similiar functions,
+ // returning either position within string length [0..size()) or npos.
+ // For all other @pos values (out of string index range) the behaviour isn't well defined
+ // For example, for TStringBuf s("abc"):
+ // s.TrySplitOn(s.find('z'), ...) is false, but s.TrySplitOn(100500, ...) is true.
+
+ bool TrySplitOn(size_t pos, TdSelf& l, TdSelf& r, size_t len = 1) const noexcept {
+ if (TBase::npos == pos)
+ return false;
+
+ DoSplitOn(pos, l, r, len);
+ return true;
+ }
+
+ void SplitOn(size_t pos, TdSelf& l, TdSelf& r, size_t len = 1) const noexcept {
+ if (!TrySplitOn(pos, l, r, len)) {
+ l = *this;
+ r = TdSelf();
+ }
+ }
+
+ bool TrySplitAt(size_t pos, TdSelf& l, TdSelf& r) const noexcept {
+ return TrySplitOn(pos, l, r, 0);
+ }
+
+ void SplitAt(size_t pos, TdSelf& l, TdSelf& r) const noexcept {
+ SplitOn(pos, l, r, 0);
+ }
+
+ /*
+ // Not implemented intentionally, use TrySplitOn() instead
+ void RSplitOn(size_t pos, TdSelf& l, TdSelf& r) const noexcept;
+ void RSplitAt(size_t pos, TdSelf& l, TdSelf& r) const noexcept;
+*/
+
+public:
+ Y_PURE_FUNCTION inline TdSelf After(TCharType c) const noexcept {
+ TdSelf l, r;
+ return TrySplit(c, l, r) ? r : *this;
+ }
+
+ Y_PURE_FUNCTION inline TdSelf Before(TCharType c) const noexcept {
+ TdSelf l, r;
+ return TrySplit(c, l, r) ? l : *this;
+ }
+
+ Y_PURE_FUNCTION inline TdSelf RAfter(TCharType c) const noexcept {
+ TdSelf l, r;
+ return TryRSplit(c, l, r) ? r : *this;
+ }
+
+ Y_PURE_FUNCTION inline TdSelf RBefore(TCharType c) const noexcept {
+ TdSelf l, r;
+ return TryRSplit(c, l, r) ? l : *this;
+ }
+
+public:
+ inline bool AfterPrefix(const TdSelf& prefix, TdSelf& result) const noexcept {
+ if (this->StartsWith(prefix)) {
+ result = Tail(prefix.size());
+ return true;
+ }
+ return false;
+ }
+
+ inline bool BeforeSuffix(const TdSelf& suffix, TdSelf& result) const noexcept {
+ if (this->EndsWith(suffix)) {
+ result = Head(size() - suffix.size());
+ return true;
+ }
+ return false;
+ }
+
+ // returns true if string started with `prefix`, false otherwise
+ inline bool SkipPrefix(const TdSelf& prefix) noexcept {
+ return AfterPrefix(prefix, *this);
+ }
+
+ // returns true if string ended with `suffix`, false otherwise
+ inline bool ChopSuffix(const TdSelf& suffix) noexcept {
+ return BeforeSuffix(suffix, *this);
+ }
+
+public:
+ // returns tail, including pos
+ TdSelf SplitOffAt(size_t pos) {
+ const TdSelf tok = SubStr(pos);
+ Trunc(pos);
+ return tok;
+ }
+
+ // returns head, tail includes pos
+ TdSelf NextTokAt(size_t pos) {
+ const TdSelf tok = Head(pos);
+ Skip(pos);
+ return tok;
+ }
+
+ TdSelf SplitOffOn(size_t pos) {
+ TdSelf tok;
+ SplitOn(pos, *this, tok);
+ return tok;
+ }
+
+ TdSelf NextTokOn(size_t pos) {
+ TdSelf tok;
+ SplitOn(pos, tok, *this);
+ return tok;
+ }
+ /*
+ // See comment on RSplitOn() above
+ TdSelf RSplitOffOn(size_t pos);
+ TdSelf RNextTokOn(size_t pos);
+*/
+
+public:
+ TdSelf SplitOff(TCharType delim) {
+ TdSelf tok;
+ Split(delim, *this, tok);
+ return tok;
+ }
+
+ TdSelf RSplitOff(TCharType delim) {
+ TdSelf tok;
+ RSplit(delim, tok, *this);
+ return tok;
+ }
+
+ bool NextTok(TCharType delim, TdSelf& tok) {
+ return NextTokTemplate(delim, tok);
+ }
+
+ bool NextTok(TdSelf delim, TdSelf& tok) {
+ return NextTokTemplate(delim, tok);
+ }
+
+ bool RNextTok(TCharType delim, TdSelf& tok) {
+ return RNextTokTemplate(delim, tok);
+ }
+
+ bool RNextTok(TdSelf delim, TdSelf& tok) {
+ return RNextTokTemplate(delim, tok);
+ }
+
+ bool ReadLine(TdSelf& tok) {
+ if (NextTok('\n', tok)) {
+ while (!tok.empty() && tok.back() == '\r') {
+ tok.remove_suffix(1);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ TdSelf NextTok(TCharType delim) {
+ return NextTokTemplate(delim);
+ }
+
+ TdSelf RNextTok(TCharType delim) {
+ return RNextTokTemplate(delim);
+ }
+
+ TdSelf NextTok(TdSelf delim) {
+ return NextTokTemplate(delim);
+ }
+
+ TdSelf RNextTok(TdSelf delim) {
+ return RNextTokTemplate(delim);
+ }
+
+public: // string subsequences
+ /// Cut last @c shift characters (or less if length is less than @c shift)
+ inline TdSelf& Chop(size_t shift) noexcept {
+ this->remove_suffix(std::min(shift, size()));
+ return *this;
+ }
+
+ /// Cut first @c shift characters (or less if length is less than @c shift)
+ inline TdSelf& Skip(size_t shift) noexcept {
+ this->remove_prefix(std::min(shift, size()));
+ return *this;
+ }
+
+ /// Sets the start pointer to a position relative to the end
+ inline TdSelf& RSeek(size_t tailSize) noexcept {
+ if (size() > tailSize) {
+ //WARN: removing TStringView:: will lead to an infinite recursion
+ *this = TStringView::substr(size() - tailSize, tailSize);
+ }
+
+ return *this;
+ }
+
+ // coverity[exn_spec_violation]
+ inline TdSelf& Trunc(size_t targetSize) noexcept {
+ // Coverity false positive issue
+ // exn_spec_violation: An exception of type "std::out_of_range" is thrown but the exception specification "noexcept" doesn't allow it to be thrown. This will result in a call to terminate().
+ // fun_call_w_exception: Called function TStringView::substr throws an exception of type "std::out_of_range".
+ // Suppress this issue because we pass argument pos=0 and string_view can't throw std::out_of_range.
+ *this = TStringView::substr(0, targetSize); //WARN: removing TStringView:: will lead to an infinite recursion
+ return *this;
+ }
+
+ Y_PURE_FUNCTION inline TdSelf SubStr(size_t beg) const noexcept {
+ return TdSelf(*this).Skip(beg);
+ }
+
+ Y_PURE_FUNCTION inline TdSelf SubStr(size_t beg, size_t len) const noexcept {
+ return SubStr(beg).Trunc(len);
+ }
+
+ Y_PURE_FUNCTION inline TdSelf Head(size_t pos) const noexcept {
+ return TdSelf(*this).Trunc(pos);
+ }
+
+ Y_PURE_FUNCTION inline TdSelf Tail(size_t pos) const noexcept {
+ return SubStr(pos);
+ }
+
+ Y_PURE_FUNCTION inline TdSelf Last(size_t len) const noexcept {
+ return TdSelf(*this).RSeek(len);
+ }
+
+private:
+ template <typename TDelimiterType>
+ TdSelf NextTokTemplate(TDelimiterType delim) {
+ TdSelf tok;
+ Split(delim, tok, *this);
+ return tok;
+ }
+
+ template <typename TDelimiterType>
+ TdSelf RNextTokTemplate(TDelimiterType delim) {
+ TdSelf tok;
+ RSplit(delim, *this, tok);
+ return tok;
+ }
+
+ template <typename TDelimiterType>
+ bool NextTokTemplate(TDelimiterType delim, TdSelf& tok) {
+ if (!empty()) {
+ tok = NextTokTemplate(delim);
+ return true;
+ }
+ return false;
+ }
+
+ template <typename TDelimiterType>
+ bool RNextTokTemplate(TDelimiterType delim, TdSelf& tok) {
+ if (!empty()) {
+ tok = RNextTokTemplate(delim);
+ return true;
+ }
+ return false;
+ }
+
+ template <typename TDelimiterType>
+ inline void SplitTemplate(TDelimiterType delim, TdSelf& l, TdSelf& r) const noexcept {
+ if (!TrySplit(delim, l, r)) {
+ l = *this;
+ r = TdSelf();
+ }
+ }
+
+ template <typename TDelimiterType>
+ inline void RSplitTemplate(TDelimiterType delim, TdSelf& l, TdSelf& r) const noexcept {
+ if (!TryRSplit(delim, l, r)) {
+ r = *this;
+ l = TdSelf();
+ }
+ }
+};
+
+std::ostream& operator<<(std::ostream& os, TStringBuf buf);
diff --git a/util/generic/strbuf_ut.cpp b/util/generic/strbuf_ut.cpp
new file mode 100644
index 0000000000..69cde785af
--- /dev/null
+++ b/util/generic/strbuf_ut.cpp
@@ -0,0 +1,373 @@
+#include "strbuf.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <string_view>
+
+Y_UNIT_TEST_SUITE(TStrBufTest) {
+ Y_UNIT_TEST(TestConstructorsAndOperators) {
+ TStringBuf str("qwerty");
+
+ UNIT_ASSERT_EQUAL(*str.data(), 'q');
+ UNIT_ASSERT_EQUAL(str.size(), 6);
+
+ TStringBuf str1("qwe\0rty"sv);
+ TStringBuf str2(str1.data());
+ UNIT_ASSERT_VALUES_UNEQUAL(str1, str2);
+ UNIT_ASSERT_VALUES_EQUAL(str1.size(), 7);
+ UNIT_ASSERT_VALUES_EQUAL(str2.size(), 3);
+
+ std::string_view helloWorld("Hello, World!");
+ TStringBuf fromStringView(helloWorld);
+ UNIT_ASSERT_EQUAL(fromStringView.data(), helloWorld.data());
+ UNIT_ASSERT_EQUAL(fromStringView.size(), helloWorld.size());
+
+ std::string_view fromStringBuf = fromStringView;
+ UNIT_ASSERT_EQUAL(helloWorld.data(), fromStringBuf.data());
+ UNIT_ASSERT_EQUAL(helloWorld.size(), fromStringBuf.size());
+ }
+
+ Y_UNIT_TEST(TestConstExpr) {
+ static constexpr TStringBuf str1("qwe\0rty", 7);
+ static constexpr TStringBuf str2(str1.data(), str1.size());
+ static constexpr TStringBuf str3 = "qwe\0rty"sv;
+
+ UNIT_ASSERT_VALUES_EQUAL(str1.size(), 7);
+
+ UNIT_ASSERT_VALUES_EQUAL(str1, str2);
+ UNIT_ASSERT_VALUES_EQUAL(str2, str3);
+ UNIT_ASSERT_VALUES_EQUAL(str1, str3);
+
+ static constexpr std::string_view view1(str1);
+ UNIT_ASSERT_VALUES_EQUAL(str1, view1);
+ static_assert(str1.data() == view1.data());
+ static_assert(str1.size() == view1.size());
+
+ static constexpr TStringBuf str4(view1);
+ UNIT_ASSERT_VALUES_EQUAL(str1, str4);
+ static_assert(str1.data() == str4.data());
+ static_assert(str1.size() == str4.size());
+ }
+
+ Y_UNIT_TEST(TestAfter) {
+ TStringBuf str("qwerty");
+
+ UNIT_ASSERT_VALUES_EQUAL(str.After('w'), TStringBuf("erty"));
+ UNIT_ASSERT_VALUES_EQUAL(str.After('x'), TStringBuf("qwerty"));
+ UNIT_ASSERT_VALUES_EQUAL(str.After('y'), TStringBuf());
+ UNIT_ASSERT_STRINGS_EQUAL(str.After('='), str);
+
+ // Also works properly on empty strings
+ TStringBuf empty;
+ UNIT_ASSERT_STRINGS_EQUAL(empty.After('x'), empty);
+ }
+
+ Y_UNIT_TEST(TestBefore) {
+ TStringBuf str("qwerty");
+
+ UNIT_ASSERT_VALUES_EQUAL(str.Before('w'), TStringBuf("q"));
+ UNIT_ASSERT_VALUES_EQUAL(str.Before('x'), TStringBuf("qwerty"));
+ UNIT_ASSERT_VALUES_EQUAL(str.Before('y'), TStringBuf("qwert"));
+ UNIT_ASSERT_VALUES_EQUAL(str.Before('q'), TStringBuf());
+ }
+
+ Y_UNIT_TEST(TestRAfterBefore) {
+ TStringBuf str("a/b/c");
+ UNIT_ASSERT_STRINGS_EQUAL(str.RAfter('/'), "c");
+ UNIT_ASSERT_STRINGS_EQUAL(str.RAfter('_'), str);
+ UNIT_ASSERT_STRINGS_EQUAL(str.RAfter('a'), "/b/c");
+ UNIT_ASSERT_STRINGS_EQUAL(str.RBefore('/'), "a/b");
+ UNIT_ASSERT_STRINGS_EQUAL(str.RBefore('_'), str);
+ UNIT_ASSERT_STRINGS_EQUAL(str.RBefore('a'), "");
+ }
+
+ Y_UNIT_TEST(TestAfterPrefix) {
+ TStringBuf str("cat_dog");
+
+ TStringBuf r = "the_same";
+ UNIT_ASSERT(!str.AfterPrefix("dog", r));
+ UNIT_ASSERT_EQUAL(r, "the_same");
+ UNIT_ASSERT(str.AfterPrefix("cat_", r));
+ UNIT_ASSERT_EQUAL(r, "dog");
+
+ //example:
+ str = "http://ya.ru";
+ if (str.AfterPrefix("http://", r)) {
+ UNIT_ASSERT_EQUAL(r, "ya.ru");
+ }
+
+ // SkipPrefix()
+ TStringBuf a = "abcdef";
+ UNIT_ASSERT(a.SkipPrefix("a") && a == "bcdef");
+ UNIT_ASSERT(a.SkipPrefix("bc") && a == "def");
+ UNIT_ASSERT(a.SkipPrefix("") && a == "def");
+ UNIT_ASSERT(!a.SkipPrefix("xyz") && a == "def");
+ UNIT_ASSERT(!a.SkipPrefix("defg") && a == "def");
+ UNIT_ASSERT(a.SkipPrefix("def") && a == "");
+ UNIT_ASSERT(a.SkipPrefix("") && a == "");
+ UNIT_ASSERT(!a.SkipPrefix("def") && a == "");
+ }
+
+ Y_UNIT_TEST(TestBeforeSuffix) {
+ TStringBuf str("cat_dog");
+
+ TStringBuf r = "the_same";
+ UNIT_ASSERT(!str.BeforeSuffix("cat", r));
+ UNIT_ASSERT_EQUAL(r, "the_same");
+ UNIT_ASSERT(str.BeforeSuffix("_dog", r));
+ UNIT_ASSERT_EQUAL(r, "cat");
+
+ //example:
+ str = "maps.yandex.com.ua";
+ if (str.BeforeSuffix(".ru", r)) {
+ UNIT_ASSERT_EQUAL(r, "maps.yandex");
+ }
+
+ // ChopSuffix()
+ TStringBuf a = "abcdef";
+ UNIT_ASSERT(a.ChopSuffix("f") && a == "abcde");
+ UNIT_ASSERT(a.ChopSuffix("de") && a == "abc");
+ UNIT_ASSERT(a.ChopSuffix("") && a == "abc");
+ UNIT_ASSERT(!a.ChopSuffix("xyz") && a == "abc");
+ UNIT_ASSERT(!a.ChopSuffix("abcd") && a == "abc");
+ UNIT_ASSERT(a.ChopSuffix("abc") && a == "");
+ UNIT_ASSERT(a.ChopSuffix("") && a == "");
+ UNIT_ASSERT(!a.ChopSuffix("abc") && a == "");
+ }
+
+ Y_UNIT_TEST(TestEmpty) {
+ UNIT_ASSERT(TStringBuf().empty());
+ UNIT_ASSERT(!TStringBuf("q").empty());
+ }
+
+ Y_UNIT_TEST(TestShift) {
+ TStringBuf qw("qwerty");
+ TStringBuf str;
+
+ str = qw;
+ str.Chop(10);
+ UNIT_ASSERT(str.empty());
+
+ str = qw;
+ UNIT_ASSERT_EQUAL(str.SubStr(2), TStringBuf("erty"));
+ UNIT_ASSERT_EQUAL(str.Skip(3), qw.SubStr(3));
+ str.Chop(1);
+ UNIT_ASSERT_EQUAL(str, TStringBuf("rt"));
+ }
+
+ Y_UNIT_TEST(TestSplit) {
+ TStringBuf qw("qwerty");
+ TStringBuf lt, rt;
+
+ rt = qw;
+ lt = rt.NextTok('r');
+ UNIT_ASSERT_EQUAL(lt, TStringBuf("qwe"));
+ UNIT_ASSERT_EQUAL(rt, TStringBuf("ty"));
+
+ lt = qw;
+ rt = lt.SplitOff('r');
+ UNIT_ASSERT_EQUAL(lt, TStringBuf("qwe"));
+ UNIT_ASSERT_EQUAL(rt, TStringBuf("ty"));
+
+ rt = qw;
+ lt = rt.NextTok('r');
+ TStringBuf ty = rt.NextTok('r'); // no 'r' in "ty"
+ UNIT_ASSERT_EQUAL(rt.size(), 0);
+ UNIT_ASSERT_EQUAL(ty, TStringBuf("ty"));
+ }
+
+ Y_UNIT_TEST(TestNextTok) {
+ TStringBuf buf("12q45q");
+ TStringBuf tok;
+
+ UNIT_ASSERT(buf.NextTok('q', tok) && tok == "12");
+ UNIT_ASSERT(buf.NextTok('q', tok) && tok == "45");
+ UNIT_ASSERT(!buf.NextTok('q', tok));
+ }
+
+ Y_UNIT_TEST(TestNextStringTok) {
+ TStringBuf buf1("a@@b@@c");
+ UNIT_ASSERT_EQUAL(buf1.NextTok("@@"), TStringBuf("a"));
+ UNIT_ASSERT_EQUAL(buf1.NextTok("@@"), TStringBuf("b"));
+ UNIT_ASSERT_EQUAL(buf1.NextTok("@@"), TStringBuf("c"));
+ UNIT_ASSERT_EQUAL(buf1, TStringBuf());
+
+ TStringBuf buf2("a@@b@@c");
+ UNIT_ASSERT_EQUAL(buf2.RNextTok("@@"), TStringBuf("c"));
+ UNIT_ASSERT_EQUAL(buf2.RNextTok("@@"), TStringBuf("b"));
+ UNIT_ASSERT_EQUAL(buf2.RNextTok("@@"), TStringBuf("a"));
+ UNIT_ASSERT_EQUAL(buf2, TStringBuf());
+
+ TStringBuf buf3("a@@b@@c");
+ UNIT_ASSERT_EQUAL(buf3.RNextTok("@@@"), TStringBuf("a@@b@@c"));
+ UNIT_ASSERT_EQUAL(buf3, TStringBuf());
+ }
+
+ Y_UNIT_TEST(TestReadLine) {
+ TStringBuf buf("12\n45\r\n\r\n23");
+ TStringBuf tok;
+
+ buf.ReadLine(tok);
+ UNIT_ASSERT_VALUES_EQUAL(tok, "12");
+
+ buf.ReadLine(tok);
+ UNIT_ASSERT_VALUES_EQUAL(tok, "45");
+
+ buf.ReadLine(tok);
+ UNIT_ASSERT_VALUES_EQUAL(tok, "");
+
+ buf.ReadLine(tok);
+ UNIT_ASSERT_VALUES_EQUAL(tok, "23");
+
+ UNIT_ASSERT(!buf.ReadLine(tok));
+ }
+
+ Y_UNIT_TEST(TestRFind) {
+ TStringBuf buf1 = "123123456";
+ UNIT_ASSERT_EQUAL(buf1.rfind('3'), 5);
+ UNIT_ASSERT_EQUAL(buf1.rfind('4'), 6);
+ UNIT_ASSERT_EQUAL(buf1.rfind('7'), TStringBuf::npos);
+
+ TStringBuf buf2;
+ UNIT_ASSERT_EQUAL(buf2.rfind('3'), TStringBuf::npos);
+
+ TStringBuf buf3 = TStringBuf("123123456", 6);
+ UNIT_ASSERT_EQUAL(buf3.rfind('3'), 5);
+ UNIT_ASSERT_EQUAL(buf3.rfind('4'), TStringBuf::npos);
+ UNIT_ASSERT_EQUAL(buf3.rfind('7'), TStringBuf::npos);
+
+ TStringBuf buf4 = TStringBuf("123123456", 5);
+ UNIT_ASSERT_EQUAL(buf4.rfind('3'), 2);
+ }
+
+ Y_UNIT_TEST(TestRNextTok) {
+ TStringBuf buf1("a.b.c");
+ UNIT_ASSERT_EQUAL(buf1.RNextTok('.'), TStringBuf("c"));
+ UNIT_ASSERT_EQUAL(buf1, TStringBuf("a.b"));
+
+ TStringBuf buf2("a");
+ UNIT_ASSERT_EQUAL(buf2.RNextTok('.'), TStringBuf("a"));
+ UNIT_ASSERT_EQUAL(buf2, TStringBuf());
+
+ TStringBuf buf3("ab cd ef"), tok;
+ UNIT_ASSERT(buf3.RNextTok(' ', tok) && tok == "ef" && buf3 == "ab cd");
+ UNIT_ASSERT(buf3.RNextTok(' ', tok) && tok == "cd" && buf3 == "ab");
+ UNIT_ASSERT(buf3.RNextTok(' ', tok) && tok == "ab" && buf3 == "");
+ UNIT_ASSERT(!buf3.RNextTok(' ', tok) && tok == "ab" && buf3 == ""); // not modified
+ }
+
+ Y_UNIT_TEST(TestRSplitOff) {
+ TStringBuf buf1("a.b.c");
+ UNIT_ASSERT_EQUAL(buf1.RSplitOff('.'), TStringBuf("a.b"));
+ UNIT_ASSERT_EQUAL(buf1, TStringBuf("c"));
+
+ TStringBuf buf2("a");
+ UNIT_ASSERT_EQUAL(buf2.RSplitOff('.'), TStringBuf());
+ UNIT_ASSERT_EQUAL(buf2, TStringBuf("a"));
+ }
+
+ Y_UNIT_TEST(TestCBeginCEnd) {
+ const char helloThere[] = "Hello there";
+ TStringBuf s{helloThere};
+
+ size_t index = 0;
+ for (auto it = s.cbegin(); s.cend() != it; ++it, ++index) {
+ UNIT_ASSERT_VALUES_EQUAL(helloThere[index], *it);
+ }
+ }
+
+ Y_UNIT_TEST(TestSplitOnAt) {
+ TStringBuf s = "abcabc";
+ TStringBuf l, r;
+
+ size_t pos = s.find('a');
+ UNIT_ASSERT(s.TrySplitOn(pos, l, r));
+ UNIT_ASSERT(l == "" && r == "bcabc");
+ UNIT_ASSERT(s.TrySplitAt(pos, l, r));
+ UNIT_ASSERT(l == "" && r == "abcabc");
+
+ pos = s.find("ca");
+ UNIT_ASSERT(s.TrySplitOn(pos, l, r));
+ UNIT_ASSERT(l == "ab" && r == "abc");
+ UNIT_ASSERT(s.TrySplitOn(pos, l, r, 2));
+ UNIT_ASSERT(l == "ab" && r == "bc");
+ UNIT_ASSERT(s.TrySplitAt(pos, l, r));
+ UNIT_ASSERT(l == "ab" && r == "cabc");
+
+ // out of range
+ pos = 100500;
+ UNIT_ASSERT(s.TrySplitOn(pos, l, r)); // still true
+ UNIT_ASSERT(l == "abcabc" && r == "");
+ l = "111";
+ r = "222";
+ UNIT_ASSERT(s.TrySplitAt(pos, l, r)); // still true
+ UNIT_ASSERT(l == "abcabc" && r == "");
+
+ // npos
+ pos = s.find("missing");
+ l = "111";
+ r = "222";
+ UNIT_ASSERT(!s.TrySplitOn(pos, l, r));
+ UNIT_ASSERT(l == "111" && r == "222"); // not modified
+ s.SplitOn(pos, l, r);
+ UNIT_ASSERT(l == "abcabc" && r == ""); // modified
+
+ l = "111";
+ r = "222";
+ UNIT_ASSERT(!s.TrySplitAt(pos, l, r));
+ UNIT_ASSERT(l == "111" && r == "222"); // not modified
+ s.SplitAt(pos, l, r);
+ UNIT_ASSERT(l == "abcabc" && r == ""); // modified
+ }
+
+ template <class T>
+ void PassByConstReference(const T& val) {
+ // In https://st.yandex-team.ru/IGNIETFERRO-294 was assumed that `const char[]` types are compile time strings
+ // and that CharTraits::Length may not be called for them. Unfortunately that is not true, `char[]` types
+ // are easily converted to `const char[]` if they are passed to a function accepting `const T&`.
+ UNIT_ASSERT(TStringBuf(val).size() == 5);
+ }
+
+ Y_UNIT_TEST(TestPassingArraysByConstReference) {
+ char data[] = "Hello\0word";
+ PassByConstReference(data);
+ }
+
+ Y_UNIT_TEST(TestTruncate) {
+ TStringBuf s = "123";
+ s.Trunc(5);
+ UNIT_ASSERT_STRINGS_EQUAL(s, "123");
+ s.Trunc(3);
+ UNIT_ASSERT_STRINGS_EQUAL(s, "123");
+ s.Trunc(1);
+ UNIT_ASSERT_STRINGS_EQUAL(s, "1");
+ s.Trunc(0);
+ UNIT_ASSERT_STRINGS_EQUAL(s, "");
+ s.Trunc(0);
+ UNIT_ASSERT_STRINGS_EQUAL(s, "");
+ }
+}
+
+Y_UNIT_TEST_SUITE(TWtrBufTest) {
+ Y_UNIT_TEST(TestConstExpr) {
+ static constexpr TWtringBuf str1(u"qwe\0rty", 7);
+ static constexpr TWtringBuf str2(str1.data(), str1.size());
+ static constexpr TWtringBuf str3 = u"qwe\0rty"sv;
+
+ UNIT_ASSERT_VALUES_EQUAL(str1.size(), 7);
+
+ UNIT_ASSERT_VALUES_EQUAL(str1, str2);
+ UNIT_ASSERT_VALUES_EQUAL(str2, str3);
+ UNIT_ASSERT_VALUES_EQUAL(str1, str3);
+
+ static constexpr std::u16string_view view1(str1);
+ UNIT_ASSERT_VALUES_EQUAL(str1, view1);
+ static_assert(str1.data() == view1.data());
+ static_assert(str1.size() == view1.size());
+
+ static constexpr TWtringBuf str4(view1);
+ UNIT_ASSERT_VALUES_EQUAL(str1, str4);
+ static_assert(str1.data() == str4.data());
+ static_assert(str1.size() == str4.size());
+ }
+}
diff --git a/util/generic/strfcpy.cpp b/util/generic/strfcpy.cpp
new file mode 100644
index 0000000000..19b4da493e
--- /dev/null
+++ b/util/generic/strfcpy.cpp
@@ -0,0 +1,50 @@
+/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Modified version is
+ * Copyright (c) Alexey Galakhov
+ */
+
+#include "strfcpy.h"
+
+/*
+ * Copy string src to buffer dst of size dsize. At most dsize-1
+ * chars will be copied. Always NUL terminates (unless dsize == 0).
+ */
+void strfcpy(char* dst, const char* src, size_t dsize)
+{
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0') {
+ break;
+ }
+ }
+ }
+
+ /* Not enough room in dst, add NUL */
+ if (nleft == 0) {
+ if (dsize != 0) {
+ *dst = '\0'; /* NUL-terminate dst */
+ }
+ }
+}
diff --git a/util/generic/strfcpy.h b/util/generic/strfcpy.h
new file mode 100644
index 0000000000..8a95bc3df2
--- /dev/null
+++ b/util/generic/strfcpy.h
@@ -0,0 +1,17 @@
+#pragma once
+
+/*
+ * strfcpy is a faster version of strlcpy().
+ * It returns void thus does not wastes time computing
+ * (most likely, unneeded) strlen(str)
+ *
+ * Comparison with other copying functions:
+ * strcpy() - buffer overflow ready
+ * strncpy() - wastes time filling exactly n bytes with 0
+ * strlcpy() - wastes time searching for the length of src
+ * memcpy() - wastes time copying exactly n bytes even if the string is shorter
+ */
+
+#include <stddef.h>
+
+void strfcpy(char* dst, const char* src, size_t n);
diff --git a/util/generic/string.cpp b/util/generic/string.cpp
new file mode 100644
index 0000000000..3c655f1f66
--- /dev/null
+++ b/util/generic/string.cpp
@@ -0,0 +1,156 @@
+#include "string.h"
+
+#include <util/string/ascii.h>
+#include <util/system/sanitizers.h>
+#include <util/system/sys_alloc.h>
+#include <util/charset/wide.h>
+
+#include <iostream>
+#include <cctype>
+
+alignas(32) const char NULL_STRING_REPR[128] = {0};
+
+std::ostream& operator<<(std::ostream& os, const TString& s) {
+ return os.write(s.data(), s.size());
+}
+
+std::istream& operator>>(std::istream& is, TString& s) {
+ return is >> s.MutRef();
+}
+
+template <>
+bool TBasicString<char, std::char_traits<char>>::to_lower(size_t pos, size_t n) {
+ return Transform([](size_t, char c) { return AsciiToLower(c); }, pos, n);
+}
+
+template <>
+bool TBasicString<char, std::char_traits<char>>::to_upper(size_t pos, size_t n) {
+ return Transform([](size_t, char c) { return AsciiToUpper(c); }, pos, n);
+}
+
+template <>
+bool TBasicString<char, std::char_traits<char>>::to_title(size_t pos, size_t n) {
+ if (n == 0) {
+ return false;
+ }
+ bool changed = to_upper(pos, 1);
+ return to_lower(pos + 1, n - 1) || changed;
+}
+
+template <>
+TUtf16String&
+TBasicString<wchar16, std::char_traits<wchar16>>::AppendAscii(const ::TStringBuf& s) {
+ ReserveAndResize(size() + s.size());
+
+ auto dst = begin() + size() - s.size();
+
+ for (const char* src = s.data(); dst != end(); ++dst, ++src) {
+ *dst = static_cast<wchar16>(*src);
+ }
+
+ return *this;
+}
+
+template <>
+TUtf16String&
+TBasicString<wchar16, std::char_traits<wchar16>>::AppendUtf8(const ::TStringBuf& s) {
+ size_t oldSize = size();
+ ReserveAndResize(size() + s.size() * 4);
+ size_t written = 0;
+ size_t pos = UTF8ToWideImpl(s.data(), s.size(), begin() + oldSize, written);
+ if (pos != s.size()) {
+ ythrow yexception() << "failed to decode UTF-8 string at pos " << pos << ::NDetail::InStringMsg(s.data(), s.size());
+ }
+ resize(oldSize + written);
+
+ return *this;
+}
+
+template <>
+bool TBasicString<wchar16, std::char_traits<wchar16>>::to_lower(size_t pos, size_t n) {
+ return ToLower(*this, pos, n);
+}
+
+template <>
+bool TBasicString<wchar16, std::char_traits<wchar16>>::to_upper(size_t pos, size_t n) {
+ return ToUpper(*this, pos, n);
+}
+
+template <>
+bool TBasicString<wchar16, std::char_traits<wchar16>>::to_title(size_t pos, size_t n) {
+ return ToTitle(*this, pos, n);
+}
+
+template <>
+TUtf32String&
+TBasicString<wchar32, std::char_traits<wchar32>>::AppendAscii(const ::TStringBuf& s) {
+ ReserveAndResize(size() + s.size());
+
+ auto dst = begin() + size() - s.size();
+
+ for (const char* src = s.data(); dst != end(); ++dst, ++src) {
+ *dst = static_cast<wchar32>(*src);
+ }
+
+ return *this;
+}
+
+template <>
+TBasicString<char, std::char_traits<char>>&
+TBasicString<char, std::char_traits<char>>::AppendUtf16(const ::TWtringBuf& s) {
+ const size_t oldSize = size();
+ ReserveAndResize(size() + WideToUTF8BufferSize(s.size()));
+
+ size_t written = 0;
+ WideToUTF8(s.data(), s.size(), begin() + oldSize, written);
+
+ resize(oldSize + written);
+
+ return *this;
+}
+
+template <>
+TUtf32String&
+TBasicString<wchar32, std::char_traits<wchar32>>::AppendUtf8(const ::TStringBuf& s) {
+ size_t oldSize = size();
+ ReserveAndResize(size() + s.size() * 4);
+ size_t written = 0;
+ size_t pos = UTF8ToWideImpl(s.data(), s.size(), begin() + oldSize, written);
+ if (pos != s.size()) {
+ ythrow yexception() << "failed to decode UTF-8 string at pos " << pos << ::NDetail::InStringMsg(s.data(), s.size());
+ }
+ resize(oldSize + written);
+
+ return *this;
+}
+
+template <>
+TUtf32String&
+TBasicString<wchar32, std::char_traits<wchar32>>::AppendUtf16(const ::TWtringBuf& s) {
+ size_t oldSize = size();
+ ReserveAndResize(size() + s.size() * 2);
+
+ wchar32* oldEnd = begin() + oldSize;
+ wchar32* end = oldEnd;
+ NDetail::UTF16ToUTF32ImplScalar(s.data(), s.data() + s.size(), end);
+ size_t written = end - oldEnd;
+
+ resize(oldSize + written);
+
+ return *this;
+}
+
+template <>
+bool TBasicString<wchar32, std::char_traits<wchar32>>::to_lower(size_t pos, size_t n) {
+ return ToLower(*this, pos, n);
+}
+
+template <>
+bool TBasicString<wchar32, std::char_traits<wchar32>>::to_upper(size_t pos, size_t n) {
+ return ToUpper(*this, pos, n);
+}
+
+template <>
+bool TBasicString<wchar32, std::char_traits<wchar32>>::to_title(size_t pos, size_t n) {
+ return ToTitle(*this, pos, n);
+}
diff --git a/util/generic/string.h b/util/generic/string.h
new file mode 100644
index 0000000000..8cd8aa6917
--- /dev/null
+++ b/util/generic/string.h
@@ -0,0 +1,1287 @@
+#pragma once
+
+#include <cstddef>
+#include <cstring>
+#include <stlfwd>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+
+#include <util/system/yassert.h>
+#include <util/system/atomic.h>
+
+#include "ptr.h"
+#include "utility.h"
+#include "bitops.h"
+#include "explicit_type.h"
+#include "reserve.h"
+#include "singleton.h"
+#include "strbase.h"
+#include "strbuf.h"
+#include "string_hash.h"
+
+#if defined(address_sanitizer_enabled) || defined(thread_sanitizer_enabled)
+ #include "hide_ptr.h"
+#endif
+
+template <class TCharType, class TCharTraits, class TAllocator>
+void ResizeUninitialized(std::basic_string<TCharType, TCharTraits, TAllocator>& s, size_t len) {
+#if defined(_YNDX_LIBCXX_ENABLE_STRING_RESIZE_UNINITIALIZED)
+ s.resize_uninitialized(len);
+#else
+ s.resize(len);
+#endif
+}
+
+#define Y_NOEXCEPT
+
+#ifndef TSTRING_IS_STD_STRING
+template <class T>
+class TStringPtrOps {
+public:
+ static inline void Ref(T* t) noexcept {
+ if (t != T::NullStr()) {
+ t->Ref();
+ }
+ }
+
+ static inline void UnRef(T* t) noexcept {
+ if (t != T::NullStr()) {
+ t->UnRef();
+ }
+ }
+
+ static inline long RefCount(const T* t) noexcept {
+ if (t == T::NullStr()) {
+ return -1;
+ }
+
+ return t->RefCount();
+ }
+};
+
+alignas(32) extern const char NULL_STRING_REPR[128];
+
+struct TRefCountHolder {
+ TAtomicCounter C = 1;
+};
+
+template <class B>
+struct TStdString: public TRefCountHolder, public B {
+ template <typename... Args>
+ inline TStdString(Args&&... args)
+ : B(std::forward<Args>(args)...)
+ {
+ }
+
+ inline bool IsNull() const noexcept {
+ return this == NullStr();
+ }
+
+ static TStdString* NullStr() noexcept {
+ #ifdef _LIBCPP_VERSION
+ return (TStdString*)NULL_STRING_REPR;
+ #else
+ return Singleton<TStdString>();
+ #endif
+ }
+
+private:
+ friend TStringPtrOps<TStdString>;
+ inline void Ref() noexcept {
+ C.Inc();
+ }
+
+ inline void UnRef() noexcept {
+ if (C.Val() == 1 || C.Dec() == 0) {
+ delete this;
+ }
+ }
+
+ inline long RefCount() const noexcept {
+ return C.Val();
+ }
+};
+
+template <class TStringType>
+class TBasicCharRef {
+public:
+ using TChar = typename TStringType::TChar;
+
+ TBasicCharRef(TStringType& s, size_t pos)
+ : S_(s)
+ , Pos_(pos)
+ {
+ }
+
+ operator TChar() const {
+ return S_.at(Pos_);
+ }
+
+ TChar* operator&() {
+ return S_.begin() + Pos_;
+ }
+
+ const TChar* operator&() const {
+ return S_.cbegin() + Pos_;
+ }
+
+ TBasicCharRef& operator=(TChar c) {
+ Y_ASSERT(Pos_ < S_.size() || (Pos_ == S_.size() && !c));
+
+ S_.Detach()[Pos_] = c;
+
+ return *this;
+ }
+
+ TBasicCharRef& operator=(const TBasicCharRef& other) {
+ return this->operator=(static_cast<TChar>(other));
+ }
+
+ /*
+ * WARN:
+ * Though references are copyable types according to the standard,
+ * the behavior of this explicit default specification is different from the one
+ * implemented by the assignment operator above.
+ *
+ * An attempt to explicitly delete it will break valid invocations like
+ * auto c = flag ? s[i] : s[j];
+ */
+ TBasicCharRef(const TBasicCharRef&) = default;
+
+private:
+ TStringType& S_;
+ size_t Pos_;
+};
+#endif
+
+template <typename TCharType, typename TTraits>
+class TBasicString: public TStringBase<TBasicString<TCharType, TTraits>, TCharType, TTraits> {
+public:
+ // TODO: Move to private section
+ using TBase = TStringBase<TBasicString, TCharType, TTraits>;
+ using TStringType = std::basic_string<TCharType, TTraits>;
+#ifdef TSTRING_IS_STD_STRING
+ using TStorage = TStringType;
+ using reference = typename TStorage::reference;
+#else
+ using TStdStr = TStdString<TStringType>;
+ using TStorage = TIntrusivePtr<TStdStr, TStringPtrOps<TStdStr>>;
+ using reference = TBasicCharRef<TBasicString>;
+#endif
+ using char_type = TCharType; // TODO: DROP
+ using value_type = TCharType;
+ using traits_type = TTraits;
+
+ using iterator = TCharType*;
+ using reverse_iterator = typename TBase::template TReverseIteratorBase<iterator>;
+ using typename TBase::const_iterator;
+ using typename TBase::const_reference;
+ using typename TBase::const_reverse_iterator;
+
+ struct TUninitialized {
+ explicit TUninitialized(size_t size)
+ : Size(size)
+ {
+ }
+
+ size_t Size;
+ };
+
+ static size_t max_size() noexcept {
+ static size_t res = TStringType().max_size();
+
+ return res;
+ }
+
+protected:
+#ifdef TSTRING_IS_STD_STRING
+ TStorage Storage_;
+#else
+ TStorage S_;
+
+ template <typename... A>
+ static TStorage Construct(A&&... a) {
+ return {new TStdStr(std::forward<A>(a)...), typename TStorage::TNoIncrement()};
+ }
+
+ static TStorage Construct() noexcept {
+ return TStdStr::NullStr();
+ }
+
+ TStdStr& StdStr() noexcept {
+ return *S_;
+ }
+
+ const TStdStr& StdStr() const noexcept {
+ return *S_;
+ }
+
+ /**
+ * Makes a distinct copy of this string. `IsDetached()` is always true after this call.
+ *
+ * @throw std::length_error
+ */
+ void Clone() {
+ Construct(StdStr()).Swap(S_);
+ }
+
+ size_t RefCount() const noexcept {
+ return S_.RefCount();
+ }
+#endif
+
+public:
+ inline const TStringType& ConstRef() const {
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_;
+#else
+ return StdStr();
+#endif
+ }
+
+ inline TStringType& MutRef() {
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_;
+#else
+ Detach();
+
+ return StdStr();
+#endif
+ }
+
+ inline const_reference operator[](size_t pos) const noexcept {
+ Y_ASSERT(pos <= length());
+
+ return this->data()[pos];
+ }
+
+ inline reference operator[](size_t pos) noexcept {
+ Y_ASSERT(pos <= length());
+
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_[pos];
+#else
+ return reference(*this, pos);
+#endif
+ }
+
+ using TBase::back;
+
+ inline reference back() noexcept {
+ Y_ASSERT(!this->empty());
+
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_.back();
+#else
+ if (Y_UNLIKELY(this->empty())) {
+ return reference(*this, 0);
+ }
+
+ return reference(*this, length() - 1);
+#endif
+ }
+
+ using TBase::front;
+
+ inline reference front() noexcept {
+ Y_ASSERT(!this->empty());
+
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_.front();
+#else
+ return reference(*this, 0);
+#endif
+ }
+
+ inline size_t length() const noexcept {
+ return ConstRef().length();
+ }
+
+ inline const TCharType* data() const noexcept {
+ return ConstRef().data();
+ }
+
+ inline const TCharType* c_str() const noexcept {
+ return ConstRef().c_str();
+ }
+
+ // ~~~ STL compatible method to obtain data pointer ~~~
+ iterator begin() {
+ return &*MutRef().begin();
+ }
+
+ iterator vend() {
+ return &*MutRef().end();
+ }
+
+ reverse_iterator rbegin() {
+ return reverse_iterator(vend());
+ }
+
+ reverse_iterator rend() {
+ return reverse_iterator(begin());
+ }
+
+ using TBase::begin; //!< const_iterator TStringBase::begin() const
+ using TBase::cbegin; //!< const_iterator TStringBase::cbegin() const
+ using TBase::cend; //!< const_iterator TStringBase::cend() const
+ using TBase::crbegin; //!< const_reverse_iterator TStringBase::crbegin() const
+ using TBase::crend; //!< const_reverse_iterator TStringBase::crend() const
+ using TBase::end; //!< const_iterator TStringBase::end() const
+ using TBase::rbegin; //!< const_reverse_iterator TStringBase::rbegin() const
+ using TBase::rend; //!< const_reverse_iterator TStringBase::rend() const
+
+ inline size_t capacity() const noexcept {
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_.capacity();
+#else
+ if (S_->IsNull()) {
+ return 0;
+ }
+
+ return S_->capacity();
+#endif
+ }
+
+ TCharType* Detach() {
+#ifdef TSTRING_IS_STD_STRING
+ return Storage_.data();
+#else
+ if (Y_UNLIKELY(!IsDetached())) {
+ Clone();
+ }
+
+ return (TCharType*)S_->data();
+#endif
+ }
+
+ bool IsDetached() const {
+#ifdef TSTRING_IS_STD_STRING
+ return true;
+#else
+ return 1 == RefCount();
+#endif
+ }
+
+ // ~~~ Size and capacity ~~~
+ TBasicString& resize(size_t n, TCharType c = ' ') { // remove or append
+ MutRef().resize(n, c);
+
+ return *this;
+ }
+
+ // ~~~ Constructor ~~~ : FAMILY0(,TBasicString)
+ TBasicString() noexcept
+#ifndef TSTRING_IS_STD_STRING
+ : S_(Construct())
+#endif
+ {
+ }
+
+ inline explicit TBasicString(::NDetail::TReserveTag rt)
+#ifndef TSTRING_IS_STD_STRING
+ : S_(Construct())
+#endif
+ {
+ reserve(rt.Capacity);
+ }
+
+ inline TBasicString(const TBasicString& s)
+#ifdef TSTRING_IS_STD_STRING
+ : Storage_(s.Storage_)
+#else
+ : S_(s.S_)
+#endif
+ {
+ }
+
+ inline TBasicString(TBasicString&& s) noexcept
+#ifdef TSTRING_IS_STD_STRING
+ : Storage_(std::move(s.Storage_))
+#else
+ : S_(Construct())
+#endif
+ {
+#ifdef TSTRING_IS_STD_STRING
+#else
+ s.swap(*this);
+#endif
+ }
+
+ template <typename T, typename A>
+ explicit inline TBasicString(const std::basic_string<TCharType, T, A>& s)
+ : TBasicString(s.data(), s.size())
+ {
+ }
+
+ template <typename T, typename A>
+ inline TBasicString(std::basic_string<TCharType, T, A>&& s)
+#ifdef TSTRING_IS_STD_STRING
+ : Storage_(std::move(s))
+#else
+ : S_(s.empty() ? Construct() : Construct(std::move(s)))
+#endif
+ {
+ }
+
+ TBasicString(const TBasicString& s, size_t pos, size_t n) Y_NOEXCEPT
+#ifdef TSTRING_IS_STD_STRING
+ : Storage_(s.Storage_, pos, n)
+#else
+ : S_(n ? Construct(s, pos, n) : Construct())
+#endif
+ {
+ }
+
+ TBasicString(const TCharType* pc)
+ : TBasicString(pc, TBase::StrLen(pc))
+ {
+ }
+ // TODO thegeorg@: uncomment and fix clients
+ // TBasicString(std::nullptr_t) = delete;
+
+ TBasicString(const TCharType* pc, size_t n)
+#ifdef TSTRING_IS_STD_STRING
+ : Storage_(pc, n)
+#else
+ : S_(n ? Construct(pc, n) : Construct())
+#endif
+ {
+ }
+ TBasicString(std::nullptr_t, size_t) = delete;
+
+ TBasicString(const TCharType* pc, size_t pos, size_t n)
+ : TBasicString(pc + pos, n)
+ {
+ }
+
+#ifdef TSTRING_IS_STD_STRING
+ explicit TBasicString(TExplicitType<TCharType> c) {
+ Storage_.push_back(c);
+ }
+#else
+ explicit TBasicString(TExplicitType<TCharType> c)
+ : TBasicString(&c.Value(), 1)
+ {
+ }
+ explicit TBasicString(const reference& c)
+ : TBasicString(&c, 1)
+ {
+ }
+#endif
+
+ TBasicString(size_t n, TCharType c)
+#ifdef TSTRING_IS_STD_STRING
+ : Storage_(n, c)
+#else
+ : S_(Construct(n, c))
+#endif
+ {
+ }
+
+ /**
+ * Constructs an uninitialized string of size `uninitialized.Size`. The proper
+ * way to use this ctor is via `TBasicString::Uninitialized` factory function.
+ *
+ * @throw std::length_error
+ */
+ TBasicString(TUninitialized uninitialized) {
+#if !defined(TSTRING_IS_STD_STRING)
+ S_ = Construct();
+#endif
+ ReserveAndResize(uninitialized.Size);
+ }
+
+ TBasicString(const TCharType* b, const TCharType* e)
+ : TBasicString(b, e - b)
+ {
+ }
+
+ explicit TBasicString(const TBasicStringBuf<TCharType, TTraits> s)
+ : TBasicString(s.data(), s.size())
+ {
+ }
+
+ template <typename Traits>
+ explicit inline TBasicString(const std::basic_string_view<TCharType, Traits>& s)
+ : TBasicString(s.data(), s.size())
+ {
+ }
+
+ /**
+ * WARN:
+ * Certain invocations of this method will result in link-time error.
+ * You are free to implement corresponding methods in string.cpp if you need them.
+ */
+ static TBasicString FromAscii(const ::TStringBuf& s) {
+ return TBasicString().AppendAscii(s);
+ }
+
+ static TBasicString FromUtf8(const ::TStringBuf& s) {
+ return TBasicString().AppendUtf8(s);
+ }
+
+ static TBasicString FromUtf16(const ::TWtringBuf& s) {
+ return TBasicString().AppendUtf16(s);
+ }
+
+ static TBasicString Uninitialized(size_t n) {
+ return TBasicString(TUninitialized(n));
+ }
+
+private:
+ template <typename... R>
+ static size_t SumLength(const TBasicStringBuf<TCharType, TTraits> s1, const R&... r) noexcept {
+ return s1.size() + SumLength(r...);
+ }
+
+ template <typename... R>
+ static size_t SumLength(const TCharType /*s1*/, const R&... r) noexcept {
+ return 1 + SumLength(r...);
+ }
+
+ static constexpr size_t SumLength() noexcept {
+ return 0;
+ }
+
+ template <typename... R>
+ static void CopyAll(TCharType* p, const TBasicStringBuf<TCharType, TTraits> s, const R&... r) {
+ TTraits::copy(p, s.data(), s.size());
+ CopyAll(p + s.size(), r...);
+ }
+
+ template <typename... R, class TNextCharType, typename = std::enable_if_t<std::is_same<TCharType, TNextCharType>::value>>
+ static void CopyAll(TCharType* p, const TNextCharType s, const R&... r) {
+ p[0] = s;
+ CopyAll(p + 1, r...);
+ }
+
+ static void CopyAll(TCharType*) noexcept {
+ }
+
+public:
+ inline void clear() noexcept {
+#ifdef TSTRING_IS_STD_STRING
+ Storage_.clear();
+#else
+ if (IsDetached()) {
+ S_->clear();
+
+ return;
+ }
+
+ Construct().Swap(S_);
+#endif
+ }
+
+ template <typename... R>
+ static inline TBasicString Join(const R&... r) {
+ TBasicString s{TUninitialized{SumLength(r...)}};
+
+ TBasicString::CopyAll((TCharType*)s.data(), r...);
+
+ return s;
+ }
+
+ // ~~~ Assignment ~~~ : FAMILY0(TBasicString&, assign);
+ TBasicString& assign(size_t size, TCharType ch) {
+ ReserveAndResize(size);
+ std::fill(begin(), vend(), ch);
+ return *this;
+ }
+
+ TBasicString& assign(const TBasicString& s) {
+ TBasicString(s).swap(*this);
+
+ return *this;
+ }
+
+ TBasicString& assign(const TBasicString& s, size_t pos, size_t n) {
+ return assign(TBasicString(s, pos, n));
+ }
+
+ TBasicString& assign(const TCharType* pc) {
+ return assign(pc, TBase::StrLen(pc));
+ }
+
+ TBasicString& assign(TCharType ch) {
+ return assign(&ch, 1);
+ }
+
+ TBasicString& assign(const TCharType* pc, size_t len) {
+#if defined(address_sanitizer_enabled) || defined(thread_sanitizer_enabled)
+ pc = (const TCharType*)HidePointerOrigin((void*)pc);
+#endif
+ if (IsDetached()) {
+ MutRef().assign(pc, len);
+ } else {
+ TBasicString(pc, len).swap(*this);
+ }
+
+ return *this;
+ }
+
+ TBasicString& assign(const TCharType* first, const TCharType* last) {
+ return assign(first, last - first);
+ }
+
+ TBasicString& assign(const TCharType* pc, size_t pos, size_t n) {
+ return assign(pc + pos, n);
+ }
+
+ TBasicString& assign(const TBasicStringBuf<TCharType, TTraits> s) {
+ return assign(s.data(), s.size());
+ }
+
+ TBasicString& assign(const TBasicStringBuf<TCharType, TTraits> s, size_t spos, size_t sn = TBase::npos) {
+ return assign(s.SubString(spos, sn));
+ }
+
+ inline TBasicString& AssignNoAlias(const TCharType* pc, size_t len) {
+ return assign(pc, len);
+ }
+
+ inline TBasicString& AssignNoAlias(const TCharType* b, const TCharType* e) {
+ return AssignNoAlias(b, e - b);
+ }
+
+ TBasicString& AssignNoAlias(const TBasicStringBuf<TCharType, TTraits> s) {
+ return AssignNoAlias(s.data(), s.size());
+ }
+
+ TBasicString& AssignNoAlias(const TBasicStringBuf<TCharType, TTraits> s, size_t spos, size_t sn = TBase::npos) {
+ return AssignNoAlias(s.SubString(spos, sn));
+ }
+
+ /**
+ * WARN:
+ * Certain invocations of this method will result in link-time error.
+ * You are free to implement corresponding methods in string.cpp if you need them.
+ */
+ auto AssignAscii(const ::TStringBuf& s) {
+ clear();
+ return AppendAscii(s);
+ }
+
+ auto AssignUtf8(const ::TStringBuf& s) {
+ clear();
+ return AppendUtf8(s);
+ }
+
+ auto AssignUtf16(const ::TWtringBuf& s) {
+ clear();
+ return AppendUtf16(s);
+ }
+
+ TBasicString& operator=(const TBasicString& s) {
+ return assign(s);
+ }
+
+ TBasicString& operator=(TBasicString&& s) noexcept {
+ swap(s);
+ return *this;
+ }
+
+ template <typename T, typename A>
+ TBasicString& operator=(std::basic_string<TCharType, T, A>&& s) noexcept {
+ TBasicString(std::move(s)).swap(*this);
+
+ return *this;
+ }
+
+ TBasicString& operator=(const TBasicStringBuf<TCharType, TTraits> s) {
+ return assign(s);
+ }
+
+ TBasicString& operator=(std::initializer_list<TCharType> il) {
+ return assign(il.begin(), il.end());
+ }
+
+ TBasicString& operator=(const TCharType* s) {
+ return assign(s);
+ }
+ TBasicString& operator=(std::nullptr_t) = delete;
+
+ TBasicString& operator=(TExplicitType<TCharType> ch) {
+ return assign(ch);
+ }
+
+ inline void reserve(size_t len) {
+ MutRef().reserve(len);
+ }
+
+ // ~~~ Appending ~~~ : FAMILY0(TBasicString&, append);
+ inline TBasicString& append(size_t count, TCharType ch) {
+ MutRef().append(count, ch);
+
+ return *this;
+ }
+
+ inline TBasicString& append(const TBasicString& s) {
+ MutRef().append(s.ConstRef());
+
+ return *this;
+ }
+
+ inline TBasicString& append(const TBasicString& s, size_t pos, size_t n) {
+ MutRef().append(s.ConstRef(), pos, n);
+
+ return *this;
+ }
+
+ inline TBasicString& append(const TCharType* pc) Y_NOEXCEPT {
+ MutRef().append(pc);
+
+ return *this;
+ }
+
+ inline TBasicString& append(TCharType c) {
+ MutRef().push_back(c);
+
+ return *this;
+ }
+
+ inline TBasicString& append(const TCharType* first, const TCharType* last) {
+ MutRef().append(first, last);
+
+ return *this;
+ }
+
+ inline TBasicString& append(const TCharType* pc, size_t len) {
+ MutRef().append(pc, len);
+
+ return *this;
+ }
+
+ inline void ReserveAndResize(size_t len) {
+ ::ResizeUninitialized(MutRef(), len);
+ }
+
+ TBasicString& AppendNoAlias(const TCharType* pc, size_t len) {
+ if (len) {
+ auto s = this->size();
+
+ ReserveAndResize(s + len);
+ memcpy(&*(begin() + s), pc, len * sizeof(*pc));
+ }
+
+ return *this;
+ }
+
+ TBasicString& AppendNoAlias(const TBasicStringBuf<TCharType, TTraits> s) {
+ return AppendNoAlias(s.data(), s.size());
+ }
+
+ TBasicString& AppendNoAlias(const TBasicStringBuf<TCharType, TTraits> s, size_t spos, size_t sn = TBase::npos) {
+ return AppendNoAlias(s.SubString(spos, sn));
+ }
+
+ TBasicString& append(const TBasicStringBuf<TCharType, TTraits> s) {
+ return append(s.data(), s.size());
+ }
+
+ TBasicString& append(const TBasicStringBuf<TCharType, TTraits> s, size_t spos, size_t sn = TBase::npos) {
+ return append(s.SubString(spos, sn));
+ }
+
+ TBasicString& append(const TCharType* pc, size_t pos, size_t n, size_t pc_len = TBase::npos) {
+ return append(pc + pos, Min(n, pc_len - pos));
+ }
+
+ /**
+ * WARN:
+ * Certain invocations of this method will result in link-time error.
+ * You are free to implement corresponding methods in string.cpp if you need them.
+ */
+ TBasicString& AppendAscii(const ::TStringBuf& s);
+
+ TBasicString& AppendUtf8(const ::TStringBuf& s);
+
+ TBasicString& AppendUtf16(const ::TWtringBuf& s);
+
+ inline void push_back(TCharType c) {
+ // TODO
+ append(c);
+ }
+
+ template <class T>
+ TBasicString& operator+=(const T& s) {
+ return append(s);
+ }
+
+ template <class T>
+ friend TBasicString operator*(const TBasicString& s, T count) {
+ TBasicString result;
+
+ for (T i = 0; i < count; ++i) {
+ result += s;
+ }
+
+ return result;
+ }
+
+ template <class T>
+ TBasicString& operator*=(T count) {
+ TBasicString temp;
+
+ for (T i = 0; i < count; ++i) {
+ temp += *this;
+ }
+
+ swap(temp);
+
+ return *this;
+ }
+
+ operator const TStringType&() const noexcept {
+ return this->ConstRef();
+ }
+
+ operator TStringType&() {
+ return this->MutRef();
+ }
+
+ /*
+ * Following overloads of "operator+" aim to choose the cheapest implementation depending on
+ * summand types: lvalues, detached rvalues, shared rvalues.
+ *
+ * General idea is to use the detached-rvalue argument (left of right) to store the result
+ * wherever possible. If a buffer in rvalue is large enough this saves a re-allocation. If
+ * both arguments are rvalues we check which one is detached. If both of them are detached then
+ * the left argument is obviously preferrable because you won't need to shift the data.
+ *
+ * If an rvalue is shared then it's basically the same as lvalue because you cannot use its
+ * buffer to store the sum. However, we rely on the fact that append() and prepend() are already
+ * optimized for the shared case and detach the string into the buffer large enough to store
+ * the sum (compared to the detach+reallocation). This way, if we have only one rvalue argument
+ * (left or right) then we simply append/prepend into it, without checking if it's detached or
+ * not. This will be checked inside ReserveAndResize anyway.
+ *
+ * If both arguments cannot be used to store the sum (e.g. two lvalues) then we fall back to the
+ * Join function that constructs a resulting string in the new buffer with the minimum overhead:
+ * malloc + memcpy + memcpy.
+ */
+
+ friend TBasicString operator+(TBasicString&& s1, const TBasicString& s2) Y_WARN_UNUSED_RESULT {
+ s1 += s2;
+ return std::move(s1);
+ }
+
+ friend TBasicString operator+(const TBasicString& s1, TBasicString&& s2) Y_WARN_UNUSED_RESULT {
+ s2.prepend(s1);
+ return std::move(s2);
+ }
+
+ friend TBasicString operator+(TBasicString&& s1, TBasicString&& s2) Y_WARN_UNUSED_RESULT {
+#if 0 && !defined(TSTRING_IS_STD_STRING)
+ if (!s1.IsDetached() && s2.IsDetached()) {
+ s2.prepend(s1);
+ return std::move(s2);
+ }
+#endif
+ s1 += s2;
+ return std::move(s1);
+ }
+
+ friend TBasicString operator+(TBasicString&& s1, const TBasicStringBuf<TCharType, TTraits> s2) Y_WARN_UNUSED_RESULT {
+ s1 += s2;
+ return std::move(s1);
+ }
+
+ friend TBasicString operator+(TBasicString&& s1, const TCharType* s2) Y_WARN_UNUSED_RESULT {
+ s1 += s2;
+ return std::move(s1);
+ }
+
+ friend TBasicString operator+(TBasicString&& s1, TCharType s2) Y_WARN_UNUSED_RESULT {
+ s1 += s2;
+ return std::move(s1);
+ }
+
+ friend TBasicString operator+(TExplicitType<TCharType> ch, const TBasicString& s) Y_WARN_UNUSED_RESULT {
+ return Join(TCharType(ch), s);
+ }
+
+ friend TBasicString operator+(const TBasicString& s1, const TBasicString& s2) Y_WARN_UNUSED_RESULT {
+ return Join(s1, s2);
+ }
+
+ friend TBasicString operator+(const TBasicString& s1, const TBasicStringBuf<TCharType, TTraits> s2) Y_WARN_UNUSED_RESULT {
+ return Join(s1, s2);
+ }
+
+ friend TBasicString operator+(const TBasicString& s1, const TCharType* s2) Y_WARN_UNUSED_RESULT {
+ return Join(s1, s2);
+ }
+
+ friend TBasicString operator+(const TBasicString& s1, TCharType s2) Y_WARN_UNUSED_RESULT {
+ return Join(s1, TBasicStringBuf<TCharType, TTraits>(&s2, 1));
+ }
+
+ friend TBasicString operator+(const TCharType* s1, TBasicString&& s2) Y_WARN_UNUSED_RESULT {
+ s2.prepend(s1);
+ return std::move(s2);
+ }
+
+ friend TBasicString operator+(const TBasicStringBuf<TCharType, TTraits> s1, TBasicString&& s2) Y_WARN_UNUSED_RESULT {
+ s2.prepend(s1);
+ return std::move(s2);
+ }
+
+ friend TBasicString operator+(const TBasicStringBuf<TCharType, TTraits> s1, const TBasicString& s2) Y_WARN_UNUSED_RESULT {
+ return Join(s1, s2);
+ }
+
+ friend TBasicString operator+(const TCharType* s1, const TBasicString& s2) Y_WARN_UNUSED_RESULT {
+ return Join(s1, s2);
+ }
+
+ friend TBasicString operator+(std::basic_string<TCharType, TTraits> l, TBasicString r) {
+ return l + r.ConstRef();
+ }
+
+ friend TBasicString operator+(TBasicString l, std::basic_string<TCharType, TTraits> r) {
+ return l.ConstRef() + r;
+ }
+
+ // ~~~ Prepending ~~~ : FAMILY0(TBasicString&, prepend);
+ TBasicString& prepend(const TBasicString& s) {
+ MutRef().insert(0, s.ConstRef());
+
+ return *this;
+ }
+
+ TBasicString& prepend(const TBasicString& s, size_t pos, size_t n) {
+ MutRef().insert(0, s.ConstRef(), pos, n);
+
+ return *this;
+ }
+
+ TBasicString& prepend(const TCharType* pc) {
+ MutRef().insert(0, pc);
+
+ return *this;
+ }
+
+ TBasicString& prepend(size_t n, TCharType c) {
+ MutRef().insert(size_t(0), n, c);
+
+ return *this;
+ }
+
+ TBasicString& prepend(TCharType c) {
+ MutRef().insert(size_t(0), 1, c);
+
+ return *this;
+ }
+
+ TBasicString& prepend(const TBasicStringBuf<TCharType, TTraits> s, size_t spos = 0, size_t sn = TBase::npos) {
+ return insert(0, s, spos, sn);
+ }
+
+ // ~~~ Insertion ~~~ : FAMILY1(TBasicString&, insert, size_t pos);
+ TBasicString& insert(size_t pos, const TBasicString& s) {
+ MutRef().insert(pos, s.ConstRef());
+
+ return *this;
+ }
+
+ TBasicString& insert(size_t pos, const TBasicString& s, size_t pos1, size_t n1) {
+ MutRef().insert(pos, s.ConstRef(), pos1, n1);
+
+ return *this;
+ }
+
+ TBasicString& insert(size_t pos, const TCharType* pc) {
+ MutRef().insert(pos, pc);
+
+ return *this;
+ }
+
+ TBasicString& insert(size_t pos, const TCharType* pc, size_t len) {
+ MutRef().insert(pos, pc, len);
+
+ return *this;
+ }
+
+ TBasicString& insert(const_iterator pos, const_iterator b, const_iterator e) {
+#ifdef TSTRING_IS_STD_STRING
+ Storage_.insert(Storage_.begin() + this->off(pos), b, e);
+
+ return *this;
+#else
+ return insert(this->off(pos), b, e - b);
+#endif
+ }
+
+ TBasicString& insert(size_t pos, size_t n, TCharType c) {
+ MutRef().insert(pos, n, c);
+
+ return *this;
+ }
+
+ TBasicString& insert(const_iterator pos, size_t len, TCharType ch) {
+ return this->insert(this->off(pos), len, ch);
+ }
+
+ TBasicString& insert(const_iterator pos, TCharType ch) {
+ return this->insert(pos, 1, ch);
+ }
+
+ TBasicString& insert(size_t pos, const TBasicStringBuf<TCharType, TTraits> s, size_t spos = 0, size_t sn = TBase::npos) {
+ MutRef().insert(pos, s, spos, sn);
+
+ return *this;
+ }
+
+ // ~~~ Removing ~~~
+ TBasicString& remove(size_t pos, size_t n) Y_NOEXCEPT {
+ if (pos < length()) {
+ MutRef().erase(pos, n);
+ }
+
+ return *this;
+ }
+
+ TBasicString& remove(size_t pos = 0) Y_NOEXCEPT {
+ if (pos < length()) {
+ MutRef().erase(pos);
+ }
+
+ return *this;
+ }
+
+ TBasicString& erase(size_t pos = 0, size_t n = TBase::npos) Y_NOEXCEPT {
+ MutRef().erase(pos, n);
+
+ return *this;
+ }
+
+ TBasicString& erase(const_iterator b, const_iterator e) Y_NOEXCEPT {
+ return erase(this->off(b), e - b);
+ }
+
+ TBasicString& erase(const_iterator i) Y_NOEXCEPT {
+ return erase(i, i + 1);
+ }
+
+ TBasicString& pop_back() Y_NOEXCEPT {
+ Y_ASSERT(!this->empty());
+
+ MutRef().pop_back();
+
+ return *this;
+ }
+
+ // ~~~ replacement ~~~ : FAMILY2(TBasicString&, replace, size_t pos, size_t n);
+ TBasicString& replace(size_t pos, size_t n, const TBasicString& s) Y_NOEXCEPT {
+ MutRef().replace(pos, n, s.ConstRef());
+
+ return *this;
+ }
+
+ TBasicString& replace(size_t pos, size_t n, const TBasicString& s, size_t pos1, size_t n1) Y_NOEXCEPT {
+ MutRef().replace(pos, n, s.ConstRef(), pos1, n1);
+
+ return *this;
+ }
+
+ TBasicString& replace(size_t pos, size_t n, const TCharType* pc) Y_NOEXCEPT {
+ MutRef().replace(pos, n, pc);
+
+ return *this;
+ }
+
+ TBasicString& replace(size_t pos, size_t n, const TCharType* s, size_t len) Y_NOEXCEPT {
+ MutRef().replace(pos, n, s, len);
+
+ return *this;
+ }
+
+ TBasicString& replace(size_t pos, size_t n, const TCharType* s, size_t spos, size_t sn) Y_NOEXCEPT {
+ MutRef().replace(pos, n, s + spos, sn - spos);
+
+ return *this;
+ }
+
+ TBasicString& replace(size_t pos, size_t n1, size_t n2, TCharType c) Y_NOEXCEPT {
+ MutRef().replace(pos, n1, n2, c);
+
+ return *this;
+ }
+
+ TBasicString& replace(size_t pos, size_t n, const TBasicStringBuf<TCharType, TTraits> s, size_t spos = 0, size_t sn = TBase::npos) Y_NOEXCEPT {
+ MutRef().replace(pos, n, s, spos, sn);
+
+ return *this;
+ }
+
+ void swap(TBasicString& s) noexcept {
+#ifdef TSTRING_IS_STD_STRING
+ std::swap(Storage_, s.Storage_);
+#else
+ S_.Swap(s.S_);
+#endif
+ }
+
+ /**
+ * @returns String suitable for debug printing (like Python's `repr()`).
+ * Format of the string is unspecified and may be changed over time.
+ */
+ TBasicString Quote() const {
+ extern TBasicString EscapeC(const TBasicString&);
+
+ return TBasicString() + '"' + EscapeC(*this) + '"';
+ }
+
+ /**
+ * Modifies the case of the string, depending on the operation.
+ * @return false if no changes have been made.
+ *
+ * @warning when the value_type is char, these methods will not work with non-ASCII letters.
+ */
+ bool to_lower(size_t pos = 0, size_t n = TBase::npos);
+ bool to_upper(size_t pos = 0, size_t n = TBase::npos);
+ bool to_title(size_t pos = 0, size_t n = TBase::npos);
+
+public:
+ /**
+ * Modifies the substring of length `n` starting from `pos`, applying `f` to each position and symbol.
+ *
+ * @return false if no changes have been made.
+ */
+ template <typename T>
+ bool Transform(T&& f, size_t pos = 0, size_t n = TBase::npos) {
+ size_t len = length();
+
+ if (pos > len) {
+ pos = len;
+ }
+
+ if (n > len - pos) {
+ n = len - pos;
+ }
+
+ bool changed = false;
+
+ for (size_t i = pos; i != pos + n; ++i) {
+#ifdef TSTRING_IS_STD_STRING
+ auto c = f(i, Storage_[i]);
+
+ if (c != Storage_[i]) {
+ changed = true;
+
+ Storage_[i] = c;
+ }
+#else
+ auto c = f(i, data()[i]);
+ if (c != data()[i]) {
+ if (!changed) {
+ Detach();
+ changed = true;
+ }
+
+ begin()[i] = c;
+ }
+#endif
+ }
+
+ return changed;
+ }
+};
+
+std::ostream& operator<<(std::ostream&, const TString&);
+std::istream& operator>>(std::istream&, TString&);
+
+template <typename TCharType, typename TTraits>
+TBasicString<TCharType> to_lower(const TBasicString<TCharType, TTraits>& s) {
+ TBasicString<TCharType> ret(s);
+ ret.to_lower();
+ return ret;
+}
+
+template <typename TCharType, typename TTraits>
+TBasicString<TCharType> to_upper(const TBasicString<TCharType, TTraits>& s) {
+ TBasicString<TCharType> ret(s);
+ ret.to_upper();
+ return ret;
+}
+
+template <typename TCharType, typename TTraits>
+TBasicString<TCharType> to_title(const TBasicString<TCharType, TTraits>& s) {
+ TBasicString<TCharType> ret(s);
+ ret.to_title();
+ return ret;
+}
+
+namespace std {
+ template <>
+ struct hash<TString> {
+ using argument_type = TString;
+ using result_type = size_t;
+ inline result_type operator()(argument_type const& s) const noexcept {
+ return NHashPrivate::ComputeStringHash(s.data(), s.size());
+ }
+ };
+}
+
+#undef Y_NOEXCEPT
+
+template <class S>
+inline S LegacySubstr(const S& s, size_t pos, size_t n = S::npos) {
+ size_t len = s.length();
+
+ pos = Min(pos, len);
+ n = Min(n, len - pos);
+
+ return S(s, pos, n);
+}
+
+template <typename S, typename... Args>
+inline S&& LegacyReplace(S&& s, size_t pos, Args&&... args) {
+ if (pos <= s.length()) {
+ s.replace(pos, std::forward<Args>(args)...);
+ }
+
+ return s;
+}
+
+template <typename S, typename... Args>
+inline S&& LegacyErase(S&& s, size_t pos, Args&&... args) {
+ if (pos <= s.length()) {
+ s.erase(pos, std::forward<Args>(args)...);
+ }
+
+ return s;
+}
+
+inline const char* LegacyStr(const char* s) noexcept {
+ return s ? s : "";
+}
+
+// interop
+template <class TCharType, class TTraits>
+auto& MutRef(TBasicString<TCharType, TTraits>& s) {
+ return s.MutRef();
+}
+
+template <class TCharType, class TTraits>
+const auto& ConstRef(const TBasicString<TCharType, TTraits>& s) noexcept {
+ return s.ConstRef();
+}
+
+template <class TCharType, class TCharTraits, class TAllocator>
+auto& MutRef(std::basic_string<TCharType, TCharTraits, TAllocator>& s) noexcept {
+ return s;
+}
+
+template <class TCharType, class TCharTraits, class TAllocator>
+const auto& ConstRef(const std::basic_string<TCharType, TCharTraits, TAllocator>& s) noexcept {
+ return s;
+}
+
+template <class TCharType, class TTraits>
+void ResizeUninitialized(TBasicString<TCharType, TTraits>& s, size_t len) {
+ s.ReserveAndResize(len);
+}
diff --git a/util/generic/string.pxd b/util/generic/string.pxd
new file mode 100644
index 0000000000..c25f7392a1
--- /dev/null
+++ b/util/generic/string.pxd
@@ -0,0 +1,118 @@
+from libcpp.string cimport string as _std_string
+
+cdef extern from "<util/generic/strbuf.h>" nogil:
+
+ cdef cppclass TStringBuf:
+ TStringBuf() except +
+ TStringBuf(const char*) except +
+ TStringBuf(const char*, size_t) except +
+ const char* data()
+ char* Data()
+ size_t size()
+ size_t Size()
+
+
+cdef extern from "<util/generic/string.h>" nogil:
+
+ size_t npos "TString::npos"
+
+ # Inheritance is bogus, but it's safe to assume TString is-a TStringBuf via implicit cast
+ cdef cppclass TString(TStringBuf):
+ TString() except +
+ TString(TString&) except +
+ TString(_std_string&) except +
+ TString(TString&, size_t, size_t) except +
+ TString(char*) except +
+ TString(char*, size_t) except +
+ TString(char*, size_t, size_t) except +
+ # as a TString formed by a repetition of character c, n times.
+ TString(size_t, char) except +
+ TString(char*, char*) except +
+ TString(TStringBuf&) except +
+ TString(TStringBuf&, TStringBuf&) except +
+ TString(TStringBuf&, TStringBuf&, TStringBuf&) except +
+
+ const char* c_str()
+ size_t max_size()
+ size_t length()
+ void resize(size_t) except +
+ void resize(size_t, char c) except +
+ size_t capacity()
+ void reserve(size_t) except +
+ void clear() except +
+ bint empty()
+
+ char& at(size_t)
+ char& operator[](size_t)
+ int compare(TStringBuf&)
+
+ TString& append(TStringBuf&) except +
+ TString& append(TStringBuf&, size_t, size_t) except +
+ TString& append(char *) except +
+ TString& append(char *, size_t) except +
+ TString& append(size_t, char) except +
+
+ void push_back(char c) except +
+
+ TString& assign(TStringBuf&) except +
+ TString& assign(TStringBuf&, size_t, size_t) except +
+ TString& assign(char *) except +
+ TString& assign(char *, size_t) except +
+
+ TString& insert(size_t, TString&) except +
+ TString& insert(size_t, TString&, size_t, size_t) except +
+ TString& insert(size_t, char* s) except +
+ TString& insert(size_t, char* s, size_t) except +
+ TString& insert(size_t, size_t, char c) except +
+
+ size_t copy(char *, size_t) except +
+ size_t copy(char *, size_t, size_t) except +
+
+ size_t find(TStringBuf&)
+ size_t find(TStringBuf&, size_t pos)
+ size_t find(char)
+ size_t find(char, size_t pos)
+
+ size_t rfind(TStringBuf&)
+ size_t rfind(TStringBuf&, size_t pos)
+ size_t rfind(char)
+ size_t rfind(char, size_t pos)
+
+ size_t find_first_of(char c)
+ size_t find_first_of(char c, size_t pos)
+ size_t find_first_of(TStringBuf& set)
+ size_t find_first_of(TStringBuf& set, size_t pos)
+
+ size_t find_first_not_of(char c)
+ size_t find_first_not_of(char c, size_t pos)
+ size_t find_first_not_of(TStringBuf& set)
+ size_t find_first_not_of(TStringBuf& set, size_t pos)
+
+ size_t find_last_of(char c)
+ size_t find_last_of(char c, size_t pos)
+ size_t find_last_of(TStringBuf& set)
+ size_t find_last_of(TStringBuf& set, size_t pos)
+
+ TString substr(size_t pos) except +
+ TString substr(size_t pos, size_t n) except +
+
+ TString operator+(TStringBuf& rhs) except +
+ TString operator+(char* rhs) except +
+
+ bint operator==(TStringBuf&)
+ bint operator==(char*)
+
+ bint operator!=(TStringBuf&)
+ bint operator!=(char*)
+
+ bint operator<(TStringBuf&)
+ bint operator<(char*)
+
+ bint operator>(TStringBuf&)
+ bint operator>(char*)
+
+ bint operator<=(TStringBuf&)
+ bint operator<=(char*)
+
+ bint operator>=(TStringBuf&)
+ bint operator>=(char*)
diff --git a/util/generic/string_hash.h b/util/generic/string_hash.h
new file mode 100644
index 0000000000..b949c7a2d9
--- /dev/null
+++ b/util/generic/string_hash.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <cstddef>
+
+// reduce code bloat and cycled includes, declare functions here
+#if defined(_64_) && !defined(NO_CITYHASH)
+ui64 CityHash64(const char* buf, size_t len) noexcept;
+#else
+size_t MurmurHashSizeT(const char* buf, size_t len) noexcept;
+#endif
+
+namespace NHashPrivate {
+ template <typename C>
+ size_t ComputeStringHash(const C* ptr, size_t size) noexcept {
+#if defined(_64_) && !defined(NO_CITYHASH)
+ return CityHash64((const char*)ptr, size * sizeof(C));
+#else
+ return MurmurHashSizeT((const char*)ptr, size * sizeof(C));
+#endif
+ }
+}
diff --git a/util/generic/string_transparent_hash_ut.cpp b/util/generic/string_transparent_hash_ut.cpp
new file mode 100644
index 0000000000..b87fa2843e
--- /dev/null
+++ b/util/generic/string_transparent_hash_ut.cpp
@@ -0,0 +1,19 @@
+#include "string.h"
+#include "vector.h"
+#include "strbuf.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+#include <library/cpp/containers/absl_flat_hash/flat_hash_set.h>
+
+#include <util/str_stl.h>
+
+Y_UNIT_TEST_SUITE(StringHashFunctorTests) {
+ Y_UNIT_TEST(TestTransparencyWithUnorderedSet) {
+ // Using Abseil hash set because `std::unordered_set` is transparent only from C++20 (while
+ // we stuck with C++17 right now).
+ absl::flat_hash_set<TString, THash<TString>, TEqualTo<TString>> s = {"foo"};
+ // If either `THash` or `TEqualTo` is not transparent compilation will fail.
+ UNIT_ASSERT_UNEQUAL(s.find(TStringBuf("foo")), s.end());
+ UNIT_ASSERT_EQUAL(s.find(TStringBuf("bar")), s.end());
+ }
+}
diff --git a/util/generic/string_ut.cpp b/util/generic/string_ut.cpp
new file mode 100644
index 0000000000..ac82e9091d
--- /dev/null
+++ b/util/generic/string_ut.cpp
@@ -0,0 +1,1270 @@
+#include "deque.h"
+#include "strbuf.h"
+#include "string_ut.h"
+#include "vector.h"
+#include "yexception.h"
+
+#include <util/charset/wide.h>
+#include <util/str_stl.h>
+#include <util/stream/output.h>
+#include <util/string/subst.h>
+
+#include <string>
+#include <sstream>
+#include <algorithm>
+#include <stdexcept>
+
+#ifdef TSTRING_IS_STD_STRING
+static_assert(sizeof(TString) == sizeof(std::string), "expect sizeof(TString) == sizeof(std::string)");
+#else
+static_assert(sizeof(TString) == sizeof(const char*), "expect sizeof(TString) == sizeof(const char*)");
+#endif
+
+class TStringTestZero: public TTestBase {
+ UNIT_TEST_SUITE(TStringTestZero);
+ UNIT_TEST(TestZero);
+ UNIT_TEST_SUITE_END();
+
+public:
+ void TestZero() {
+ const char data[] = "abc\0def\0";
+ TString s(data, sizeof(data));
+ UNIT_ASSERT(s.size() == sizeof(data));
+ UNIT_ASSERT(s.StartsWith(s));
+ UNIT_ASSERT(s.EndsWith(s));
+ UNIT_ASSERT(s.Contains('\0'));
+
+ const char raw_def[] = "def";
+ const char raw_zero[] = "\0";
+ TString def(raw_def, sizeof(raw_def) - 1);
+ TString zero(raw_zero, sizeof(raw_zero) - 1);
+ UNIT_ASSERT_EQUAL(4, s.find(raw_def));
+ UNIT_ASSERT_EQUAL(4, s.find(def));
+ UNIT_ASSERT_EQUAL(4, s.find_first_of(raw_def));
+ UNIT_ASSERT_EQUAL(3, s.find_first_of(zero));
+ UNIT_ASSERT_EQUAL(7, s.find_first_not_of(def, 4));
+
+ const char nonSubstring[] = "def\0ghi";
+ UNIT_ASSERT_EQUAL(TString::npos, s.find(TString(nonSubstring, sizeof(nonSubstring))));
+
+ TString copy = s;
+ copy.replace(copy.size() - 1, 1, "z");
+ UNIT_ASSERT(s != copy);
+ copy.replace(copy.size() - 1, 1, "\0", 0, 1);
+ UNIT_ASSERT(s == copy);
+
+ TString prefix(data, 5);
+ UNIT_ASSERT(s.StartsWith(prefix));
+ UNIT_ASSERT(s != prefix);
+ UNIT_ASSERT(s > prefix);
+ UNIT_ASSERT(s > s.data());
+ UNIT_ASSERT(s == TString(s.data(), s.size()));
+ UNIT_ASSERT(data < s);
+
+ s.remove(5);
+ UNIT_ASSERT(s == prefix);
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TStringTestZero);
+
+template <typename TStringType, typename TTestData>
+class TStringStdTestImpl {
+ using TChar = typename TStringType::char_type;
+ using TTraits = typename TStringType::traits_type;
+ using TView = std::basic_string_view<TChar, TTraits>;
+
+ TTestData Data_;
+
+protected:
+ void Constructor() {
+ // @todo use UNIT_TEST_EXCEPTION
+ try {
+ TStringType s((size_t)-1, *Data_.a());
+ UNIT_ASSERT(false);
+ } catch (const std::length_error&) {
+ UNIT_ASSERT(true);
+ } catch (...) {
+ //Expected exception is length_error:
+ UNIT_ASSERT(false);
+ }
+ }
+
+ void reserve() {
+#if 0
+ TStringType s;
+ // @todo use UNIT_TEST_EXCEPTION
+ try {
+ s.reserve(s.max_size() + 1);
+ UNIT_ASSERT(false);
+ } catch (const std::length_error&) {
+ UNIT_ASSERT(true);
+ } catch (...) {
+ //Expected exception is length_error:
+ UNIT_ASSERT(false);
+ }
+
+ // Non-shared behaviour - never shrink
+
+ s.reserve(256);
+ #ifndef TSTRING_IS_STD_STRING
+ const auto* data = s.data();
+
+ UNIT_ASSERT(s.capacity() >= 256);
+
+ s.reserve(128);
+
+ UNIT_ASSERT(s.capacity() >= 256 && s.data() == data);
+ #endif
+
+ s.resize(64, 'x');
+ s.reserve(10);
+
+ #ifdef TSTRING_IS_STD_STRING
+ UNIT_ASSERT(s.capacity() >= 64);
+ #else
+ UNIT_ASSERT(s.capacity() >= 256 && s.data() == data);
+ #endif
+
+ #ifndef TSTRING_IS_STD_STRING
+ // Shared behaviour - always reallocate, just as much as requisted
+
+ TStringType holder = s;
+
+ UNIT_ASSERT(s.capacity() >= 256);
+
+ s.reserve(128);
+
+ UNIT_ASSERT(s.capacity() >= 128 && s.capacity() < 256 && s.data() != data);
+ UNIT_ASSERT(s.IsDetached());
+
+ s.resize(64, 'x');
+ data = s.data();
+ holder = s;
+
+ s.reserve(10);
+
+ UNIT_ASSERT(s.capacity() >= 64 && s.capacity() < 128 && s.data() != data);
+ UNIT_ASSERT(s.IsDetached());
+ #endif
+#endif
+ }
+
+ void short_string() {
+ TStringType const ref_short_str1(Data_.str1()), ref_short_str2(Data_.str2());
+ TStringType short_str1(ref_short_str1), short_str2(ref_short_str2);
+ TStringType const ref_long_str1(Data_.str__________________________________________________1());
+ TStringType const ref_long_str2(Data_.str__________________________________________________2());
+ TStringType long_str1(ref_long_str1), long_str2(ref_long_str2);
+
+ UNIT_ASSERT(short_str1 == ref_short_str1);
+ UNIT_ASSERT(long_str1 == ref_long_str1);
+
+ {
+ TStringType str1(short_str1);
+ str1 = long_str1;
+ UNIT_ASSERT(str1 == ref_long_str1);
+ }
+
+ {
+ TStringType str1(long_str1);
+ str1 = short_str1;
+ UNIT_ASSERT(str1 == ref_short_str1);
+ }
+
+ {
+ short_str1.swap(short_str2);
+ UNIT_ASSERT((short_str1 == ref_short_str2) && (short_str2 == ref_short_str1));
+ short_str1.swap(short_str2);
+ }
+
+ {
+ long_str1.swap(long_str2);
+ UNIT_ASSERT((long_str1 == ref_long_str2) && (long_str2 == ref_long_str1));
+ long_str1.swap(long_str2);
+ }
+
+ {
+ short_str1.swap(long_str1);
+ UNIT_ASSERT((short_str1 == ref_long_str1) && (long_str1 == ref_short_str1));
+ short_str1.swap(long_str1);
+ }
+
+ {
+ long_str1.swap(short_str1);
+ UNIT_ASSERT((short_str1 == ref_long_str1) && (long_str1 == ref_short_str1));
+ long_str1.swap(short_str1);
+ }
+
+ {
+ //This is to test move constructor
+ TVector<TStringType> str_vect;
+
+ str_vect.push_back(short_str1);
+ str_vect.push_back(long_str1);
+ str_vect.push_back(short_str2);
+ str_vect.push_back(long_str2);
+
+ UNIT_ASSERT(str_vect[0] == ref_short_str1);
+ UNIT_ASSERT(str_vect[1] == ref_long_str1);
+ UNIT_ASSERT(str_vect[2] == ref_short_str2);
+ UNIT_ASSERT(str_vect[3] == ref_long_str2);
+ }
+ }
+
+ void erase() {
+ TChar const* c_str = Data_.Hello_World();
+ TStringType str(c_str);
+ UNIT_ASSERT(str == c_str);
+
+ str.erase(str.begin() + 1, str.end() - 1); // Erase all but first and last.
+
+ size_t i;
+ for (i = 0; i < str.size(); ++i) {
+ switch (i) {
+ case 0:
+ UNIT_ASSERT(str[i] == *Data_.H());
+ break;
+
+ case 1:
+ UNIT_ASSERT(str[i] == *Data_.d());
+ break;
+
+ default:
+ UNIT_ASSERT(false);
+ }
+ }
+
+ str.insert(1, c_str);
+ str.erase(str.begin()); // Erase first element.
+ str.erase(str.end() - 1); // Erase last element.
+ UNIT_ASSERT(str == c_str);
+ str.clear(); // Erase all.
+ UNIT_ASSERT(str.empty());
+
+ str = c_str;
+ UNIT_ASSERT(str == c_str);
+
+ str.erase(1, str.size() - 1); // Erase all but first and last.
+ for (i = 0; i < str.size(); i++) {
+ switch (i) {
+ case 0:
+ UNIT_ASSERT(str[i] == *Data_.H());
+ break;
+
+ case 1:
+ UNIT_ASSERT(str[i] == *Data_.d());
+ break;
+
+ default:
+ UNIT_ASSERT(false);
+ }
+ }
+
+ str.erase(1);
+ UNIT_ASSERT(str == Data_.H());
+ }
+
+ void data() {
+ TStringType xx;
+
+ // ISO-IEC-14882:1998(E), 21.3.6, paragraph 3
+ UNIT_ASSERT(xx.data() != nullptr);
+ }
+
+ void c_str() {
+ TStringType low(Data_._2004_01_01());
+ TStringType xx;
+ TStringType yy;
+
+ // ISO-IEC-14882:1998(E), 21.3.6, paragraph 1
+ UNIT_ASSERT(*(yy.c_str()) == 0);
+
+ // Blocks A and B should follow each other.
+ // Block A:
+ xx = Data_._123456();
+ xx += low;
+ UNIT_ASSERT(xx.c_str() == TView(Data_._1234562004_01_01()));
+ // End of block A
+
+ // Block B:
+ xx = Data_._1234();
+ xx += Data_._5();
+ UNIT_ASSERT(xx.c_str() == TView(Data_._12345()));
+ // End of block B
+ }
+
+ void null_char_of_empty() {
+ const TStringType s;
+
+ UNIT_ASSERT(s[s.size()] == 0);
+ }
+
+ void null_char() {
+ // ISO/IEC 14882:1998(E), ISO/IEC 14882:2003(E), 21.3.4 ('... the const version')
+ const TStringType s(Data_._123456());
+
+ UNIT_ASSERT(s[s.size()] == 0);
+ }
+
+ // Allowed since C++17, see http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2475
+ void null_char_assignment_to_subscript_of_empty() {
+ TStringType s;
+
+#ifdef TSTRING_IS_STD_STRING
+ using reference = volatile typename TStringType::value_type&;
+#else
+ using reference = typename TStringType::reference;
+#endif
+ reference trailing_zero = s[s.size()];
+ trailing_zero = 0;
+ UNIT_ASSERT(trailing_zero == 0);
+ }
+
+ // Allowed since C++17, see http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2475
+ void null_char_assignment_to_subscript_of_nonempty() {
+ TStringType s(Data_._123456());
+
+#ifdef TSTRING_IS_STD_STRING
+ using reference = volatile typename TStringType::value_type&;
+#else
+ using reference = typename TStringType::reference;
+#endif
+ reference trailing_zero = s[s.size()];
+ trailing_zero = 0;
+ UNIT_ASSERT(trailing_zero == 0);
+ }
+
+#ifndef TSTRING_IS_STD_STRING
+ // Dereferencing string end() is not allowed by C++ standard as of C++20, avoid using in real code.
+ void null_char_assignment_to_end_of_empty() {
+ TStringType s;
+
+ volatile auto& trailing_zero = *(s.begin() + s.size());
+ trailing_zero = 0;
+ UNIT_ASSERT(trailing_zero == 0);
+ }
+
+ // Dereferencing string end() is not allowed by C++ standard as of C++20, avoid using in real code.
+ void null_char_assignment_to_end_of_nonempty() {
+ TStringType s(Data_._123456());
+
+ volatile auto& trailing_zero = *(s.begin() + s.size());
+ trailing_zero = 0;
+ UNIT_ASSERT(trailing_zero == 0);
+ }
+#endif
+
+ void insert() {
+ TStringType strorg = Data_.This_is_test_string_for_string_calls();
+ TStringType str;
+
+ // In case of reallocation there is no auto reference problem
+ // so we reserve a big enough TStringType to be sure to test this
+ // particular point.
+
+ str.reserve(100);
+ str = strorg;
+
+ //test self insertion:
+ str.insert(10, str.c_str() + 5, 15);
+ UNIT_ASSERT(str == Data_.This_is_teis_test_string_st_string_for_string_calls());
+
+ str = strorg;
+ str.insert(15, str.c_str() + 5, 25);
+ UNIT_ASSERT(str == Data_.This_is_test_stis_test_string_for_stringring_for_string_calls());
+
+ str = strorg;
+ str.insert(0, str.c_str() + str.size() - 4, 4);
+ UNIT_ASSERT(str == Data_.allsThis_is_test_string_for_string_calls());
+
+ str = strorg;
+ str.insert(0, str.c_str() + str.size() / 2 - 1, str.size() / 2 + 1);
+ UNIT_ASSERT(str == Data_.ng_for_string_callsThis_is_test_string_for_string_calls());
+
+ str = strorg;
+ typename TStringType::iterator b = str.begin();
+ typename TStringType::const_iterator s = str.begin() + str.size() / 2 - 1;
+ typename TStringType::const_iterator e = str.end();
+ str.insert(b, s, e);
+ UNIT_ASSERT(str == Data_.ng_for_string_callsThis_is_test_string_for_string_calls());
+
+#if 0
+ // AV
+ str = strorg;
+ str.insert(str.begin(), str.begin() + str.size() / 2 - 1, str.end());
+ UNIT_ASSERT(str == Data.ng_for_string_callsThis_is_test_string_for_string_calls());
+#endif
+
+ TStringType str0;
+ str0.insert(str0.begin(), 5, *Data_._0());
+ UNIT_ASSERT(str0 == Data_._00000());
+
+ TStringType str1;
+ {
+ typename TStringType::size_type pos = 0, nb = 2;
+ str1.insert(pos, nb, *Data_._1());
+ }
+ UNIT_ASSERT(str1 == Data_._11());
+
+ str0.insert(0, str1);
+ UNIT_ASSERT(str0 == Data_._1100000());
+
+ TStringType str2(Data_._2345());
+ str0.insert(str0.size(), str2, 1, 2);
+ UNIT_ASSERT(str0 == Data_._110000034());
+
+ str1.insert(str1.begin() + 1, 2, *Data_._2());
+ UNIT_ASSERT(str1 == Data_._1221());
+
+ str1.insert(2, Data_._333333(), 3);
+ UNIT_ASSERT(str1 == Data_._1233321());
+
+ str1.insert(4, Data_._4444());
+ UNIT_ASSERT(str1 == Data_._12334444321());
+
+ str1.insert(str1.begin() + 6, *Data_._5());
+ UNIT_ASSERT(str1 == Data_._123344544321());
+ }
+
+ void resize() {
+ TStringType s;
+
+ s.resize(0);
+
+ UNIT_ASSERT(*s.c_str() == 0);
+
+ s = Data_._1234567();
+
+ s.resize(0);
+ UNIT_ASSERT(*s.c_str() == 0);
+
+ s = Data_._1234567();
+ s.resize(1);
+ UNIT_ASSERT(s.size() == 1);
+ UNIT_ASSERT(*s.c_str() == *Data_._1());
+ UNIT_ASSERT(*(s.c_str() + 1) == 0);
+
+ s = Data_._1234567();
+#if 0
+ s.resize(10);
+#else
+ s.resize(10, 0);
+#endif
+ UNIT_ASSERT(s.size() == 10);
+ UNIT_ASSERT(s[6] == *Data_._7());
+ UNIT_ASSERT(s[7] == 0);
+ UNIT_ASSERT(s[8] == 0);
+ UNIT_ASSERT(s[9] == 0);
+ }
+
+ void find() {
+ TStringType s(Data_.one_two_three_one_two_three());
+
+ UNIT_ASSERT(s.find(Data_.one()) == 0);
+ UNIT_ASSERT(s.find(*Data_.t()) == 4);
+ UNIT_ASSERT(s.find(*Data_.t(), 5) == 8);
+
+ UNIT_ASSERT(s.find(Data_.four()) == TStringType::npos);
+ UNIT_ASSERT(s.find(Data_.one(), TStringType::npos) == TStringType::npos);
+ UNIT_ASSERT(s.find_first_of(Data_.abcde()) == 2);
+ UNIT_ASSERT(s.find_first_not_of(Data_.enotw_()) == 9);
+ }
+
+ void capacity() {
+ TStringType s;
+
+ UNIT_ASSERT(s.capacity() < s.max_size());
+ UNIT_ASSERT(s.capacity() >= s.size());
+
+ for (int i = 0; i < 18; ++i) {
+ s += ' ';
+
+ UNIT_ASSERT(s.capacity() > 0);
+ UNIT_ASSERT(s.capacity() < s.max_size());
+ UNIT_ASSERT(s.capacity() >= s.size());
+ }
+ }
+
+ void assign() {
+ TStringType s;
+ TChar const* cstr = Data_.test_string_for_assign();
+
+ s.assign(cstr, cstr + 22);
+ UNIT_ASSERT(s == Data_.test_string_for_assign());
+
+ TStringType s2(Data_.other_test_string());
+ s.assign(s2);
+ UNIT_ASSERT(s == s2);
+
+ static TStringType str1;
+ static TStringType str2;
+
+ // short TStringType optim:
+ str1 = Data_._123456();
+ // longer than short TStringType:
+ str2 = Data_._1234567890123456789012345678901234567890();
+
+ UNIT_ASSERT(str1[5] == *Data_._6());
+ UNIT_ASSERT(str2[29] == *Data_._0());
+ }
+
+ void copy() {
+ TStringType s(Data_.foo());
+ TChar dest[4];
+ dest[0] = dest[1] = dest[2] = dest[3] = 1;
+ s.copy(dest, 4);
+ int pos = 0;
+ UNIT_ASSERT(dest[pos++] == *Data_.f());
+ UNIT_ASSERT(dest[pos++] == *Data_.o());
+ UNIT_ASSERT(dest[pos++] == *Data_.o());
+ UNIT_ASSERT(dest[pos++] == 1);
+
+ dest[0] = dest[1] = dest[2] = dest[3] = 1;
+ s.copy(dest, 4, 2);
+ pos = 0;
+ UNIT_ASSERT(dest[pos++] == *Data_.o());
+ UNIT_ASSERT(dest[pos++] == 1);
+
+ // @todo use UNIT_TEST_EXCEPTION
+ try {
+ s.copy(dest, 4, 5);
+ UNIT_ASSERT(!"expected std::out_of_range");
+ } catch (const std::out_of_range&) {
+ UNIT_ASSERT(true);
+ } catch (...) {
+ UNIT_ASSERT(!"expected std::out_of_range");
+ }
+ }
+
+ void cbegin_cend() {
+ const char helloThere[] = "Hello there";
+ TString s = helloThere;
+ size_t index = 0;
+ for (auto it = s.cbegin(); s.cend() != it; ++it, ++index) {
+ UNIT_ASSERT_VALUES_EQUAL(helloThere[index], *it);
+ }
+ }
+
+ void compare() {
+ TStringType str1(Data_.abcdef());
+ TStringType str2;
+
+ str2 = Data_.abcdef();
+ UNIT_ASSERT(str1.compare(str2) == 0);
+ UNIT_ASSERT(str1.compare(str2.data(), str2.size()) == 0);
+ str2 = Data_.abcde();
+ UNIT_ASSERT(str1.compare(str2) > 0);
+ UNIT_ASSERT(str1.compare(str2.data(), str2.size()) > 0);
+ str2 = Data_.abcdefg();
+ UNIT_ASSERT(str1.compare(str2) < 0);
+ UNIT_ASSERT(str1.compare(str2.data(), str2.size()) < 0);
+
+ UNIT_ASSERT(str1.compare(Data_.abcdef()) == 0);
+ UNIT_ASSERT(str1.compare(Data_.abcde()) > 0);
+ UNIT_ASSERT(str1.compare(Data_.abcdefg()) < 0);
+
+ str2 = Data_.cde();
+ UNIT_ASSERT(str1.compare(2, 3, str2) == 0);
+ str2 = Data_.cd();
+ UNIT_ASSERT(str1.compare(2, 3, str2) > 0);
+ str2 = Data_.cdef();
+ UNIT_ASSERT(str1.compare(2, 3, str2) < 0);
+
+ str2 = Data_.abcdef();
+ UNIT_ASSERT(str1.compare(2, 3, str2, 2, 3) == 0);
+ UNIT_ASSERT(str1.compare(2, 3, str2, 2, 2) > 0);
+ UNIT_ASSERT(str1.compare(2, 3, str2, 2, 4) < 0);
+
+ UNIT_ASSERT(str1.compare(2, 3, Data_.cdefgh(), 3) == 0);
+ UNIT_ASSERT(str1.compare(2, 3, Data_.cdefgh(), 2) > 0);
+ UNIT_ASSERT(str1.compare(2, 3, Data_.cdefgh(), 4) < 0);
+ }
+
+ void find_last_of() {
+ // 21.3.6.4
+ TStringType s(Data_.one_two_three_one_two_three());
+
+ UNIT_ASSERT(s.find_last_of(Data_.abcde()) == 26);
+ UNIT_ASSERT(s.find_last_of(TStringType(Data_.abcde())) == 26);
+
+ TStringType test(Data_.aba());
+
+ UNIT_ASSERT(test.find_last_of(Data_.a(), 2, 1) == 2);
+ UNIT_ASSERT(test.find_last_of(Data_.a(), 1, 1) == 0);
+ UNIT_ASSERT(test.find_last_of(Data_.a(), 0, 1) == 0);
+
+ UNIT_ASSERT(test.find_last_of(*Data_.a(), 2) == 2);
+ UNIT_ASSERT(test.find_last_of(*Data_.a(), 1) == 0);
+ UNIT_ASSERT(test.find_last_of(*Data_.a(), 0) == 0);
+ }
+#if 0
+ void rfind() {
+ // 21.3.6.2
+ TStringType s(Data.one_two_three_one_two_three());
+
+ UNIT_ASSERT(s.rfind(Data.two()) == 18);
+ UNIT_ASSERT(s.rfind(Data.two(), 0) == TStringType::npos);
+ UNIT_ASSERT(s.rfind(Data.two(), 11) == 4);
+ UNIT_ASSERT(s.rfind(*Data.w()) == 19);
+
+ TStringType test(Data.aba());
+
+ UNIT_ASSERT(test.rfind(Data.a(), 2, 1) == 2);
+ UNIT_ASSERT(test.rfind(Data.a(), 1, 1) == 0);
+ UNIT_ASSERT(test.rfind(Data.a(), 0, 1) == 0);
+
+ UNIT_ASSERT(test.rfind(*Data.a(), 2) == 2);
+ UNIT_ASSERT(test.rfind(*Data.a(), 1) == 0);
+ UNIT_ASSERT(test.rfind(*Data.a(), 0) == 0);
+ }
+#endif
+ void find_last_not_of() {
+ // 21.3.6.6
+ TStringType s(Data_.one_two_three_one_two_three());
+
+ UNIT_ASSERT(s.find_last_not_of(Data_.ehortw_()) == 15);
+
+ TStringType test(Data_.aba());
+
+ UNIT_ASSERT(test.find_last_not_of(Data_.a(), 2, 1) == 1);
+ UNIT_ASSERT(test.find_last_not_of(Data_.b(), 2, 1) == 2);
+ UNIT_ASSERT(test.find_last_not_of(Data_.a(), 1, 1) == 1);
+ UNIT_ASSERT(test.find_last_not_of(Data_.b(), 1, 1) == 0);
+ UNIT_ASSERT(test.find_last_not_of(Data_.a(), 0, 1) == TStringType::npos);
+ UNIT_ASSERT(test.find_last_not_of(Data_.b(), 0, 1) == 0);
+
+ UNIT_ASSERT(test.find_last_not_of(*Data_.a(), 2) == 1);
+ UNIT_ASSERT(test.find_last_not_of(*Data_.b(), 2) == 2);
+ UNIT_ASSERT(test.find_last_not_of(*Data_.a(), 1) == 1);
+ UNIT_ASSERT(test.find_last_not_of(*Data_.b(), 1) == 0);
+ UNIT_ASSERT(test.find_last_not_of(*Data_.a(), 0) == TStringType::npos);
+ UNIT_ASSERT(test.find_last_not_of(*Data_.b(), 0) == 0);
+ }
+#if 0
+ void replace() {
+ // This test case is for the non template basic_TString::replace method,
+ // this is why we play with the const iterators and reference to guaranty
+ // that the right method is called.
+
+ const TStringType v(Data._78());
+ TStringType s(Data._123456());
+ TStringType const& cs = s;
+
+ typename TStringType::iterator i = s.begin() + 1;
+ s.replace(i, i + 3, v.begin(), v.end());
+ UNIT_ASSERT(s == Data._17856());
+
+ s = Data._123456();
+ i = s.begin() + 1;
+ s.replace(i, i + 1, v.begin(), v.end());
+ UNIT_ASSERT(s == Data._1783456());
+
+ s = Data._123456();
+ i = s.begin() + 1;
+ typename TStringType::const_iterator ci = s.begin() + 1;
+ s.replace(i, i + 3, ci + 3, cs.end());
+ UNIT_ASSERT(s == Data._15656());
+
+ s = Data._123456();
+ i = s.begin() + 1;
+ ci = s.begin() + 1;
+ s.replace(i, i + 3, ci, ci + 2);
+ UNIT_ASSERT(s == Data._12356());
+
+ s = Data._123456();
+ i = s.begin() + 1;
+ ci = s.begin() + 1;
+ s.replace(i, i + 3, ci + 1, cs.end());
+ UNIT_ASSERT(s == Data._1345656());
+
+ s = Data._123456();
+ i = s.begin();
+ ci = s.begin() + 1;
+ s.replace(i, i, ci, ci + 1);
+ UNIT_ASSERT(s == Data._2123456());
+
+ s = Data._123456();
+ s.replace(s.begin() + 4, s.end(), cs.begin(), cs.end());
+ UNIT_ASSERT(s == Data._1234123456());
+
+ // This is the test for the template replace method.
+
+ s = Data._123456();
+ typename TStringType::iterator b = s.begin() + 4;
+ typename TStringType::iterator e = s.end();
+ typename TStringType::const_iterator rb = s.begin();
+ typename TStringType::const_iterator re = s.end();
+ s.replace(b, e, rb, re);
+ UNIT_ASSERT(s == Data._1234123456());
+
+ s = Data._123456();
+ s.replace(s.begin() + 4, s.end(), s.begin(), s.end());
+ UNIT_ASSERT(s == Data._1234123456());
+
+ TStringType strorg(Data.This_is_test_StringT_for_StringT_calls());
+ TStringType str = strorg;
+ str.replace(5, 15, str.c_str(), 10);
+ UNIT_ASSERT(str == Data.This_This_is_tefor_StringT_calls());
+
+ str = strorg;
+ str.replace(5, 5, str.c_str(), 10);
+ UNIT_ASSERT(str == Data.This_This_is_test_StringT_for_StringT_calls());
+
+ #if !defined(STLPORT) || defined(_STLP_MEMBER_TEMPLATES)
+ deque<TChar> cdeque;
+ cdeque.push_back(*Data.I());
+ str.replace(str.begin(), str.begin() + 11, cdeque.begin(), cdeque.end());
+ UNIT_ASSERT(str == Data.Is_test_StringT_for_StringT_calls());
+ #endif
+ }
+#endif
+}; // TStringStdTestImpl
+
+class TStringTest: public TTestBase, private TStringTestImpl<TString, TTestData<char>> {
+public:
+ UNIT_TEST_SUITE(TStringTest);
+ UNIT_TEST(TestMaxSize);
+ UNIT_TEST(TestConstructors);
+ UNIT_TEST(TestReplace);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(TestRefCount);
+#endif
+ UNIT_TEST(TestFind);
+ UNIT_TEST(TestContains);
+ UNIT_TEST(TestOperators);
+ UNIT_TEST(TestMulOperators);
+ UNIT_TEST(TestFuncs);
+ UNIT_TEST(TestUtils);
+ UNIT_TEST(TestEmpty);
+ UNIT_TEST(TestJoin);
+ UNIT_TEST(TestCopy);
+ UNIT_TEST(TestStrCpy);
+ UNIT_TEST(TestPrefixSuffix);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(TestCharRef);
+#endif
+ UNIT_TEST(TestBack)
+ UNIT_TEST(TestFront)
+ UNIT_TEST(TestIterators);
+ UNIT_TEST(TestReverseIterators);
+ UNIT_TEST(TestAppendUtf16)
+ UNIT_TEST(TestFillingAssign)
+ UNIT_TEST(TestStdStreamApi)
+ //UNIT_TEST(TestOperatorsCI); must fail
+ UNIT_TEST_SUITE_END();
+
+ void TestAppendUtf16() {
+ TString appended = TString("А роза упала").AppendUtf16(u" на лапу Азора");
+ UNIT_ASSERT(appended == "А роза упала на лапу Азора");
+ }
+
+ void TestFillingAssign() {
+ TString s("abc");
+ s.assign(5, 'a');
+ UNIT_ASSERT_VALUES_EQUAL(s, "aaaaa");
+ }
+
+ void TestStdStreamApi() {
+ const TString data = "abracadabra";
+ std::stringstream ss;
+ ss << data;
+
+ UNIT_ASSERT_VALUES_EQUAL(data, ss.str());
+
+ ss << '\n'
+ << data << std::endl;
+
+ TString read = "xxx";
+ ss >> read;
+ UNIT_ASSERT_VALUES_EQUAL(read, data);
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TStringTest);
+
+class TWideStringTest: public TTestBase, private TStringTestImpl<TUtf16String, TTestData<wchar16>> {
+public:
+ UNIT_TEST_SUITE(TWideStringTest);
+ UNIT_TEST(TestConstructors);
+ UNIT_TEST(TestReplace);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(TestRefCount);
+#endif
+ UNIT_TEST(TestFind);
+ UNIT_TEST(TestContains);
+ UNIT_TEST(TestOperators);
+ UNIT_TEST(TestLetOperator)
+ UNIT_TEST(TestMulOperators);
+ UNIT_TEST(TestFuncs);
+ UNIT_TEST(TestUtils);
+ UNIT_TEST(TestEmpty);
+ UNIT_TEST(TestJoin);
+ UNIT_TEST(TestCopy);
+ UNIT_TEST(TestStrCpy);
+ UNIT_TEST(TestPrefixSuffix);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(TestCharRef);
+#endif
+ UNIT_TEST(TestBack);
+ UNIT_TEST(TestFront)
+ UNIT_TEST(TestDecodingMethods);
+ UNIT_TEST(TestIterators);
+ UNIT_TEST(TestReverseIterators);
+ UNIT_TEST(TestStringLiterals);
+ UNIT_TEST_SUITE_END();
+
+private:
+ void TestDecodingMethods() {
+ UNIT_ASSERT(TUtf16String::FromAscii("").empty());
+ UNIT_ASSERT(TUtf16String::FromAscii("abc") == ASCIIToWide("abc"));
+
+ const char* text = "123kx83abcd ej)#$%ddja&%J&";
+ TUtf16String wtext = ASCIIToWide(text);
+
+ UNIT_ASSERT(wtext == TUtf16String::FromAscii(text));
+
+ TString strtext(text);
+ UNIT_ASSERT(wtext == TUtf16String::FromAscii(strtext));
+
+ TStringBuf strbuftext(text);
+ UNIT_ASSERT(wtext == TUtf16String::FromAscii(strbuftext));
+
+ UNIT_ASSERT(wtext.substr(5) == TUtf16String::FromAscii(text + 5));
+
+ const wchar16 wideCyrillicAlphabet[] = {
+ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ 0x00};
+
+ TUtf16String strWide(wideCyrillicAlphabet);
+ TString strUtf8 = WideToUTF8(strWide);
+
+ UNIT_ASSERT(strWide == TUtf16String::FromUtf8(strUtf8.c_str()));
+ UNIT_ASSERT(strWide == TUtf16String::FromUtf8(strUtf8));
+ UNIT_ASSERT(strWide == TUtf16String::FromUtf8(TStringBuf(strUtf8)));
+
+ // assign
+
+ TUtf16String s1;
+ s1.AssignAscii("1234");
+ UNIT_ASSERT(s1 == ASCIIToWide("1234"));
+
+ s1.AssignUtf8(strUtf8);
+ UNIT_ASSERT(s1 == strWide);
+
+ s1.AssignAscii(text);
+ UNIT_ASSERT(s1 == wtext);
+
+ // append
+
+ TUtf16String s2;
+ TUtf16String testAppend = strWide;
+ s2.AppendUtf8(strUtf8);
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += ' ';
+ s2.AppendAscii(" ");
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += '_';
+ s2.AppendUtf8("_");
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += wtext;
+ s2.AppendAscii(text);
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += wtext;
+ s2.AppendUtf8(text);
+ UNIT_ASSERT(testAppend == s2);
+ }
+
+ void TestLetOperator() {
+ TUtf16String str;
+
+ str = wchar16('X');
+ UNIT_ASSERT(str == TUtf16String::FromAscii("X"));
+
+ const TUtf16String hello = TUtf16String::FromAscii("hello");
+ str = hello.data();
+ UNIT_ASSERT(str == hello);
+
+ str = hello;
+ UNIT_ASSERT(str == hello);
+ }
+
+ void TestStringLiterals() {
+ TUtf16String s1 = u"hello";
+ UNIT_ASSERT_VALUES_EQUAL(s1, TUtf16String::FromAscii("hello"));
+
+ TUtf16String s2 = u"привет";
+ UNIT_ASSERT_VALUES_EQUAL(s2, TUtf16String::FromUtf8("привет"));
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TWideStringTest);
+
+class TUtf32StringTest: public TTestBase, private TStringTestImpl<TUtf32String, TTestData<wchar32>> {
+public:
+ UNIT_TEST_SUITE(TUtf32StringTest);
+ UNIT_TEST(TestConstructors);
+ UNIT_TEST(TestReplace);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(TestRefCount);
+#endif
+ UNIT_TEST(TestFind);
+ UNIT_TEST(TestContains);
+ UNIT_TEST(TestOperators);
+ UNIT_TEST(TestLetOperator)
+ UNIT_TEST(TestMulOperators);
+ UNIT_TEST(TestFuncs);
+ UNIT_TEST(TestUtils);
+ UNIT_TEST(TestEmpty);
+ UNIT_TEST(TestJoin);
+ UNIT_TEST(TestCopy);
+ UNIT_TEST(TestStrCpy);
+ UNIT_TEST(TestPrefixSuffix);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(TestCharRef);
+#endif
+ UNIT_TEST(TestBack);
+ UNIT_TEST(TestFront)
+ UNIT_TEST(TestDecodingMethods);
+ UNIT_TEST(TestDecodingMethodsMixedStr);
+ UNIT_TEST(TestIterators);
+ UNIT_TEST(TestReverseIterators);
+ UNIT_TEST(TestStringLiterals);
+ UNIT_TEST_SUITE_END();
+
+private:
+ void TestDecodingMethods() {
+ UNIT_ASSERT(TUtf32String::FromAscii("").empty());
+ UNIT_ASSERT(TUtf32String::FromAscii("abc") == ASCIIToUTF32("abc"));
+
+ const char* text = "123kx83abcd ej)#$%ddja&%J&";
+ TUtf32String wtext = ASCIIToUTF32(text);
+
+ UNIT_ASSERT(wtext == TUtf32String::FromAscii(text));
+
+ TString strtext(text);
+ UNIT_ASSERT(wtext == TUtf32String::FromAscii(strtext));
+
+ TStringBuf strbuftext(text);
+ UNIT_ASSERT(wtext == TUtf32String::FromAscii(strbuftext));
+
+ UNIT_ASSERT(wtext.substr(5) == TUtf32String::FromAscii(text + 5));
+
+ const wchar32 wideCyrillicAlphabet[] = {
+ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ 0x00};
+
+ TUtf32String strWide(wideCyrillicAlphabet);
+ TString strUtf8 = WideToUTF8(strWide);
+
+ UNIT_ASSERT(strWide == TUtf32String::FromUtf8(strUtf8.c_str()));
+ UNIT_ASSERT(strWide == TUtf32String::FromUtf8(strUtf8));
+ UNIT_ASSERT(strWide == TUtf32String::FromUtf8(TStringBuf(strUtf8)));
+
+ // assign
+
+ TUtf32String s1;
+ s1.AssignAscii("1234");
+ UNIT_ASSERT(s1 == ASCIIToUTF32("1234"));
+
+ s1.AssignUtf8(strUtf8);
+ UNIT_ASSERT(s1 == strWide);
+
+ s1.AssignAscii(text);
+ UNIT_ASSERT(s1 == wtext);
+
+ // append
+
+ TUtf32String s2;
+ TUtf32String testAppend = strWide;
+ s2.AppendUtf8(strUtf8);
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += ' ';
+ s2.AppendAscii(" ");
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += '_';
+ s2.AppendUtf8("_");
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += wtext;
+ s2.AppendAscii(text);
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += wtext;
+ s2.AppendUtf8(text);
+
+ UNIT_ASSERT(testAppend == s2);
+ }
+
+ void TestDecodingMethodsMixedStr() {
+ UNIT_ASSERT(TUtf32String::FromAscii("").empty());
+ UNIT_ASSERT(TUtf32String::FromAscii("abc") == ASCIIToUTF32("abc"));
+
+ const char* text = "123kx83abcd ej)#$%ddja&%J&";
+ TUtf32String wtext = ASCIIToUTF32(text);
+
+ UNIT_ASSERT(wtext == TUtf32String::FromAscii(text));
+
+ TString strtext(text);
+ UNIT_ASSERT(wtext == TUtf32String::FromAscii(strtext));
+
+ TStringBuf strbuftext(text);
+ UNIT_ASSERT(wtext == TUtf32String::FromAscii(strbuftext));
+
+ UNIT_ASSERT(wtext.substr(5) == TUtf32String::FromAscii(text + 5));
+
+ const wchar32 cyrilicAndLatinWide[] = {
+ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ wchar32('z'),
+ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ wchar32('z'),
+ 0x00};
+
+ TUtf32String strWide(cyrilicAndLatinWide);
+ TString strUtf8 = WideToUTF8(strWide);
+
+ UNIT_ASSERT(strWide == TUtf32String::FromUtf8(strUtf8.c_str()));
+ UNIT_ASSERT(strWide == TUtf32String::FromUtf8(strUtf8));
+ UNIT_ASSERT(strWide == UTF8ToUTF32<true>(strUtf8));
+ UNIT_ASSERT(strWide == UTF8ToUTF32<false>(strUtf8));
+ UNIT_ASSERT(strWide == TUtf32String::FromUtf8(TStringBuf(strUtf8)));
+
+ // assign
+
+ TUtf32String s1;
+ s1.AssignAscii("1234");
+ UNIT_ASSERT(s1 == ASCIIToUTF32("1234"));
+
+ s1.AssignUtf8(strUtf8);
+ UNIT_ASSERT(s1 == strWide);
+
+ s1.AssignAscii(text);
+ UNIT_ASSERT(s1 == wtext);
+
+ // append
+
+ TUtf32String s2;
+ TUtf32String testAppend = strWide;
+ s2.AppendUtf16(UTF8ToWide(strUtf8));
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += ' ';
+ s2.AppendAscii(" ");
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += '_';
+ s2.AppendUtf8("_");
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += wtext;
+ s2.AppendAscii(text);
+ UNIT_ASSERT(testAppend == s2);
+
+ testAppend += wtext;
+ s2.AppendUtf8(text);
+
+ UNIT_ASSERT(testAppend == s2);
+ }
+
+ void TestLetOperator() {
+ TUtf32String str;
+
+ str = wchar32('X');
+ UNIT_ASSERT(str == TUtf32String::FromAscii("X"));
+
+ const TUtf32String hello = TUtf32String::FromAscii("hello");
+ str = hello.data();
+ UNIT_ASSERT(str == hello);
+
+ str = hello;
+ UNIT_ASSERT(str == hello);
+ }
+
+ void TestStringLiterals() {
+ TUtf32String s1 = U"hello";
+ UNIT_ASSERT_VALUES_EQUAL(s1, TUtf32String::FromAscii("hello"));
+
+ TUtf32String s2 = U"привет";
+ UNIT_ASSERT_VALUES_EQUAL(s2, TUtf32String::FromUtf8("привет"));
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TUtf32StringTest);
+
+class TStringStdTest: public TTestBase, private TStringStdTestImpl<TString, TTestData<char>> {
+public:
+ UNIT_TEST_SUITE(TStringStdTest);
+ UNIT_TEST(Constructor);
+ UNIT_TEST(reserve);
+ UNIT_TEST(short_string);
+ UNIT_TEST(erase);
+ UNIT_TEST(data);
+ UNIT_TEST(c_str);
+ UNIT_TEST(null_char_of_empty);
+ UNIT_TEST(null_char);
+ UNIT_TEST(null_char_assignment_to_subscript_of_empty);
+ UNIT_TEST(null_char_assignment_to_subscript_of_nonempty);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(null_char_assignment_to_end_of_empty);
+ UNIT_TEST(null_char_assignment_to_end_of_nonempty);
+#endif
+ UNIT_TEST(insert);
+ UNIT_TEST(resize);
+ UNIT_TEST(find);
+ UNIT_TEST(capacity);
+ UNIT_TEST(assign);
+ UNIT_TEST(copy);
+ UNIT_TEST(cbegin_cend);
+ UNIT_TEST(compare);
+ UNIT_TEST(find_last_of);
+#if 0
+ UNIT_TEST(rfind);
+ UNIT_TEST(replace);
+#endif
+ UNIT_TEST(find_last_not_of);
+ UNIT_TEST_SUITE_END();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TStringStdTest);
+
+class TWideStringStdTest: public TTestBase, private TStringStdTestImpl<TUtf16String, TTestData<wchar16>> {
+public:
+ UNIT_TEST_SUITE(TWideStringStdTest);
+ UNIT_TEST(Constructor);
+ UNIT_TEST(reserve);
+ UNIT_TEST(short_string);
+ UNIT_TEST(erase);
+ UNIT_TEST(data);
+ UNIT_TEST(c_str);
+ UNIT_TEST(null_char_of_empty);
+ UNIT_TEST(null_char);
+ UNIT_TEST(null_char_assignment_to_subscript_of_empty);
+ UNIT_TEST(null_char_assignment_to_subscript_of_nonempty);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_TEST(null_char_assignment_to_end_of_empty);
+ UNIT_TEST(null_char_assignment_to_end_of_nonempty);
+#endif
+ UNIT_TEST(insert);
+ UNIT_TEST(resize);
+ UNIT_TEST(find);
+ UNIT_TEST(capacity);
+ UNIT_TEST(assign);
+ UNIT_TEST(copy);
+ UNIT_TEST(cbegin_cend);
+ UNIT_TEST(compare);
+ UNIT_TEST(find_last_of);
+#if 0
+ UNIT_TEST(rfind);
+ UNIT_TEST(replace);
+#endif
+ UNIT_TEST(find_last_not_of);
+ UNIT_TEST_SUITE_END();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TWideStringStdTest);
+
+Y_UNIT_TEST_SUITE(TStringConversionTest) {
+ Y_UNIT_TEST(ConversionToStdStringTest) {
+ TString abra = "cadabra";
+ std::string stdAbra = abra;
+ UNIT_ASSERT_VALUES_EQUAL(stdAbra, "cadabra");
+ }
+
+ Y_UNIT_TEST(ConversionToStdStringViewTest) {
+ TString abra = "cadabra";
+ std::string_view stdAbra = abra;
+ UNIT_ASSERT_VALUES_EQUAL(stdAbra, "cadabra");
+ }
+}
+
+Y_UNIT_TEST_SUITE(HashFunctorTests) {
+ Y_UNIT_TEST(TestTransparency) {
+ THash<TString> h;
+ const char* ptr = "a";
+ const TStringBuf strbuf = ptr;
+ const TString str = ptr;
+ const std::string stdStr = ptr;
+ UNIT_ASSERT_VALUES_EQUAL(h(ptr), h(strbuf));
+ UNIT_ASSERT_VALUES_EQUAL(h(ptr), h(str));
+ UNIT_ASSERT_VALUES_EQUAL(h(ptr), h(stdStr));
+ }
+}
+
+#if !defined(TSTRING_IS_STD_STRING)
+Y_UNIT_TEST_SUITE(StdNonConformant) {
+ Y_UNIT_TEST(TestEraseNoThrow) {
+ TString x;
+
+ LegacyErase(x, 10);
+ }
+
+ Y_UNIT_TEST(TestReplaceNoThrow) {
+ TString x;
+
+ LegacyReplace(x, 0, 0, "1");
+
+ UNIT_ASSERT_VALUES_EQUAL(x, "1");
+
+ LegacyReplace(x, 10, 0, "1");
+
+ UNIT_ASSERT_VALUES_EQUAL(x, "1");
+ }
+
+ Y_UNIT_TEST(TestNoAlias) {
+ TString s = "x";
+
+ s.AppendNoAlias("abc", 3);
+
+ UNIT_ASSERT_VALUES_EQUAL(s, "xabc");
+ UNIT_ASSERT_VALUES_EQUAL(TString(s.c_str()), "xabc");
+ }
+}
+#endif
+
+Y_UNIT_TEST_SUITE(Interop) {
+ static void Mutate(std::string& s) {
+ s += "y";
+ }
+
+ static void Mutate(TString& s) {
+ Mutate(MutRef(s));
+ }
+
+ Y_UNIT_TEST(TestMutate) {
+ TString x = "x";
+
+ Mutate(x);
+
+ UNIT_ASSERT_VALUES_EQUAL(x, "xy");
+ }
+
+ static std::string TransformStd(const std::string& s) {
+ return s + "y";
+ }
+
+ static TString Transform(const TString& s) {
+ return TransformStd(s);
+ }
+
+ Y_UNIT_TEST(TestTransform) {
+ UNIT_ASSERT_VALUES_EQUAL(Transform(TString("x")), "xy");
+ }
+
+ Y_UNIT_TEST(TestTemp) {
+ UNIT_ASSERT_VALUES_EQUAL("x" + ConstRef(TString("y")), "xy");
+ }
+}
diff --git a/util/generic/string_ut.h b/util/generic/string_ut.h
new file mode 100644
index 0000000000..44bb10bdeb
--- /dev/null
+++ b/util/generic/string_ut.h
@@ -0,0 +1,1156 @@
+#pragma once
+
+#include "string.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/string/reverse.h>
+
+template <typename CharT, size_t N>
+struct TCharBuffer {
+ CharT Data[N];
+ //! copies characters from string to the internal buffer without any conversion
+ //! @param s a string that must contain only characters less than 0x7F
+ explicit TCharBuffer(const char* s) {
+ // copy all symbols including null terminated symbol
+ for (size_t i = 0; i < N; ++i) {
+ Data[i] = s[i];
+ }
+ }
+ const CharT* GetData() const {
+ return Data;
+ }
+};
+
+template <>
+struct TCharBuffer<char, 0> {
+ const char* Data;
+ //! stores pointer to string
+ explicit TCharBuffer(const char* s)
+ : Data(s)
+ {
+ }
+ const char* GetData() const {
+ return Data;
+ }
+};
+
+#define DECLARE_AND_RETURN_BUFFER(s) \
+ static TCharBuffer<CharT, sizeof(s)> buf(s); \
+ return buf.GetData();
+
+//! @attention this class can process characters less than 0x7F only (the low half of ASCII table)
+template <typename CharT>
+struct TTestData {
+ // words
+ const CharT* str1() {
+ DECLARE_AND_RETURN_BUFFER("str1");
+ }
+ const CharT* str2() {
+ DECLARE_AND_RETURN_BUFFER("str2");
+ }
+ const CharT* str__________________________________________________1() {
+ DECLARE_AND_RETURN_BUFFER("str 1");
+ }
+ const CharT* str__________________________________________________2() {
+ DECLARE_AND_RETURN_BUFFER("str 2");
+ }
+ const CharT* one() {
+ DECLARE_AND_RETURN_BUFFER("one");
+ }
+ const CharT* two() {
+ DECLARE_AND_RETURN_BUFFER("two");
+ }
+ const CharT* three() {
+ DECLARE_AND_RETURN_BUFFER("three");
+ }
+ const CharT* thrii() {
+ DECLARE_AND_RETURN_BUFFER("thrii");
+ }
+ const CharT* four() {
+ DECLARE_AND_RETURN_BUFFER("four");
+ }
+ const CharT* enotw_() {
+ DECLARE_AND_RETURN_BUFFER("enotw ");
+ }
+ const CharT* foo() {
+ DECLARE_AND_RETURN_BUFFER("foo");
+ }
+ const CharT* abcdef() {
+ DECLARE_AND_RETURN_BUFFER("abcdef");
+ }
+ const CharT* abcdefg() {
+ DECLARE_AND_RETURN_BUFFER("abcdefg");
+ }
+ const CharT* aba() {
+ DECLARE_AND_RETURN_BUFFER("aba");
+ }
+ const CharT* hr() {
+ DECLARE_AND_RETURN_BUFFER("hr");
+ }
+ const CharT* hrt() {
+ DECLARE_AND_RETURN_BUFFER("hrt");
+ }
+ const CharT* thr() {
+ DECLARE_AND_RETURN_BUFFER("thr");
+ }
+ const CharT* tw() {
+ DECLARE_AND_RETURN_BUFFER("tw");
+ }
+ const CharT* ow() {
+ DECLARE_AND_RETURN_BUFFER("ow");
+ }
+ const CharT* opq() {
+ DECLARE_AND_RETURN_BUFFER("opq");
+ }
+ const CharT* xyz() {
+ DECLARE_AND_RETURN_BUFFER("xyz");
+ }
+ const CharT* abc() {
+ DECLARE_AND_RETURN_BUFFER("abc");
+ }
+ const CharT* abcd() {
+ DECLARE_AND_RETURN_BUFFER("abcd");
+ }
+ const CharT* abcde() {
+ DECLARE_AND_RETURN_BUFFER("abcde");
+ }
+ const CharT* abcc() {
+ DECLARE_AND_RETURN_BUFFER("abcc");
+ }
+ const CharT* abce() {
+ DECLARE_AND_RETURN_BUFFER("abce");
+ }
+ const CharT* qwe() {
+ DECLARE_AND_RETURN_BUFFER("qwe");
+ }
+ const CharT* cd() {
+ DECLARE_AND_RETURN_BUFFER("cd");
+ }
+ const CharT* cde() {
+ DECLARE_AND_RETURN_BUFFER("cde");
+ }
+ const CharT* cdef() {
+ DECLARE_AND_RETURN_BUFFER("cdef");
+ }
+ const CharT* cdefgh() {
+ DECLARE_AND_RETURN_BUFFER("cdefgh");
+ }
+ const CharT* ehortw_() {
+ DECLARE_AND_RETURN_BUFFER("ehortw ");
+ }
+ const CharT* fg() {
+ DECLARE_AND_RETURN_BUFFER("fg");
+ }
+ const CharT* abcdefgh() {
+ DECLARE_AND_RETURN_BUFFER("abcdefgh");
+ }
+
+ // phrases
+ const CharT* Hello_World() {
+ DECLARE_AND_RETURN_BUFFER("Hello World");
+ }
+ const CharT* This_is_test_string_for_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("This is test string for string calls");
+ }
+ const CharT* This_is_teis_test_string_st_string_for_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("This is teis test string st string for string calls");
+ }
+ const CharT* This_is_test_stis_test_string_for_stringring_for_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("This is test stis test string for stringring for string calls");
+ }
+ const CharT* allsThis_is_test_string_for_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("allsThis is test string for string calls");
+ }
+ const CharT* ng_for_string_callsThis_is_test_string_for_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("ng for string callsThis is test string for string calls");
+ }
+ const CharT* one_two_three_one_two_three() {
+ DECLARE_AND_RETURN_BUFFER("one two three one two three");
+ }
+ const CharT* test_string_for_assign() {
+ DECLARE_AND_RETURN_BUFFER("test string for assign");
+ }
+ const CharT* other_test_string() {
+ DECLARE_AND_RETURN_BUFFER("other test string");
+ }
+ const CharT* This_This_is_tefor_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("This This is tefor string calls");
+ }
+ const CharT* This_This_is_test_string_for_string_calls() {
+ DECLARE_AND_RETURN_BUFFER("This This is test string for string calls");
+ }
+
+ const CharT* _0123456x() {
+ DECLARE_AND_RETURN_BUFFER("0123456x");
+ }
+ const CharT* _0123456xy() {
+ DECLARE_AND_RETURN_BUFFER("0123456xy");
+ }
+ const CharT* _0123456xyz() {
+ DECLARE_AND_RETURN_BUFFER("0123456xyz");
+ }
+ const CharT* _0123456xyzZ() {
+ DECLARE_AND_RETURN_BUFFER("0123456xyzZ");
+ }
+ const CharT* _0123456xyzZ0() {
+ DECLARE_AND_RETURN_BUFFER("0123456xyzZ0");
+ }
+ const CharT* abc0123456xyz() {
+ DECLARE_AND_RETURN_BUFFER("abc0123456xyz");
+ }
+ const CharT* BCabc0123456xyz() {
+ DECLARE_AND_RETURN_BUFFER("BCabc0123456xyz");
+ }
+ const CharT* qweBCabc0123456xyz() {
+ DECLARE_AND_RETURN_BUFFER("qweBCabc0123456xyz");
+ }
+ const CharT* _1qweBCabc0123456xyz() {
+ DECLARE_AND_RETURN_BUFFER("1qweBCabc0123456xyz");
+ }
+ const CharT* _01abc23456() {
+ DECLARE_AND_RETURN_BUFFER("01abc23456");
+ }
+ const CharT* _01ABCabc23456() {
+ DECLARE_AND_RETURN_BUFFER("01ABCabc23456");
+ }
+ const CharT* ABC() {
+ DECLARE_AND_RETURN_BUFFER("ABC");
+ }
+ const CharT* ABCD() {
+ DECLARE_AND_RETURN_BUFFER("ABCD");
+ }
+ const CharT* QWE() {
+ DECLARE_AND_RETURN_BUFFER("QWE");
+ }
+ const CharT* XYZ() {
+ DECLARE_AND_RETURN_BUFFER("XYZ");
+ }
+ const CharT* W01ABCabc23456() {
+ DECLARE_AND_RETURN_BUFFER("W01ABCabc23456");
+ }
+ const CharT* abcd456() {
+ DECLARE_AND_RETURN_BUFFER("abcd456");
+ }
+ const CharT* abcdABCD() {
+ DECLARE_AND_RETURN_BUFFER("abcdABCD");
+ }
+ const CharT* abcdABC123() {
+ DECLARE_AND_RETURN_BUFFER("abcdABC123");
+ }
+ const CharT* z123z123() {
+ DECLARE_AND_RETURN_BUFFER("z123z123");
+ }
+ const CharT* ASDF1234QWER() {
+ DECLARE_AND_RETURN_BUFFER("ASDF1234QWER");
+ }
+ const CharT* asdf1234qwer() {
+ DECLARE_AND_RETURN_BUFFER("asdf1234qwer");
+ }
+ const CharT* asDF1234qWEr() {
+ DECLARE_AND_RETURN_BUFFER("asDF1234qWEr");
+ }
+ const CharT* AsDF1234qWEr() {
+ DECLARE_AND_RETURN_BUFFER("AsDF1234qWEr");
+ }
+ const CharT* Asdf1234qwer() {
+ DECLARE_AND_RETURN_BUFFER("Asdf1234qwer");
+ }
+ const CharT* Asdf1234qwerWWWW() {
+ DECLARE_AND_RETURN_BUFFER("Asdf1234qwerWWWW");
+ }
+ const CharT* Asdf() {
+ DECLARE_AND_RETURN_BUFFER("Asdf");
+ }
+ const CharT* orig() {
+ DECLARE_AND_RETURN_BUFFER("orig");
+ }
+ const CharT* fdfdsfds() {
+ DECLARE_AND_RETURN_BUFFER("fdfdsfds");
+ }
+
+ // numbers
+ const CharT* _0() {
+ DECLARE_AND_RETURN_BUFFER("0");
+ }
+ const CharT* _00() {
+ DECLARE_AND_RETURN_BUFFER("00");
+ }
+ const CharT* _0000000000() {
+ DECLARE_AND_RETURN_BUFFER("0000000000");
+ }
+ const CharT* _00000() {
+ DECLARE_AND_RETURN_BUFFER("00000");
+ }
+ const CharT* _0123() {
+ DECLARE_AND_RETURN_BUFFER("0123");
+ }
+ const CharT* _01230123() {
+ DECLARE_AND_RETURN_BUFFER("01230123");
+ }
+ const CharT* _01234() {
+ DECLARE_AND_RETURN_BUFFER("01234");
+ }
+ const CharT* _0123401234() {
+ DECLARE_AND_RETURN_BUFFER("0123401234");
+ }
+ const CharT* _012345() {
+ DECLARE_AND_RETURN_BUFFER("012345");
+ }
+ const CharT* _0123456() {
+ DECLARE_AND_RETURN_BUFFER("0123456");
+ }
+ const CharT* _1() {
+ DECLARE_AND_RETURN_BUFFER("1");
+ }
+ const CharT* _11() {
+ DECLARE_AND_RETURN_BUFFER("11");
+ }
+ const CharT* _1100000() {
+ DECLARE_AND_RETURN_BUFFER("1100000");
+ }
+ const CharT* _110000034() {
+ DECLARE_AND_RETURN_BUFFER("110000034");
+ }
+ const CharT* _12() {
+ DECLARE_AND_RETURN_BUFFER("12");
+ }
+ const CharT* _123() {
+ DECLARE_AND_RETURN_BUFFER("123");
+ }
+ const CharT* _1233321() {
+ DECLARE_AND_RETURN_BUFFER("1233321");
+ }
+ const CharT* _1221() {
+ DECLARE_AND_RETURN_BUFFER("1221");
+ }
+ const CharT* _1234123456() {
+ DECLARE_AND_RETURN_BUFFER("1234123456");
+ }
+ const CharT* _12334444321() {
+ DECLARE_AND_RETURN_BUFFER("12334444321");
+ }
+ const CharT* _123344544321() {
+ DECLARE_AND_RETURN_BUFFER("123344544321");
+ }
+ const CharT* _1234567890123456789012345678901234567890() {
+ DECLARE_AND_RETURN_BUFFER("1234567890123456789012345678901234567890");
+ }
+ const CharT* _1234() {
+ DECLARE_AND_RETURN_BUFFER("1234");
+ }
+ const CharT* _12345() {
+ DECLARE_AND_RETURN_BUFFER("12345");
+ }
+ const CharT* _123456() {
+ DECLARE_AND_RETURN_BUFFER("123456");
+ }
+ const CharT* _1234567() {
+ DECLARE_AND_RETURN_BUFFER("1234567");
+ }
+ const CharT* _1234561234() {
+ DECLARE_AND_RETURN_BUFFER("1234561234");
+ }
+ const CharT* _12356() {
+ DECLARE_AND_RETURN_BUFFER("12356");
+ }
+ const CharT* _1345656() {
+ DECLARE_AND_RETURN_BUFFER("1345656");
+ }
+ const CharT* _15656() {
+ DECLARE_AND_RETURN_BUFFER("15656");
+ }
+ const CharT* _17856() {
+ DECLARE_AND_RETURN_BUFFER("17856");
+ }
+ const CharT* _1783456() {
+ DECLARE_AND_RETURN_BUFFER("1783456");
+ }
+ const CharT* _2() {
+ DECLARE_AND_RETURN_BUFFER("2");
+ }
+ const CharT* _2123456() {
+ DECLARE_AND_RETURN_BUFFER("2123456");
+ }
+ const CharT* _23() {
+ DECLARE_AND_RETURN_BUFFER("23");
+ }
+ const CharT* _2345() {
+ DECLARE_AND_RETURN_BUFFER("2345");
+ }
+ const CharT* _3() {
+ DECLARE_AND_RETURN_BUFFER("3");
+ }
+ const CharT* _345() {
+ DECLARE_AND_RETURN_BUFFER("345");
+ }
+ const CharT* _3456() {
+ DECLARE_AND_RETURN_BUFFER("3456");
+ }
+ const CharT* _333333() {
+ DECLARE_AND_RETURN_BUFFER("333333");
+ }
+ const CharT* _389() {
+ DECLARE_AND_RETURN_BUFFER("389");
+ }
+ const CharT* _4294967295() {
+ DECLARE_AND_RETURN_BUFFER("4294967295");
+ }
+ const CharT* _4444() {
+ DECLARE_AND_RETURN_BUFFER("4444");
+ }
+ const CharT* _5() {
+ DECLARE_AND_RETURN_BUFFER("5");
+ }
+ const CharT* _6() {
+ DECLARE_AND_RETURN_BUFFER("6");
+ }
+ const CharT* _6543210() {
+ DECLARE_AND_RETURN_BUFFER("6543210");
+ }
+ const CharT* _7() {
+ DECLARE_AND_RETURN_BUFFER("7");
+ }
+ const CharT* _78() {
+ DECLARE_AND_RETURN_BUFFER("78");
+ }
+ const CharT* _2004_01_01() {
+ DECLARE_AND_RETURN_BUFFER("2004-01-01");
+ }
+ const CharT* _1234562004_01_01() {
+ DECLARE_AND_RETURN_BUFFER("1234562004-01-01");
+ }
+ const CharT* _0123456_12345() {
+ DECLARE_AND_RETURN_BUFFER("0123456_12345");
+ }
+
+ // letters
+ const CharT* a() {
+ DECLARE_AND_RETURN_BUFFER("a");
+ }
+ const CharT* b() {
+ DECLARE_AND_RETURN_BUFFER("b");
+ }
+ const CharT* c() {
+ DECLARE_AND_RETURN_BUFFER("c");
+ }
+ const CharT* d() {
+ DECLARE_AND_RETURN_BUFFER("d");
+ }
+ const CharT* e() {
+ DECLARE_AND_RETURN_BUFFER("e");
+ }
+ const CharT* f() {
+ DECLARE_AND_RETURN_BUFFER("f");
+ }
+ const CharT* h() {
+ DECLARE_AND_RETURN_BUFFER("h");
+ }
+ const CharT* o() {
+ DECLARE_AND_RETURN_BUFFER("o");
+ }
+ const CharT* p() {
+ DECLARE_AND_RETURN_BUFFER("p");
+ }
+ const CharT* q() {
+ DECLARE_AND_RETURN_BUFFER("q");
+ }
+ const CharT* r() {
+ DECLARE_AND_RETURN_BUFFER("r");
+ }
+ const CharT* s() {
+ DECLARE_AND_RETURN_BUFFER("s");
+ }
+ const CharT* t() {
+ DECLARE_AND_RETURN_BUFFER("t");
+ }
+ const CharT* w() {
+ DECLARE_AND_RETURN_BUFFER("w");
+ }
+ const CharT* x() {
+ DECLARE_AND_RETURN_BUFFER("x");
+ }
+ const CharT* y() {
+ DECLARE_AND_RETURN_BUFFER("y");
+ }
+ const CharT* z() {
+ DECLARE_AND_RETURN_BUFFER("z");
+ }
+ const CharT* H() {
+ DECLARE_AND_RETURN_BUFFER("H");
+ }
+ const CharT* I() {
+ DECLARE_AND_RETURN_BUFFER("I");
+ }
+ const CharT* W() {
+ DECLARE_AND_RETURN_BUFFER("W");
+ }
+
+ const CharT* Space() {
+ DECLARE_AND_RETURN_BUFFER(" ");
+ }
+ const CharT* Empty() {
+ DECLARE_AND_RETURN_BUFFER("");
+ }
+
+ size_t HashOf_0123456() {
+ return 0;
+ }
+};
+
+template <>
+size_t TTestData<char>::HashOf_0123456() {
+ return 1229863857ul;
+}
+
+template <>
+size_t TTestData<wchar16>::HashOf_0123456() {
+ return 2775195331ul;
+}
+
+template <class TStringType, typename TTestData>
+class TStringTestImpl {
+protected:
+ using char_type = typename TStringType::char_type;
+ using traits_type = typename TStringType::traits_type;
+
+ TTestData Data;
+
+public:
+ void TestMaxSize() {
+ const size_t badMaxVal = TStringType{}.max_size() + 1;
+
+ TStringType s;
+ UNIT_CHECK_GENERATED_EXCEPTION(s.reserve(badMaxVal), std::length_error);
+ }
+
+ void TestConstructors() {
+ TStringType s0(nullptr);
+ UNIT_ASSERT(s0.size() == 0);
+ UNIT_ASSERT_EQUAL(s0, TStringType());
+
+ TStringType s;
+ TStringType s1(*Data._0());
+ TStringType s2(Data._0());
+ UNIT_ASSERT(s1 == s2);
+
+ TStringType fromZero(0);
+ UNIT_ASSERT_VALUES_EQUAL(fromZero.size(), 0u);
+
+ TStringType fromChar(char_type('a'));
+ UNIT_ASSERT_VALUES_EQUAL(fromChar.size(), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(fromChar[0], char_type('a'));
+
+#ifndef TSTRING_IS_STD_STRING
+ TStringType s3 = TStringType::Uninitialized(10);
+ UNIT_ASSERT(s3.size() == 10);
+#endif
+
+ TStringType s4(Data._0123456(), 1, 3);
+ UNIT_ASSERT(s4 == Data._123());
+
+ TStringType s5(5, *Data._0());
+ UNIT_ASSERT(s5 == Data._00000());
+
+ TStringType s6(Data._0123456());
+ UNIT_ASSERT(s6 == Data._0123456());
+ TStringType s7(s6);
+ UNIT_ASSERT(s7 == s6);
+#ifndef TSTRING_IS_STD_STRING
+ UNIT_ASSERT(s7.c_str() == s6.c_str());
+#endif
+
+ TStringType s8(s7, 1, 3);
+ UNIT_ASSERT(s8 == Data._123());
+
+ TStringType s9(*Data._1());
+ UNIT_ASSERT(s9 == Data._1());
+
+ TStringType s10(Reserve(100));
+ UNIT_ASSERT(s10.empty());
+ UNIT_ASSERT(s10.capacity() >= 100);
+ }
+
+ void TestReplace() {
+ TStringType s(Data._0123456());
+ UNIT_ASSERT(s.copy() == Data._0123456());
+
+ // append family
+ s.append(Data.x());
+ UNIT_ASSERT(s == Data._0123456x());
+
+#ifdef TSTRING_IS_STD_STRING
+ s.append(Data.xyz() + 1, 1);
+#else
+ s.append(Data.xyz(), 1, 1);
+#endif
+ UNIT_ASSERT(s == Data._0123456xy());
+
+ s.append(TStringType(Data.z()));
+ UNIT_ASSERT(s == Data._0123456xyz());
+
+ s.append(TStringType(Data.XYZ()), 2, 1);
+ UNIT_ASSERT(s == Data._0123456xyzZ());
+
+ s.append(*Data._0());
+ UNIT_ASSERT(s == Data._0123456xyzZ0());
+
+ // prepend family
+ s = Data._0123456xyz();
+ s.prepend(TStringType(Data.abc()));
+ UNIT_ASSERT(s == Data.abc0123456xyz());
+
+ s.prepend(TStringType(Data.ABC()), 1, 2);
+ UNIT_ASSERT(s == Data.BCabc0123456xyz());
+
+ s.prepend(Data.qwe());
+ UNIT_ASSERT(s == Data.qweBCabc0123456xyz());
+
+ s.prepend(*Data._1());
+ UNIT_ASSERT(s == Data._1qweBCabc0123456xyz());
+
+ // substr
+ s = Data.abc0123456xyz();
+ s = s.substr(3, 7);
+ UNIT_ASSERT(s == Data._0123456());
+
+ // insert family
+ s.insert(2, Data.abc());
+ UNIT_ASSERT(s == Data._01abc23456());
+
+ s.insert(2, TStringType(Data.ABC()));
+ UNIT_ASSERT(s == Data._01ABCabc23456());
+
+ s.insert(0, TStringType(Data.QWE()), 1, 1);
+ UNIT_ASSERT(s == Data.W01ABCabc23456());
+
+ // replace family
+ s = Data._01abc23456();
+ s.replace(0, 7, Data.abcd());
+ UNIT_ASSERT(s == Data.abcd456());
+
+ s.replace(4, 3, TStringType(Data.ABCD()));
+ UNIT_ASSERT(s == Data.abcdABCD());
+
+ s.replace(7, 10, TStringType(Data._01234()), 1, 3);
+ UNIT_ASSERT(s == Data.abcdABC123());
+ UNIT_ASSERT(Data.abcdABC123() == s);
+
+ // remove, erase
+ s.remove(4);
+ UNIT_ASSERT(s == Data.abcd());
+ s.erase(3);
+ UNIT_ASSERT(s == Data.abc());
+
+ // Read access
+ s = Data._012345();
+ UNIT_ASSERT(s.at(1) == *Data._1());
+ UNIT_ASSERT(s[1] == *Data._1());
+ UNIT_ASSERT(s.at(s.size()) == 0);
+ UNIT_ASSERT(s[s.size()] == 0);
+ }
+
+#ifndef TSTRING_IS_STD_STRING
+ void TestRefCount() {
+ using TStr = TStringType;
+
+ struct TestStroka: public TStr {
+ using TStr::TStr;
+ // un-protect
+ using TStr::RefCount;
+ };
+
+ TestStroka s1(Data.orig());
+ UNIT_ASSERT_EQUAL(s1.RefCount() == 1, true);
+ TestStroka s2(s1);
+ UNIT_ASSERT_EQUAL(s1.RefCount() == 2, true);
+ UNIT_ASSERT_EQUAL(s2.RefCount() == 2, true);
+ UNIT_ASSERT_EQUAL(s1.c_str() == s2.c_str(), true); // the same pointer
+ char_type* beg = s2.begin();
+ UNIT_ASSERT_EQUAL(s1 == beg, true);
+ UNIT_ASSERT_EQUAL(s1.RefCount() == 1, true);
+ UNIT_ASSERT_EQUAL(s2.RefCount() == 1, true);
+ UNIT_ASSERT_EQUAL(s1.c_str() == s2.c_str(), false);
+ }
+#endif
+
+ // Find family
+
+ void TestFind() {
+ const TStringType s(Data._0123456_12345());
+ const TStringType s2(Data._0123());
+
+ UNIT_ASSERT(s.find(Data._345()) == 3);
+ UNIT_ASSERT(s.find(Data._345(), 5) == 10);
+
+ UNIT_ASSERT(s.find(Data._345(), 20) == TStringType::npos);
+ UNIT_ASSERT(s.find(*Data._3()) == 3);
+ UNIT_ASSERT(s.find(TStringType(Data._345())) == 3);
+ UNIT_ASSERT(s.find(TStringType(Data._345()), 2) == 3);
+
+ UNIT_ASSERT(s.find_first_of(TStringType(Data._389())) == 3);
+ UNIT_ASSERT(s.find_first_of(Data._389()) == 3);
+ UNIT_ASSERT(s.find_first_of(Data._389(), s.size()) == TStringType::npos);
+ UNIT_ASSERT(s.find_first_not_of(Data._123()) == 0);
+ UNIT_ASSERT(s.find_first_of('6') == 6);
+ UNIT_ASSERT(s.find_first_of('1', 2) == 8);
+ UNIT_ASSERT(s.find_first_not_of('0') == 1);
+ UNIT_ASSERT(s.find_first_not_of('1', 1) == 2);
+
+ const TStringType rs = Data._0123401234();
+ UNIT_ASSERT(rs.rfind(*Data._3()) == 8);
+
+ const TStringType empty;
+ UNIT_ASSERT(empty.find(empty) == 0);
+ UNIT_ASSERT(s.find(empty, 0) == 0);
+ UNIT_ASSERT(s.find(empty, 1) == 1);
+ UNIT_ASSERT(s.find(empty, s.length()) == s.length());
+ UNIT_ASSERT(s.find(empty, s.length() + 1) == TStringType::npos);
+
+ UNIT_ASSERT(s.rfind(empty) == s.length());
+ UNIT_ASSERT(empty.rfind(empty) == 0);
+ UNIT_ASSERT(empty.rfind(s) == TStringType::npos);
+
+ UNIT_ASSERT(s2.rfind(s) == TStringType::npos);
+ UNIT_ASSERT(s.rfind(s2) == 0);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345())) == 10);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345()), 13) == 10);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345()), 10) == 10);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345()), 9) == 3);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345()), 6) == 3);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345()), 3) == 3);
+ UNIT_ASSERT(s.rfind(TStringType(Data._345()), 2) == TStringType::npos);
+ }
+
+ void TestContains() {
+ const TStringType s(Data._0123456_12345());
+ const TStringType s2(Data._0123());
+
+ UNIT_ASSERT(s.Contains(Data._345()));
+ UNIT_ASSERT(!s2.Contains(Data._345()));
+
+ UNIT_ASSERT(s.Contains('1'));
+ UNIT_ASSERT(!s.Contains('*'));
+
+ TStringType empty;
+ UNIT_ASSERT(s.Contains(empty));
+ UNIT_ASSERT(!empty.Contains(s));
+ UNIT_ASSERT(empty.Contains(empty));
+ UNIT_ASSERT(s.Contains(empty, s.length()));
+ }
+
+ // Operators
+
+ void TestOperators() {
+ TStringType s(Data._0123456());
+
+ // operator +=
+ s += TStringType(Data.x());
+ UNIT_ASSERT(s == Data._0123456x());
+
+ s += Data.y();
+ UNIT_ASSERT(s == Data._0123456xy());
+
+ s += *Data.z();
+ UNIT_ASSERT(s == Data._0123456xyz());
+
+ // operator +
+ s = Data._0123456();
+ s = s + TStringType(Data.x());
+ UNIT_ASSERT(s == Data._0123456x());
+
+ s = s + Data.y();
+ UNIT_ASSERT(s == Data._0123456xy());
+
+ s = s + *Data.z();
+ UNIT_ASSERT(s == Data._0123456xyz());
+
+ // operator !=
+ s = Data._012345();
+ UNIT_ASSERT(s != TStringType(Data.xyz()));
+ UNIT_ASSERT(s != Data.xyz());
+ UNIT_ASSERT(Data.xyz() != s);
+
+ // operator <
+ UNIT_ASSERT_EQUAL(s < TStringType(Data.xyz()), true);
+ UNIT_ASSERT_EQUAL(s < Data.xyz(), true);
+ UNIT_ASSERT_EQUAL(Data.xyz() < s, false);
+
+ // operator <=
+ UNIT_ASSERT_EQUAL(s <= TStringType(Data.xyz()), true);
+ UNIT_ASSERT_EQUAL(s <= Data.xyz(), true);
+ UNIT_ASSERT_EQUAL(Data.xyz() <= s, false);
+
+ // operator >
+ UNIT_ASSERT_EQUAL(s > TStringType(Data.xyz()), false);
+ UNIT_ASSERT_EQUAL(s > Data.xyz(), false);
+ UNIT_ASSERT_EQUAL(Data.xyz() > s, true);
+
+ // operator >=
+ UNIT_ASSERT_EQUAL(s >= TStringType(Data.xyz()), false);
+ UNIT_ASSERT_EQUAL(s >= Data.xyz(), false);
+ UNIT_ASSERT_EQUAL(Data.xyz() >= s, true);
+ }
+
+ void TestOperatorsCI() {
+ TStringType s(Data.ABCD());
+ UNIT_ASSERT(s > Data.abc0123456xyz());
+ UNIT_ASSERT(s == Data.abcd());
+
+ using TCIStringBuf = TBasicStringBuf<char_type, traits_type>;
+
+ UNIT_ASSERT(s > TCIStringBuf(Data.abc0123456xyz()));
+ UNIT_ASSERT(TCIStringBuf(Data.abc0123456xyz()) < s);
+ UNIT_ASSERT(s == TCIStringBuf(Data.abcd()));
+ }
+
+ void TestMulOperators() {
+ {
+ TStringType s(Data._0());
+ s *= 10;
+ UNIT_ASSERT_EQUAL(s, TStringType(Data._0000000000()));
+ }
+ {
+ TStringType s = TStringType(Data._0()) * 2;
+ UNIT_ASSERT_EQUAL(s, TStringType(Data._00()));
+ }
+ }
+
+ // Test any other functions
+
+ void TestFuncs() {
+ TStringType s(Data._0123456());
+ UNIT_ASSERT(s.c_str() == s.data());
+
+ // length()
+ UNIT_ASSERT(s.length() == s.size());
+ UNIT_ASSERT(s.length() == traits_type::length(s.data()));
+
+ // is_null()
+ TStringType s1(Data.Empty());
+ UNIT_ASSERT(s1.is_null() == true);
+ UNIT_ASSERT(s1.is_null() == s1.empty());
+ UNIT_ASSERT(s1.is_null() == !s1);
+
+ TStringType s2(s);
+ UNIT_ASSERT(s2 == s);
+
+ // reverse()
+ ReverseInPlace(s2);
+ UNIT_ASSERT(s2 == Data._6543210());
+
+ // to_upper()
+ s2 = Data.asdf1234qwer();
+ s2.to_upper();
+ UNIT_ASSERT(s2 == Data.ASDF1234QWER());
+
+ // to_lower()
+ s2.to_lower();
+ UNIT_ASSERT(s2 == Data.asdf1234qwer());
+
+ // to_title()
+ s2 = Data.asDF1234qWEr();
+ s2.to_title();
+ UNIT_ASSERT(s2 == Data.Asdf1234qwer());
+
+ s2 = Data.AsDF1234qWEr();
+ s2.to_title();
+ UNIT_ASSERT(s2 == Data.Asdf1234qwer());
+
+ // Friend functions
+ s2 = Data.asdf1234qwer();
+ TStringType s3 = to_upper(s2);
+ UNIT_ASSERT(s3 == Data.ASDF1234QWER());
+ s3 = to_lower(s2);
+ UNIT_ASSERT(s3 == Data.asdf1234qwer());
+ s3 = to_title(s2);
+ UNIT_ASSERT(s3 == Data.Asdf1234qwer());
+ s2 = s3;
+
+ // resize family
+ s2.resize(s2.size()); // without length change
+ UNIT_ASSERT(s2 == Data.Asdf1234qwer());
+
+ s2.resize(s2.size() + 4, *Data.W());
+ UNIT_ASSERT(s2 == Data.Asdf1234qwerWWWW());
+
+ s2.resize(4);
+ UNIT_ASSERT(s2 == Data.Asdf());
+
+ // assign family
+ s2 = Data.asdf1234qwer();
+ s2.assign(s, 1, 3);
+ UNIT_ASSERT(s2 == Data._123());
+
+ s2.assign(Data._0123456(), 4);
+ UNIT_ASSERT(s2 == Data._0123());
+
+ s2.assign('1');
+ UNIT_ASSERT(s2 == Data._1());
+
+ s2.assign(Data._0123456());
+ UNIT_ASSERT(s2 == Data._0123456());
+
+ // hash()
+ TStringType sS = s2; // type 'TStringType' is used as is
+
+ ComputeHash(sS); /*size_t hash_val = sS.hash();
+
+ try {
+ //UNIT_ASSERT(hash_val == Data.HashOf_0123456());
+ } catch (...) {
+ Cerr << hash_val << Endl;
+ throw;
+ }*/
+
+ s2.assign(Data._0123456(), 2, 2);
+ UNIT_ASSERT(s2 == Data._23());
+
+ //s2.reserve();
+
+ TStringType s5(Data.abcde());
+ s5.clear();
+ UNIT_ASSERT(s5 == Data.Empty());
+ }
+
+ void TestUtils() {
+ TStringType s;
+ s = Data._01230123();
+ TStringType from = Data._0();
+ TStringType to = Data.z();
+
+ SubstGlobal(s, from, to);
+ UNIT_ASSERT(s == Data.z123z123());
+ }
+
+ void TestEmpty() {
+ TStringType s;
+ s = Data._2();
+ s = TStringType(Data.fdfdsfds(), (size_t)0, (size_t)0);
+ UNIT_ASSERT(s.empty());
+ }
+
+ void TestJoin() {
+ UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), Data._3456()), Data._123456());
+ UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), TStringType(Data._3456())), Data._123456());
+ UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data._12()), Data._3456()), Data._123456());
+ UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), Data._345(), Data._6()), Data._123456());
+ UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), TStringType(Data._345()), Data._6()), Data._123456());
+ UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data._12()), TStringType(Data._345()), Data._6()), Data._123456());
+ UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data.a()), Data.b(), TStringType(Data.cd()), TStringType(Data.e()), Data.fg(), TStringType(Data.h())), Data.abcdefgh());
+ UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data.a()), static_cast<typename TStringType::char_type>('b'), TStringType(Data.cd()), TStringType(Data.e()), Data.fg(), TStringType(Data.h())), Data.abcdefgh());
+ }
+
+ void TestCopy() {
+ TStringType s(Data.abcd());
+ TStringType c = s.copy();
+
+ UNIT_ASSERT_EQUAL(s, c);
+ UNIT_ASSERT(s.end() != c.end());
+ }
+
+ void TestStrCpy() {
+ {
+ TStringType s(Data.abcd());
+ char_type data[5];
+
+ data[4] = 1;
+
+ s.strcpy(data, 4);
+
+ UNIT_ASSERT_EQUAL(data[0], *Data.a());
+ UNIT_ASSERT_EQUAL(data[1], *Data.b());
+ UNIT_ASSERT_EQUAL(data[2], *Data.c());
+ UNIT_ASSERT_EQUAL(data[3], 0);
+ UNIT_ASSERT_EQUAL(data[4], 1);
+ }
+
+ {
+ TStringType s(Data.abcd());
+ char_type data[5];
+
+ s.strcpy(data, 5);
+
+ UNIT_ASSERT_EQUAL(data[0], *Data.a());
+ UNIT_ASSERT_EQUAL(data[1], *Data.b());
+ UNIT_ASSERT_EQUAL(data[2], *Data.c());
+ UNIT_ASSERT_EQUAL(data[3], *Data.d());
+ UNIT_ASSERT_EQUAL(data[4], 0);
+ }
+ }
+
+ void TestPrefixSuffix() {
+ const TStringType emptyStr;
+ UNIT_ASSERT_EQUAL(emptyStr.StartsWith('x'), false);
+ UNIT_ASSERT_EQUAL(emptyStr.EndsWith('x'), false);
+ UNIT_ASSERT_EQUAL(emptyStr.StartsWith(0), false);
+ UNIT_ASSERT_EQUAL(emptyStr.EndsWith(0), false);
+ UNIT_ASSERT_EQUAL(emptyStr.StartsWith(emptyStr), true);
+ UNIT_ASSERT_EQUAL(emptyStr.EndsWith(emptyStr), true);
+
+ const char_type chars[] = {'h', 'e', 'l', 'l', 'o', 0};
+ const TStringType str(chars);
+ UNIT_ASSERT_EQUAL(str.StartsWith('h'), true);
+ UNIT_ASSERT_EQUAL(str.StartsWith('o'), false);
+ UNIT_ASSERT_EQUAL(str.EndsWith('o'), true);
+ UNIT_ASSERT_EQUAL(str.EndsWith('h'), false);
+ UNIT_ASSERT_EQUAL(str.StartsWith(emptyStr), true);
+ UNIT_ASSERT_EQUAL(str.EndsWith(emptyStr), true);
+ }
+
+#ifndef TSTRING_IS_STD_STRING
+ void TestCharRef() {
+ const char_type abc[] = {'a', 'b', 'c', 0};
+ const char_type bbc[] = {'b', 'b', 'c', 0};
+ const char_type cbc[] = {'c', 'b', 'c', 0};
+
+ TStringType s0 = abc;
+ TStringType s1 = s0;
+
+ UNIT_ASSERT(!s0.IsDetached());
+ UNIT_ASSERT(!s1.IsDetached());
+
+ /* Read access shouldn't detach. */
+ UNIT_ASSERT_VALUES_EQUAL(s0[0], (ui8)'a');
+ UNIT_ASSERT(!s0.IsDetached());
+ UNIT_ASSERT(!s1.IsDetached());
+
+ /* Writing should detach. */
+ s1[0] = (ui8)'b';
+ TStringType s2 = s0;
+ s0[0] = (ui8)'c';
+
+ UNIT_ASSERT_VALUES_EQUAL(s0, cbc);
+ UNIT_ASSERT_VALUES_EQUAL(s1, bbc);
+ UNIT_ASSERT_VALUES_EQUAL(s2, abc);
+ UNIT_ASSERT(s0.IsDetached());
+ UNIT_ASSERT(s1.IsDetached());
+ UNIT_ASSERT(s2.IsDetached());
+
+ /* Accessing null terminator is OK. Note that writing into it is UB. */
+ UNIT_ASSERT_VALUES_EQUAL(s0[3], (ui8)'\0');
+ UNIT_ASSERT_VALUES_EQUAL(s1[3], (ui8)'\0');
+ UNIT_ASSERT_VALUES_EQUAL(s2[3], (ui8)'\0');
+
+ /* Assignment one char reference to another results in modification of underlying character */
+ {
+ const char_type dark_eyed[] = {'d', 'a', 'r', 'k', '-', 'e', 'y', 'e', 'd', 0};
+ const char_type red_eared[] = {'r', 'e', 'd', '-', 'e', 'a', 'r', 'e', 'd', 0};
+ TStringType s0 = dark_eyed;
+ TStringType s1 = TStringType::Uninitialized(s0.size());
+ for (size_t i = 0; i < s1.size(); ++i) {
+ const size_t j = (3u * (i + 1u) ^ 1u) % s0.size();
+ s1[i] = s0[j];
+ }
+ UNIT_ASSERT_VALUES_EQUAL(s1, red_eared);
+ }
+ }
+#endif
+
+ void TestBack() {
+ const char_type chars[] = {'f', 'o', 'o', 0};
+
+ TStringType str = chars;
+ const TStringType constStr = str;
+
+ UNIT_ASSERT_VALUES_EQUAL(constStr.back(), (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(str.back(), (ui8)'o');
+
+ str.back() = 'r';
+ UNIT_ASSERT_VALUES_EQUAL(constStr.back(), (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(str.back(), (ui8)'r');
+ }
+
+ void TestFront() {
+ const char_type chars[] = {'f', 'o', 'o', 0};
+
+ TStringType str = chars;
+ const TStringType constStr = str;
+
+ UNIT_ASSERT_VALUES_EQUAL(constStr.front(), (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(str.front(), (ui8)'f');
+
+ str.front() = 'r';
+ UNIT_ASSERT_VALUES_EQUAL(constStr.front(), (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(str.front(), (ui8)'r');
+ }
+
+ void TestIterators() {
+ const char_type chars[] = {'f', 'o', 0};
+
+ TStringType str = chars;
+ const TStringType constStr = str;
+
+ typename TStringType::const_iterator itBegin = str.begin();
+ typename TStringType::const_iterator itEnd = str.end();
+ typename TStringType::const_iterator citBegin = constStr.begin();
+ typename TStringType::const_iterator citEnd = constStr.end();
+
+ UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'f');
+
+ str.front() = 'r';
+ UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'f');
+
+ UNIT_ASSERT_VALUES_EQUAL(2, itEnd - itBegin);
+ UNIT_ASSERT_VALUES_EQUAL(2, citEnd - citBegin);
+
+ UNIT_ASSERT_VALUES_EQUAL(*(++itBegin), (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(*(++citBegin), (ui8)'o');
+
+ UNIT_ASSERT_VALUES_EQUAL(*(--itBegin), (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*(--citBegin), (ui8)'f');
+
+ UNIT_ASSERT_VALUES_EQUAL(*(itBegin++), (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*(citBegin++), (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'o');
+
+ UNIT_ASSERT_VALUES_EQUAL(*(itBegin--), (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(*(citBegin--), (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'f');
+ }
+
+ void TestReverseIterators() {
+ const char_type chars[] = {'f', 'o', 0};
+
+ TStringType str = chars;
+ const TStringType constStr = str;
+
+ typename TStringType::reverse_iterator ritBegin = str.rbegin();
+ typename TStringType::reverse_iterator ritEnd = str.rend();
+ typename TStringType::const_reverse_iterator critBegin = constStr.rbegin();
+ typename TStringType::const_reverse_iterator critEnd = constStr.rend();
+
+ UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'o');
+
+ str.back() = (ui8)'r';
+ UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'o');
+
+ UNIT_ASSERT_VALUES_EQUAL(2, ritEnd - ritBegin);
+ UNIT_ASSERT_VALUES_EQUAL(2, critEnd - critBegin);
+
+ UNIT_ASSERT_VALUES_EQUAL(*(++ritBegin), (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(*(++critBegin), (ui8)'f');
+
+ UNIT_ASSERT_VALUES_EQUAL(*(--ritBegin), (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*(--critBegin), (ui8)'o');
+
+ UNIT_ASSERT_VALUES_EQUAL(*(ritBegin++), (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*(critBegin++), (ui8)'o');
+ UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'f');
+
+ UNIT_ASSERT_VALUES_EQUAL(*(ritBegin--), (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(*(critBegin--), (ui8)'f');
+ UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'r');
+ UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'o');
+
+ *ritBegin = (ui8)'e';
+ UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'e');
+ }
+};
diff --git a/util/generic/string_ut.pyx b/util/generic/string_ut.pyx
new file mode 100644
index 0000000000..5407f5b4c1
--- /dev/null
+++ b/util/generic/string_ut.pyx
@@ -0,0 +1,225 @@
+# cython: c_string_type=str, c_string_encoding=utf8
+
+from libcpp.string cimport string as std_string
+from util.generic.string cimport TString, npos
+
+import pytest
+import unittest
+
+import sys
+
+
+class TestStroka(unittest.TestCase):
+ def test_unicode(self):
+ cdef TString x = "привет"
+ self.assertEquals(x, "привет")
+
+
+ def test_ctor1(self):
+ cdef TString tmp = TString()
+ cdef TString tmp2 = TString(tmp)
+ self.assertEquals(tmp2, "")
+
+ def test_ctor2(self):
+ cdef std_string tmp = b"hello"
+ cdef TString tmp2 = TString(tmp)
+ self.assertEquals(tmp2, "hello")
+
+ def test_ctor3(self):
+ cdef TString tmp = b"hello"
+ cdef TString tmp2 = TString(tmp, 0, 4)
+ self.assertEquals(tmp2, "hell")
+
+ def test_ctor4(self):
+ cdef TString tmp = TString(<char*>b"hello")
+ self.assertEquals(tmp, "hello")
+
+ def test_ctor5(self):
+ cdef TString tmp = TString(<char*>b"hello", 4)
+ self.assertEquals(tmp, "hell")
+
+ def test_ctor6(self):
+ cdef TString tmp = TString(<char*>b"hello", 1, 3)
+ self.assertEquals(tmp, "ell")
+
+ def test_ctor7(self):
+ cdef TString tmp = TString(3, <char>'x')
+ self.assertEquals(tmp, "xxx")
+
+ def test_ctor8(self):
+ cdef bytes tmp = b"hello"
+ cdef TString tmp2 = TString(<char*>tmp, <char*>tmp + 4)
+ self.assertEquals(tmp2, "hell")
+
+ def test_compare(self):
+ cdef TString tmp1 = b"abacab"
+ cdef TString tmp2 = b"abacab"
+ cdef TString tmp3 = b"abacac"
+
+ self.assertTrue(tmp1.compare(tmp2) == 0)
+ self.assertTrue(tmp1.compare(tmp3) < 0)
+ self.assertTrue(tmp3.compare(tmp1) > 0)
+
+ self.assertTrue(tmp1 == tmp2)
+ self.assertTrue(tmp1 != tmp3)
+
+ self.assertTrue(tmp1 < tmp3)
+ self.assertTrue(tmp1 <= tmp3)
+
+ self.assertTrue(tmp3 > tmp1)
+ self.assertTrue(tmp3 >= tmp1)
+
+ def test_operator_assign(self):
+ cdef TString tmp = b"hello"
+ cdef TString tmp2 = tmp
+ self.assertEquals(tmp2, "hello")
+
+ def test_operator_plus(self):
+ cdef TString tmp = TString(b"hello ") + TString(b"world")
+ self.assertEquals(tmp, "hello world")
+
+ def test_c_str(self):
+ cdef TString tmp = b"hello"
+ if sys.version_info.major == 2:
+ self.assertEquals(bytes(tmp.c_str()), b"hello")
+ else:
+ self.assertEquals(bytes(tmp.c_str(), 'utf8'), b"hello")
+
+ def test_length(self):
+ cdef TString tmp = b"hello"
+ self.assertEquals(tmp.size(), tmp.length())
+
+ def test_index(self):
+ cdef TString tmp = b"hello"
+
+ self.assertEquals(<bytes>tmp[0], b'h')
+ self.assertEquals(<bytes>tmp.at(0), b'h')
+
+ self.assertEquals(<bytes>tmp[4], b'o')
+ self.assertEquals(<bytes>tmp.at(4), b'o')
+
+ # Actually, TString::at() is noexcept
+ # with pytest.raises(IndexError):
+ # tmp.at(100)
+
+ def test_append(self):
+ cdef TString tmp
+ cdef TString tmp2 = b"fuu"
+
+ tmp.append(tmp2)
+ self.assertEquals(tmp, "fuu")
+
+ tmp.append(tmp2, 1, 2)
+ self.assertEquals(tmp, "fuuuu")
+
+ tmp.append(<char*>"ll ")
+ self.assertEquals(tmp, "fuuuull ")
+
+ tmp.append(<char*>"of greatness", 4)
+ self.assertEquals(tmp, "fuuuull of g")
+
+ tmp.append(2, <char>b'o')
+ self.assertEquals(tmp, "fuuuull of goo")
+
+ tmp.push_back(b'z')
+ self.assertEquals(tmp, "fuuuull of gooz")
+
+ def test_assign(self):
+ cdef TString tmp
+
+ tmp.assign(b"one")
+ self.assertEquals(tmp, "one")
+
+ tmp.assign(b"two hundred", 0, 3)
+ self.assertEquals(tmp, "two")
+
+ tmp.assign(<char*>b"three")
+ self.assertEquals(tmp, "three")
+
+ tmp.assign(<char*>b"three fiddy", 5)
+ self.assertEquals(tmp, "three")
+
+ def test_insert(self):
+ cdef TString tmp
+
+ tmp = b"xx"
+ tmp.insert(1, b"foo")
+ self.assertEquals(tmp, "xfoox")
+
+ tmp = b"xx"
+ tmp.insert(1, b"haxor", 1, 3)
+ self.assertEquals(tmp, "xaxox")
+
+ tmp = b"xx"
+ tmp.insert(1, <char*>b"foo")
+ self.assertEquals(tmp, "xfoox")
+
+ tmp = b"xx"
+ tmp.insert(1, <char*>b"foozzy", 3)
+ self.assertEquals(tmp, "xfoox")
+
+ tmp = b"xx"
+ tmp.insert(1, 2, <char>b'u')
+ self.assertEquals(tmp, "xuux")
+
+ def test_copy(self):
+ cdef char buf[16]
+ cdef TString tmp = b"hello"
+ tmp.copy(buf, 5, 0)
+ self.assertEquals(buf[:5], "hello")
+
+ def test_find(self):
+ cdef TString haystack = b"whole lotta bytes"
+ cdef TString needle = "hole"
+
+ self.assertEquals(haystack.find(needle), 1)
+ self.assertEquals(haystack.find(needle, 3), npos)
+
+ self.assertEquals(haystack.find(<char>b'h'), 1)
+ self.assertEquals(haystack.find(<char>b'h', 3), npos)
+
+ def test_rfind(self):
+ cdef TString haystack = b"whole lotta bytes"
+ cdef TString needle = b"hole"
+
+ self.assertEquals(haystack.rfind(needle), 1)
+ self.assertEquals(haystack.rfind(needle, 0), npos)
+
+ self.assertEquals(haystack.rfind(<char>b'h'), 1)
+ self.assertEquals(haystack.rfind(<char>b'h', 0), npos)
+
+ def test_find_first_of(self):
+ cdef TString haystack = b"whole lotta bytes"
+ cdef TString cset = b"hxz"
+
+ self.assertEquals(haystack.find_first_of(<char>b'h'), 1)
+ self.assertEquals(haystack.find_first_of(<char>b'h', 3), npos)
+
+ self.assertEquals(haystack.find_first_of(cset), 1)
+ self.assertEquals(haystack.find_first_of(cset, 3), npos)
+
+ def test_first_not_of(self):
+ cdef TString haystack = b"whole lotta bytes"
+ cdef TString cset = b"wxz"
+
+ self.assertEquals(haystack.find_first_not_of(<char>b'w'), 1)
+ self.assertEquals(haystack.find_first_not_of(<char>b'w', 3), 3)
+
+ self.assertEquals(haystack.find_first_not_of(cset), 1)
+ self.assertEquals(haystack.find_first_not_of(cset, 3), 3)
+
+ def test_find_last_of(self):
+ cdef TString haystack = b"whole lotta bytes"
+ cdef TString cset = b"hxz"
+
+ self.assertEquals(haystack.find_last_of(<char>b'h'), 1)
+ self.assertEquals(haystack.find_last_of(<char>b'h', 0), npos)
+
+ self.assertEquals(haystack.find_last_of(cset), 1)
+ self.assertEquals(haystack.find_last_of(cset, 0), npos)
+
+ def test_substr(self):
+ cdef TString tmp = b"foobar"
+
+ self.assertEquals(tmp.substr(1), "oobar")
+ self.assertEquals(tmp.substr(1, 4), "ooba")
diff --git a/util/generic/typelist.cpp b/util/generic/typelist.cpp
new file mode 100644
index 0000000000..275b565ddb
--- /dev/null
+++ b/util/generic/typelist.cpp
@@ -0,0 +1,5 @@
+#include "typelist.h"
+
+static_assert(
+ TSignedInts::THave<char>::value,
+ "char type in Arcadia must be signed; add -fsigned-char to compiler options");
diff --git a/util/generic/typelist.h b/util/generic/typelist.h
new file mode 100644
index 0000000000..5ce26ab97c
--- /dev/null
+++ b/util/generic/typelist.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#include <util/system/types.h>
+
+#include <util/generic/typetraits.h>
+
+#include <type_traits>
+
+template <class... R>
+struct TTypeList;
+
+namespace NTL {
+ template <unsigned N, typename TL>
+ struct TGetImpl {
+ using type = typename TGetImpl<N - 1, typename TL::TTail>::type;
+ };
+
+ template <typename TL>
+ struct TGetImpl<0u, TL> {
+ using type = typename TL::THead;
+ };
+}
+
+template <>
+struct TTypeList<> {
+ static constexpr size_t Length = 0;
+
+ template <class>
+ using THave = std::false_type;
+
+ template <template <class> class P>
+ struct TSelectBy {
+ using type = TTypeList<>;
+ };
+};
+
+using TNone = TTypeList<>;
+
+template <class H, class... R>
+struct TTypeList<H, R...> {
+ using THead = H;
+ using TTail = TTypeList<R...>;
+
+ static constexpr size_t Length = 1 + sizeof...(R);
+
+ template <class V>
+ using THave = TDisjunction<std::is_same<H, V>, typename TTail::template THave<V>>;
+
+ template <unsigned N>
+ using TGet = typename ::NTL::TGetImpl<N, TTypeList<H, R...>>::type;
+
+ template <template <class> class P>
+ struct TSelectBy {
+ using type = std::conditional_t<P<THead>::value, THead, typename TTail::template TSelectBy<P>::type>;
+ };
+};
+
+//FIXME: temporary to check overall build
+template <class T>
+struct TTypeList<T, TNone>: public TTypeList<T> {
+};
+
+using TCommonSignedInts = TTypeList<signed char, signed short, signed int, signed long, signed long long>;
+using TCommonUnsignedInts = TTypeList<unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long, bool>;
+using TFixedWidthSignedInts = TTypeList<i8, i16, i32, i64>;
+using TFixedWidthUnsignedInts = TTypeList<ui8, ui16, ui32, ui64>;
+using TFloats = TTypeList<float, double, long double>;
+
+namespace NTL {
+ template <class T1, class T2>
+ struct TConcat;
+
+ template <class... R1, class... R2>
+ struct TConcat<TTypeList<R1...>, TTypeList<R2...>> {
+ using type = TTypeList<R1..., R2...>;
+ };
+
+ template <bool isSigned, class T, class TS, class TU>
+ struct TTypeSelectorBase {
+ using TSignedInts = typename TConcat<TTypeList<T>, TS>::type;
+ using TUnsignedInts = TU;
+ };
+
+ template <class T, class TS, class TU>
+ struct TTypeSelectorBase<false, T, TS, TU> {
+ using TSignedInts = TS;
+ using TUnsignedInts = typename TConcat<TTypeList<T>, TU>::type;
+ };
+
+ template <class T, class TS, class TU>
+ struct TTypeSelector: public TTypeSelectorBase<((T)(-1) < 0), T, TS, TU> {
+ };
+
+ using T1 = TTypeSelector<char, TCommonSignedInts, TCommonUnsignedInts>;
+ using T2 = TTypeSelector<wchar_t, T1::TSignedInts, T1::TUnsignedInts>;
+}
+
+using TSignedInts = NTL::T2::TSignedInts;
+using TUnsignedInts = NTL::T2::TUnsignedInts;
+
+template <unsigned sizeOf>
+struct TSizeOfPredicate {
+ template <class T>
+ using TResult = TBoolConstant<sizeof(T) == sizeOf>;
+};
+
+template <typename T>
+using TFixedWidthSignedInt = typename TFixedWidthSignedInts::template TSelectBy<TSizeOfPredicate<sizeof(T)>::template TResult>::type;
+
+template <typename T>
+using TFixedWidthUnsignedInt = typename TFixedWidthUnsignedInts::template TSelectBy<TSizeOfPredicate<sizeof(T)>::template TResult>::type;
+
+template <typename T>
+using TFixedWidthFloat = typename TFloats::template TSelectBy<TSizeOfPredicate<sizeof(T)>::template TResult>::type;
diff --git a/util/generic/typelist_ut.cpp b/util/generic/typelist_ut.cpp
new file mode 100644
index 0000000000..eeabfa97b1
--- /dev/null
+++ b/util/generic/typelist_ut.cpp
@@ -0,0 +1,85 @@
+#include <library/cpp/testing/unittest/registar.h>
+#include <util/system/type_name.h>
+
+#include "typelist.h"
+#include "vector.h"
+#include "map.h"
+
+class TTypeListTest: public TTestBase {
+ UNIT_TEST_SUITE(TTypeListTest);
+ UNIT_TEST(TestSimple);
+ UNIT_TEST(TestHave);
+ UNIT_TEST(TestGet);
+ UNIT_TEST(TestFloatList);
+ UNIT_TEST(TestSelectBy);
+ UNIT_TEST_SUITE_END();
+
+public:
+ void TestGet() {
+ using TListType = TTypeList<int, char, float>;
+
+ UNIT_ASSERT_TYPES_EQUAL(TListType::TGet<0>, int);
+ UNIT_ASSERT_TYPES_EQUAL(TListType::TGet<1>, char);
+ UNIT_ASSERT_TYPES_EQUAL(TListType::TGet<2>, float);
+ }
+
+ void TestSimple() {
+ using TListType = TTypeList<int, char, float>;
+ UNIT_ASSERT_EQUAL(TListType::Length, 3);
+ UNIT_ASSERT_TYPES_EQUAL(TListType::THead, int);
+ }
+
+ struct TA {};
+ struct TB {};
+ struct TC {};
+ void TestHave() {
+ using TListType = TTypeList<TA, TB*, const TC&>;
+ UNIT_ASSERT(TListType::THave<TA>::value);
+ UNIT_ASSERT(TListType::THave<TB*>::value);
+ UNIT_ASSERT(!TListType::THave<TB>::value);
+ UNIT_ASSERT(TListType::THave<const TC&>::value);
+ UNIT_ASSERT(!TListType::THave<TC&>::value);
+ }
+
+ template <class T>
+ class TT {};
+
+ template <class T>
+ struct TIs1ArgTemplate: std::false_type {};
+
+ template <class T, template <class> class TT>
+ struct TIs1ArgTemplate<TT<T>>: std::true_type {};
+
+ template <class T>
+ struct TIsNArgTemplate: std::false_type {};
+
+ template <template <class...> class TT, class... R>
+ struct TIsNArgTemplate<TT<R...>>: std::true_type {};
+
+ template <class>
+ struct TAnyType: std::true_type {};
+
+ template <class T>
+ struct TMyVector {};
+
+ template <class T1, class T2>
+ struct TMyMap {};
+
+ void TestSelectBy() {
+ using TListType = TTypeList<TA, TB, TMyMap<TA*, TB>, TMyVector<TA>, TC>;
+
+ UNIT_ASSERT_TYPES_EQUAL(TListType::TSelectBy<TAnyType>::type, TA);
+ UNIT_ASSERT_TYPES_EQUAL(TListType::TSelectBy<TIs1ArgTemplate>::type, TMyVector<TA>);
+ using TMyMapPTATB = TMyMap<TA*, TB>;
+ UNIT_ASSERT_TYPES_EQUAL(TListType::TSelectBy<TIsNArgTemplate>::type, TMyMapPTATB);
+ }
+
+ void TestFloatList() {
+ UNIT_ASSERT_TYPES_EQUAL(TFixedWidthFloat<ui32>, float);
+ UNIT_ASSERT_TYPES_EQUAL(TFixedWidthFloat<i32>, float);
+ UNIT_ASSERT_TYPES_EQUAL(TFixedWidthFloat<ui64>, double);
+ UNIT_ASSERT_TYPES_EQUAL(TFixedWidthFloat<i64>, double);
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TTypeListTest);
diff --git a/util/generic/typetraits.cpp b/util/generic/typetraits.cpp
new file mode 100644
index 0000000000..7eadd13559
--- /dev/null
+++ b/util/generic/typetraits.cpp
@@ -0,0 +1 @@
+#include "typetraits.h"
diff --git a/util/generic/typetraits.h b/util/generic/typetraits.h
new file mode 100644
index 0000000000..d165bd1a06
--- /dev/null
+++ b/util/generic/typetraits.h
@@ -0,0 +1,320 @@
+#pragma once
+
+#include "va_args.h"
+
+#include <util/system/defaults.h>
+
+#include <iterator>
+#include <type_traits>
+#include <stlfwd>
+
+#if _LIBCPP_STD_VER >= 17
+template <bool B>
+using TBoolConstant = std::bool_constant<B>;
+#else
+template <bool B>
+struct TBoolConstant: std::integral_constant<bool, B> {};
+#endif
+
+#if _LIBCPP_STD_VER >= 17
+template <class B>
+using TNegation = std::negation<B>;
+#else
+template <class B>
+struct TNegation: ::TBoolConstant<!bool(B::value)> {};
+#endif
+
+namespace NPrivate {
+ template <class... Bs>
+ constexpr bool ConjunctionImpl() {
+ bool bs[] = {(bool)Bs::value...};
+ for (auto b : bs) {
+ if (!b) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ template <class... Bs>
+ constexpr bool DisjunctionImpl() {
+ bool bs[] = {(bool)Bs::value...};
+ for (auto b : bs) {
+ if (b) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+#if _LIBCPP_STD_VER >= 17 && !defined(_MSC_VER)
+// Disable std::conjunction for MSVC by analogy with std::disjunction.
+template <class... Bs>
+using TConjunction = std::conjunction<Bs...>;
+#else
+template <class... Bs>
+struct TConjunction: ::TBoolConstant<::NPrivate::ConjunctionImpl<Bs...>()> {};
+#endif
+
+#if _LIBCPP_STD_VER >= 17 && !defined(_MSC_VER)
+// Disable std::disjunction for MSVC.
+// It reduces build time (500 -> 20 seconds) and memory consumption (20 GB -> less than 1 GB)
+// for some files (notably search/dssm_boosting/dssm_boosting_calcer.cpp).
+template <class... Bs>
+using TDisjunction = std::disjunction<Bs...>;
+#else
+template <class... Bs>
+struct TDisjunction: ::TBoolConstant<::NPrivate::DisjunctionImpl<Bs...>()> {};
+#endif
+
+#if _LIBCPP_STD_VER >= 17
+template <class... Bs>
+using TVoidT = std::void_t<Bs...>;
+#else
+template <class...>
+using TVoidT = void;
+#endif
+
+template <class T>
+struct TPodTraits {
+ enum {
+ IsPod = false
+ };
+};
+
+template <class T>
+class TTypeTraitsBase {
+public:
+ static constexpr bool IsPod = (TPodTraits<std::remove_cv_t<T>>::IsPod || std::is_scalar<std::remove_all_extents_t<T>>::value ||
+ TPodTraits<std::remove_cv_t<std::remove_all_extents_t<T>>>::IsPod);
+};
+
+namespace NPrivate {
+ template <class T>
+ struct TIsSmall: std::integral_constant<bool, (sizeof(T) <= sizeof(void*))> {};
+}
+
+template <class T>
+class TTypeTraits: public TTypeTraitsBase<T> {
+ using TBase = TTypeTraitsBase<T>;
+
+ /*
+ * can be effectively passed to function as value
+ */
+ static constexpr bool IsValueType = std::is_scalar<T>::value ||
+ std::is_array<T>::value ||
+ std::is_reference<T>::value ||
+ (TBase::IsPod &&
+ std::conditional_t<
+ std::is_function<T>::value,
+ std::false_type,
+ ::NPrivate::TIsSmall<T>>::value);
+
+public:
+ /*
+ * can be used in function templates for effective parameters passing
+ */
+ using TFuncParam = std::conditional_t<IsValueType, T, const std::remove_reference_t<T>&>;
+};
+
+template <>
+class TTypeTraits<void>: public TTypeTraitsBase<void> {};
+
+#define Y_DECLARE_PODTYPE(type) \
+ template <> \
+ struct TPodTraits<type> { \
+ enum { IsPod = true }; \
+ }
+
+#define Y_HAS_MEMBER_IMPL_2(method, name) \
+ template <class T> \
+ struct TClassHas##name { \
+ struct TBase { \
+ void method(); \
+ }; \
+ class THelper: public T, public TBase { \
+ public: \
+ template <class T1> \
+ inline THelper(const T1& = T1()) { \
+ } \
+ }; \
+ template <class T1, T1 val> \
+ class TChecker {}; \
+ struct TNo { \
+ char ch; \
+ }; \
+ struct TYes { \
+ char arr[2]; \
+ }; \
+ template <class T1> \
+ static TNo CheckMember(T1*, TChecker<void (TBase::*)(), &T1::method>* = nullptr); \
+ static TYes CheckMember(...); \
+ static constexpr bool value = \
+ (sizeof(TYes) == sizeof(CheckMember((THelper*)nullptr))); \
+ }; \
+ template <class T, bool isClassType> \
+ struct TBaseHas##name: std::false_type {}; \
+ template <class T> \
+ struct TBaseHas##name<T, true> \
+ : std::integral_constant<bool, TClassHas##name<T>::value> {}; \
+ template <class T> \
+ struct THas##name \
+ : TBaseHas##name<T, std::is_class<T>::value || std::is_union<T>::value> {}
+
+#define Y_HAS_MEMBER_IMPL_1(name) Y_HAS_MEMBER_IMPL_2(name, name)
+
+/* @def Y_HAS_MEMBER
+ *
+ * This macro should be used to define compile-time introspection helper classes for template
+ * metaprogramming.
+ *
+ * Macro accept one or two parameters, when used with two parameters e.g. `Y_HAS_MEMBER(xyz, ABC)`
+ * will define class `THasABC` with static member `value` of type bool. Usage with one parameter
+ * e.g. `Y_HAS_MEMBER(xyz)` will produce the same result as `Y_HAS_MEMBER(xyz, xyz)`.
+ *
+ * @code
+ * #include <type_traits>
+ *
+ * Y_HAS_MEMBER(push_front, PushFront);
+ *
+ * template <typename T, typename U>
+ * std::enable_if_t<THasPushFront<T>::value, void>
+ * PushFront(T& container, const U value) {
+ * container.push_front(x);
+ * }
+ *
+ * template <typename T, typename U>
+ * std::enable_if_t<!THasPushFront<T>::value, void>
+ * PushFront(T& container, const U value) {
+ * container.insert(container.begin(), x);
+ * }
+ * @endcode
+ */
+#define Y_HAS_MEMBER(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, Y_HAS_MEMBER_IMPL_2, Y_HAS_MEMBER_IMPL_1)(__VA_ARGS__))
+
+#define Y_HAS_SUBTYPE_IMPL_2(subtype, name) \
+ template <class T, class = void> \
+ struct THas##name: std::false_type {}; \
+ template <class T> \
+ struct THas##name<T, ::TVoidT<typename T::subtype>>: std::true_type {};
+
+#define Y_HAS_SUBTYPE_IMPL_1(name) Y_HAS_SUBTYPE_IMPL_2(name, name)
+
+/* @def Y_HAS_SUBTYPE
+ *
+ * This macro should be used to define compile-time introspection helper classes for template
+ * metaprogramming.
+ *
+ * Macro accept one or two parameters, when used with two parameters e.g. `Y_HAS_SUBTYPE(xyz, ABC)`
+ * will define class `THasABC` with static member `value` of type bool. Usage with one parameter
+ * e.g. `Y_HAS_SUBTYPE(xyz)` will produce the same result as `Y_HAS_SUBTYPE(xyz, xyz)`.
+ *
+ * @code
+ * Y_HAS_MEMBER(find, FindMethod);
+ * Y_HAS_SUBTYPE(const_iterator, ConstIterator);
+ * Y_HAS_SUBTYPE(key_type, KeyType);
+ *
+ * template <typename T>
+ * using TIsAssocCont = std::conditional_t<
+ * THasFindMethod<T>::value && THasConstIterator<T>::value && THasKeyType<T>::value,
+ * std::true_type,
+ * std::false_type,
+ * >;
+ *
+ * static_assert(TIsAssocCont<TVector<int>>::value == false, "");
+ * static_assert(TIsAssocCont<THashMap<int>>::value == true, "");
+ * @endcode
+ */
+#define Y_HAS_SUBTYPE(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, Y_HAS_SUBTYPE_IMPL_2, Y_HAS_SUBTYPE_IMPL_1)(__VA_ARGS__))
+
+template <class T1, class T2>
+struct TPodTraits<std::pair<T1, T2>> {
+ enum {
+ IsPod = TTypeTraits<T1>::IsPod && TTypeTraits<T2>::IsPod
+ };
+};
+
+template <class T>
+struct TIsPointerToConstMemberFunction: std::false_type {
+};
+
+template <class R, class T, class... Args>
+struct TIsPointerToConstMemberFunction<R (T::*)(Args...) const>: std::true_type {
+};
+
+template <class R, class T, class... Args>
+struct TIsPointerToConstMemberFunction<R (T::*)(Args...) const&>: std::true_type {
+};
+
+template <class R, class T, class... Args>
+struct TIsPointerToConstMemberFunction<R (T::*)(Args...) const&&>: std::true_type {
+};
+
+template <class R, class T, class... Args>
+struct TIsPointerToConstMemberFunction<R (T::*)(Args..., ...) const>: std::true_type {
+};
+
+template <class R, class T, class... Args>
+struct TIsPointerToConstMemberFunction<R (T::*)(Args..., ...) const&>: std::true_type {
+};
+
+template <class R, class T, class... Args>
+struct TIsPointerToConstMemberFunction<R (T::*)(Args..., ...) const&&>: std::true_type {
+};
+
+template <template <class...> class T, class U>
+struct TIsSpecializationOf: std::false_type {};
+
+template <template <class...> class T, class... Ts>
+struct TIsSpecializationOf<T, T<Ts...>>: std::true_type {};
+
+/*
+ * TDependentFalse is a constant dependent on a template parameter.
+ * Use it in static_assert in a false branch of if constexpr to produce a compile error.
+ * See an example with dependent_false at https://en.cppreference.com/w/cpp/language/if
+ *
+ * if constexpr (std::is_same<T, someType1>) {
+ * } else if constexpr (std::is_same<T, someType2>) {
+ * } else {
+ * static_assert(TDependentFalse<T>, "unknown type");
+ * }
+ */
+template <typename... T>
+constexpr bool TDependentFalse = false;
+
+// FIXME: neither nvcc10 nor nvcc11 support using auto in this context
+#if defined(__NVCC__)
+template <size_t Value>
+constexpr bool TValueDependentFalse = false;
+#else
+template <auto... Values>
+constexpr bool TValueDependentFalse = false;
+#endif
+
+/*
+ * shortcut for std::enable_if_t<...> which checks that T is std::tuple or std::pair
+ */
+template <class T, class R = void>
+using TEnableIfTuple = std::enable_if_t<::TDisjunction<::TIsSpecializationOf<std::tuple, std::decay_t<T>>,
+ ::TIsSpecializationOf<std::pair, std::decay_t<T>>>::value,
+ R>;
+
+namespace NPrivate {
+ // To allow ADL with custom begin/end
+ using std::begin;
+ using std::end;
+
+ template <typename T>
+ auto IsIterableImpl(int) -> decltype(
+ begin(std::declval<T&>()) != end(std::declval<T&>()), // begin/end and operator !=
+ ++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
+ *begin(std::declval<T&>()), // operator*
+ std::true_type{});
+
+ template <typename T>
+ std::false_type IsIterableImpl(...);
+}
+
+template <typename T>
+using TIsIterable = decltype(NPrivate::IsIterableImpl<T>(0));
diff --git a/util/generic/typetraits_ut.cpp b/util/generic/typetraits_ut.cpp
new file mode 100644
index 0000000000..e7571c75ec
--- /dev/null
+++ b/util/generic/typetraits_ut.cpp
@@ -0,0 +1,468 @@
+#include "typetraits.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <vector>
+#include <tuple>
+
+namespace {
+ enum ETestEnum {
+ };
+
+ class TPodClass {
+ };
+
+ class TNonPodClass {
+ TNonPodClass() {
+ }
+ };
+
+ class TEmptyClass {
+ void operator()() const {
+ }
+ };
+
+ class TAnotherEmptyClass {
+ };
+
+ class TEmptyDerivedClass: public TEmptyClass {
+ };
+
+ class TEmptyMultiDerivedClass: public TEmptyDerivedClass, public TAnotherEmptyClass {
+ /* Not empty under MSVC.
+ * MSVC's EBCO implementation can handle only one empty base class. */
+ };
+
+ struct TNonEmptyClass {
+ TEmptyClass member;
+ };
+
+ class TNonEmptyDerivedClass: public TNonEmptyClass {
+ };
+
+ class TStdLayoutClass1: public TEmptyClass {
+ public:
+ int Value1;
+ int Value2;
+ };
+
+ class TStdLayoutClass2: public TNonEmptyClass {
+ };
+
+ class TNonStdLayoutClass1 {
+ public:
+ int Value1;
+
+ protected:
+ int Value2;
+ };
+
+ class TNonStdLayoutClass2 {
+ public:
+ virtual void Func() {
+ }
+ };
+
+ class TNonStdLayoutClass3: public TNonStdLayoutClass2 {
+ };
+
+ class TNonStdLayoutClass4: public TEmptyClass {
+ public:
+ TEmptyClass Base;
+ };
+}
+
+#define ASSERT_SAME_TYPE(x, y) \
+ { \
+ const bool x_ = std::is_same<x, y>::value; \
+ UNIT_ASSERT_C(x_, #x " != " #y); \
+ }
+
+Y_UNIT_TEST_SUITE(TTypeTraitsTest) {
+ Y_UNIT_TEST(TestIsSame) {
+ UNIT_ASSERT((std::is_same<int, int>::value));
+ UNIT_ASSERT(!(std::is_same<signed int, unsigned int>::value));
+ }
+
+ Y_UNIT_TEST(TestRemoveReference) {
+ ASSERT_SAME_TYPE(std::remove_reference_t<int>, int);
+ ASSERT_SAME_TYPE(std::remove_reference_t<const int>, const int);
+ ASSERT_SAME_TYPE(std::remove_reference_t<int&>, int);
+ ASSERT_SAME_TYPE(std::remove_reference_t<const int&>, const int);
+ ASSERT_SAME_TYPE(std::remove_reference_t<int&&>, int);
+ ASSERT_SAME_TYPE(std::remove_reference_t<const int&&>, const int);
+
+ class TIncompleteType;
+ ASSERT_SAME_TYPE(std::remove_reference_t<TIncompleteType&>, TIncompleteType);
+ }
+
+ Y_UNIT_TEST(TestRemoveConst) {
+ ASSERT_SAME_TYPE(std::remove_const_t<const int>, int);
+ }
+
+ Y_UNIT_TEST(TestRemoveVolatile) {
+ ASSERT_SAME_TYPE(std::remove_volatile_t<volatile int>, int);
+ }
+
+ Y_UNIT_TEST(TestRemoveCV) {
+ ASSERT_SAME_TYPE(std::remove_cv_t<const volatile int>, int);
+ }
+
+ Y_UNIT_TEST(TestAddCV) {
+ ASSERT_SAME_TYPE(std::add_cv_t<int>, const volatile int);
+ }
+
+ Y_UNIT_TEST(TestClass) {
+ UNIT_ASSERT(std::is_class<TString>::value);
+ UNIT_ASSERT(!std::is_class<ETestEnum>::value);
+ UNIT_ASSERT(!std::is_class<int>::value);
+ UNIT_ASSERT(!std::is_class<void*>::value);
+ }
+
+ template <class T>
+ inline void TestArithmeticType() {
+ UNIT_ASSERT(std::is_arithmetic<T>::value);
+ UNIT_ASSERT(std::is_arithmetic<const T>::value);
+ UNIT_ASSERT(std::is_arithmetic<volatile T>::value);
+ UNIT_ASSERT(std::is_arithmetic<const volatile T>::value);
+
+ UNIT_ASSERT(!std::is_arithmetic<T&>::value);
+ UNIT_ASSERT(!std::is_arithmetic<T&&>::value);
+ UNIT_ASSERT(!std::is_arithmetic<T*>::value);
+
+ bool a;
+
+ a = std::is_same<typename TTypeTraits<T>::TFuncParam, T>::value;
+ UNIT_ASSERT(a);
+ a = std::is_same<typename TTypeTraits<const volatile T>::TFuncParam, const volatile T>::value;
+ UNIT_ASSERT(a);
+ }
+
+ template <class T>
+ inline void TestUnsignedIntType() {
+ UNIT_ASSERT(std::is_unsigned<T>::value);
+ UNIT_ASSERT(std::is_unsigned<const T>::value);
+ UNIT_ASSERT(std::is_unsigned<volatile T>::value);
+ UNIT_ASSERT(std::is_unsigned<const volatile T>::value);
+
+ UNIT_ASSERT(!std::is_unsigned<T&>::value);
+ UNIT_ASSERT(!std::is_unsigned<T&&>::value);
+ UNIT_ASSERT(!std::is_unsigned<T*>::value);
+
+ enum ETypedEnum: T {};
+ UNIT_ASSERT(!std::is_unsigned<ETypedEnum>::value);
+ }
+
+ template <class T>
+ inline void TestSignedIntType() {
+ UNIT_ASSERT(std::is_signed<T>::value);
+ UNIT_ASSERT(std::is_signed<const T>::value);
+ UNIT_ASSERT(std::is_signed<volatile T>::value);
+ UNIT_ASSERT(std::is_signed<const volatile T>::value);
+
+ UNIT_ASSERT(!std::is_signed<T&>::value);
+ UNIT_ASSERT(!std::is_signed<T&&>::value);
+ UNIT_ASSERT(!std::is_signed<T*>::value);
+
+ enum ETypedEnum: T {};
+ UNIT_ASSERT(!std::is_signed<ETypedEnum>::value);
+ }
+
+ Y_UNIT_TEST(TestBool) {
+ TestArithmeticType<bool>();
+ TestUnsignedIntType<bool>();
+ }
+
+ Y_UNIT_TEST(TestUnsignedChar) {
+ TestArithmeticType<unsigned char>();
+ TestUnsignedIntType<unsigned char>();
+ }
+
+ Y_UNIT_TEST(TestSizeT) {
+ TestArithmeticType<size_t>();
+ TestUnsignedIntType<size_t>();
+ }
+
+ Y_UNIT_TEST(TestInt) {
+ TestArithmeticType<int>();
+ TestSignedIntType<int>();
+ }
+
+ Y_UNIT_TEST(TestDouble) {
+ TestArithmeticType<double>();
+ }
+
+ Y_UNIT_TEST(TestLongDouble) {
+ TestArithmeticType<long double>();
+ }
+
+ Y_UNIT_TEST(TestAddRValueReference) {
+ ASSERT_SAME_TYPE(std::add_rvalue_reference_t<int>, int&&);
+ ASSERT_SAME_TYPE(std::add_rvalue_reference_t<int const&>, int const&);
+ ASSERT_SAME_TYPE(std::add_rvalue_reference_t<int*>, int*&&);
+ ASSERT_SAME_TYPE(std::add_rvalue_reference_t<int*&>, int*&);
+ ASSERT_SAME_TYPE(std::add_rvalue_reference_t<int&&>, int&&);
+ ASSERT_SAME_TYPE(std::add_rvalue_reference_t<void>, void);
+ }
+
+ Y_UNIT_TEST(TestIsEmpty) {
+ UNIT_ASSERT(std::is_empty<TEmptyClass>::value);
+ UNIT_ASSERT(std::is_empty<TEmptyDerivedClass>::value);
+ UNIT_ASSERT(std::is_empty<TAnotherEmptyClass>::value);
+#ifdef _MSC_VER
+ UNIT_ASSERT(!std::is_empty<TEmptyMultiDerivedClass>::value);
+#else
+ UNIT_ASSERT(std::is_empty<TEmptyMultiDerivedClass>::value);
+#endif
+ UNIT_ASSERT(!std::is_empty<TNonEmptyClass>::value);
+ UNIT_ASSERT(!std::is_empty<TNonEmptyDerivedClass>::value);
+ }
+
+ Y_UNIT_TEST(TestIsStandardLayout) {
+ UNIT_ASSERT(std::is_standard_layout<TStdLayoutClass1>::value);
+ UNIT_ASSERT(std::is_standard_layout<TStdLayoutClass2>::value);
+ UNIT_ASSERT(!std::is_standard_layout<TNonStdLayoutClass1>::value);
+ UNIT_ASSERT(!std::is_standard_layout<TNonStdLayoutClass2>::value);
+ UNIT_ASSERT(!std::is_standard_layout<TNonStdLayoutClass3>::value);
+ UNIT_ASSERT(!std::is_standard_layout<TNonStdLayoutClass4>::value);
+ }
+
+ template <class T>
+ using TTrySum = decltype(std::declval<T>() + std::declval<T>());
+
+ Y_UNIT_TEST(TestIsTriviallyCopyable) {
+ struct TPod {
+ int value;
+ };
+
+ struct TNontriviallyCopyAssignable {
+ TNontriviallyCopyAssignable(const TNontriviallyCopyAssignable&) = default;
+ TNontriviallyCopyAssignable& operator=(const TNontriviallyCopyAssignable&);
+ };
+
+ struct TNonTriviallyCopyConstructible {
+ TNonTriviallyCopyConstructible(const TNonTriviallyCopyConstructible&);
+ TNonTriviallyCopyConstructible& operator=(const TNonTriviallyCopyConstructible&) = default;
+ };
+
+ struct TNonTriviallyDestructible {
+ TNonTriviallyDestructible(const TNonTriviallyDestructible&) = default;
+ TNonTriviallyDestructible& operator=(const TNonTriviallyDestructible&) = default;
+ ~TNonTriviallyDestructible();
+ };
+
+ UNIT_ASSERT(std::is_trivially_copyable<int>::value);
+ UNIT_ASSERT(std::is_trivially_copyable<TPod>::value);
+ UNIT_ASSERT(!std::is_trivially_copyable<TNontriviallyCopyAssignable>::value);
+ UNIT_ASSERT(!std::is_trivially_copyable<TNonTriviallyCopyConstructible>::value);
+ UNIT_ASSERT(!std::is_trivially_copyable<TNonTriviallyDestructible>::value);
+ }
+};
+
+namespace {
+ template <typename T>
+ struct TTypeTraitsExpected;
+
+ template <>
+ struct TTypeTraitsExpected<void> {
+ enum { IsIntegral = false };
+ enum { IsArithmetic = false };
+ enum { IsPod = true };
+ enum { IsVolatile = false };
+ enum { IsConstant = false };
+ enum { IsPointer = false };
+ enum { IsReference = false };
+ enum { IsLvalueReference = false };
+ enum { IsRvalueReference = false };
+ enum { IsArray = false };
+ enum { IsClassType = false };
+ enum { IsVoid = true };
+ enum { IsEnum = false };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<int>: public TTypeTraitsExpected<void> {
+ enum { IsIntegral = true };
+ enum { IsArithmetic = true };
+ enum { IsVoid = false };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<size_t>: public TTypeTraitsExpected<int> {
+ };
+
+ template <>
+ struct TTypeTraitsExpected<float>: public TTypeTraitsExpected<int> {
+ enum { IsIntegral = false };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<long double>: public TTypeTraitsExpected<float> {
+ };
+
+ template <>
+ struct TTypeTraitsExpected<const int>: public TTypeTraitsExpected<int> {
+ enum { IsConstant = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<volatile int>: public TTypeTraitsExpected<int> {
+ enum { IsVolatile = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<ETestEnum>: public TTypeTraitsExpected<int> {
+ enum { IsIntegral = false };
+ enum { IsArithmetic = false };
+ enum { IsEnum = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<TPodClass>: public TTypeTraitsExpected<void> {
+ enum { IsClassType = true };
+ enum { IsVoid = false };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<TNonPodClass>: public TTypeTraitsExpected<TPodClass> {
+ enum { IsPod = false };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<TNonPodClass&>: public TTypeTraitsExpected<TNonPodClass> {
+ enum { IsClassType = false };
+ enum { IsReference = true };
+ enum { IsLvalueReference = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<TNonPodClass&&>: public TTypeTraitsExpected<TNonPodClass> {
+ enum { IsClassType = false };
+ enum { IsReference = true };
+ enum { IsRvalueReference = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<const TNonPodClass&>: public TTypeTraitsExpected<TNonPodClass&> {
+ };
+
+ template <>
+ struct TTypeTraitsExpected<float*>: public TTypeTraitsExpected<int> {
+ enum { IsIntegral = false };
+ enum { IsArithmetic = false };
+ enum { IsPointer = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<float&>: public TTypeTraitsExpected<float*> {
+ enum { IsPointer = false };
+ enum { IsReference = true };
+ enum { IsLvalueReference = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<float&&>: public TTypeTraitsExpected<float*> {
+ enum { IsPointer = false };
+ enum { IsReference = true };
+ enum { IsRvalueReference = true };
+ };
+
+ template <>
+ struct TTypeTraitsExpected<const float&>: public TTypeTraitsExpected<float&> {
+ };
+
+ template <>
+ struct TTypeTraitsExpected<float[17]>: public TTypeTraitsExpected<int> {
+ enum { IsIntegral = false };
+ enum { IsArithmetic = false };
+ enum { IsArray = true };
+ };
+}
+
+#define UNIT_ASSERT_EQUAL_ENUM(expected, actual) UNIT_ASSERT_VALUES_EQUAL((bool)(expected), (bool)(actual))
+
+Y_UNIT_TEST_SUITE(TTypeTraitsTestNg) {
+ template <typename T>
+ void TestImpl() {
+ //UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsPod, TTypeTraits<T>::IsPod);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsVoid, std::is_void<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsEnum, std::is_enum<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsIntegral, std::is_integral<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsArithmetic, std::is_arithmetic<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsVolatile, std::is_volatile<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsConstant, std::is_const<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsPointer, std::is_pointer<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsReference, std::is_reference<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsLvalueReference, std::is_lvalue_reference<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsRvalueReference, std::is_rvalue_reference<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsArray, std::is_array<T>::value);
+ UNIT_ASSERT_EQUAL_ENUM(TTypeTraitsExpected<T>::IsClassType, std::is_class<T>::value);
+ }
+
+#define TYPE_TEST(name, type) \
+ Y_UNIT_TEST(name) { \
+ TestImpl<type>(); \
+ }
+
+ TYPE_TEST(Void, void)
+ TYPE_TEST(Int, int)
+ TYPE_TEST(Float, float)
+ TYPE_TEST(LongDouble, long double)
+ TYPE_TEST(SizeT, size_t)
+ TYPE_TEST(VolatileInt, volatile int)
+ TYPE_TEST(ConstInt, const int)
+ TYPE_TEST(Enum, ETestEnum)
+ TYPE_TEST(FloatPointer, float*)
+ TYPE_TEST(FloatReference, float&)
+ TYPE_TEST(FloatConstReference, const float&)
+ TYPE_TEST(FloatArray, float[17])
+ TYPE_TEST(PodClass, TPodClass)
+ TYPE_TEST(NonPodClass, TNonPodClass)
+ TYPE_TEST(NonPodClassReference, TNonPodClass&)
+ TYPE_TEST(NonPodClassConstReference, const TNonPodClass&)
+}
+
+enum E4 {
+ X
+};
+
+enum class E64: ui64 {
+ X
+};
+
+enum class E8: ui8 {
+ X
+};
+
+// test for std::underlying_type_t
+static_assert(sizeof(std::underlying_type_t<E4>) == sizeof(int), "");
+static_assert(sizeof(std::underlying_type_t<E64>) == sizeof(ui64), "");
+static_assert(sizeof(std::underlying_type_t<E8>) == sizeof(ui8), "");
+
+// tests for TFixedWidthUnsignedInt
+static_assert(std::is_same<ui8, TFixedWidthUnsignedInt<i8>>::value, "");
+static_assert(std::is_same<ui16, TFixedWidthUnsignedInt<i16>>::value, "");
+static_assert(std::is_same<ui32, TFixedWidthUnsignedInt<i32>>::value, "");
+static_assert(std::is_same<ui64, TFixedWidthUnsignedInt<i64>>::value, "");
+
+// tests for TFixedWidthSignedInt
+static_assert(std::is_same<i8, TFixedWidthSignedInt<ui8>>::value, "");
+static_assert(std::is_same<i16, TFixedWidthSignedInt<ui16>>::value, "");
+static_assert(std::is_same<i32, TFixedWidthSignedInt<ui32>>::value, "");
+static_assert(std::is_same<i64, TFixedWidthSignedInt<ui64>>::value, "");
+
+// test for TIsSpecializationOf
+static_assert(TIsSpecializationOf<std::vector, std::vector<int>>::value, "");
+static_assert(TIsSpecializationOf<std::tuple, std::tuple<int, double, char>>::value, "");
+static_assert(!TIsSpecializationOf<std::vector, std::tuple<int, double, char>>::value, "");
+static_assert(!TIsSpecializationOf<std::pair, std::vector<int>>::value, "");
+
+// test for TIsIterable
+static_assert(TIsIterable<std::vector<int>>::value, "");
+static_assert(!TIsIterable<int>::value, "");
+static_assert(TIsIterable<int[42]>::value, "");
+
+// test for TDependentFalse
+static_assert(TDependentFalse<int> == false);
+static_assert(TDependentFalse<TNonPodClass> == false);
+static_assert(TValueDependentFalse<0x1000> == false);
diff --git a/util/generic/ut/ya.make b/util/generic/ut/ya.make
new file mode 100644
index 0000000000..6eaf24cc5f
--- /dev/null
+++ b/util/generic/ut/ya.make
@@ -0,0 +1,73 @@
+UNITTEST_FOR(util)
+
+OWNER(g:util)
+SUBSCRIBER(g:util-subscribers)
+
+FORK_TESTS()
+
+SRCS(
+ generic/adaptor_ut.cpp
+ generic/algorithm_ut.cpp
+ generic/array_ref_ut.cpp
+ generic/array_size_ut.cpp
+ generic/bitmap_ut.cpp
+ generic/bitops_ut.cpp
+ generic/buffer_ut.cpp
+ generic/cast_ut.cpp
+ generic/deque_ut.cpp
+ generic/explicit_type_ut.cpp
+ generic/flags_ut.cpp
+ generic/function_ut.cpp
+ generic/guid_ut.cpp
+ generic/hash_primes_ut.cpp
+ generic/hash_ut.cpp
+ generic/intrlist_ut.cpp
+ generic/is_in_ut.cpp
+ generic/iterator_ut.cpp
+ generic/iterator_range_ut.cpp
+ generic/lazy_value_ut.cpp
+ generic/list_ut.cpp
+ generic/map_ut.cpp
+ generic/mapfindptr_ut.cpp
+ generic/maybe_ut.cpp
+ generic/mem_copy_ut.cpp
+ generic/objects_counter_ut.cpp
+ generic/overloaded_ut.cpp
+ generic/ptr_ut.cpp
+ generic/queue_ut.cpp
+ generic/serialized_enum_ut.cpp
+ generic/set_ut.cpp
+ generic/singleton_ut.cpp
+ generic/size_literals_ut.cpp
+ generic/stack_ut.cpp
+ generic/store_policy_ut.cpp
+ generic/strbuf_ut.cpp
+ generic/string_ut.cpp
+ generic/typelist_ut.cpp
+ generic/typetraits_ut.cpp
+ generic/utility_ut.cpp
+ generic/va_args_ut.cpp
+ generic/vector_ut.cpp
+ generic/xrange_ut.cpp
+ generic/yexception_ut.c
+ generic/yexception_ut.cpp
+ generic/ylimits_ut.cpp
+ generic/ymath_ut.cpp
+ generic/scope_ut.cpp
+)
+
+INCLUDE(${ARCADIA_ROOT}/util/tests/ya_util_tests.inc)
+
+IF (NOT OS_IOS AND NOT ARCH_PPC64LE)
+ # Abseil fails to build (with linkage error) on ios and with compilation error on PowerPC
+ # (somewhere in unscaledcycleclock.cc).
+ PEERDIR(
+ library/cpp/containers/absl_flat_hash
+ )
+
+ SRCS(
+ generic/string_transparent_hash_ut.cpp
+ )
+ENDIF()
+
+END()
diff --git a/util/generic/utility.cpp b/util/generic/utility.cpp
new file mode 100644
index 0000000000..e1ed101c76
--- /dev/null
+++ b/util/generic/utility.cpp
@@ -0,0 +1,21 @@
+#include "utility.h"
+
+#ifdef _MSC_VER
+ #include <Windows.h>
+#endif
+
+void SecureZero(void* pointer, size_t count) noexcept {
+#ifdef _MSC_VER
+ SecureZeroMemory(pointer, count);
+#elif defined(memset_s)
+ memset_s(pointer, count, 0, count);
+#else
+ volatile char* vPointer = (volatile char*)pointer;
+
+ while (count) {
+ *vPointer = 0;
+ vPointer++;
+ count--;
+ }
+#endif
+}
diff --git a/util/generic/utility.h b/util/generic/utility.h
new file mode 100644
index 0000000000..43b98eeafc
--- /dev/null
+++ b/util/generic/utility.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "typetraits.h"
+
+#include <cstring>
+
+template <class T>
+static constexpr const T& Min(const T& l, const T& r) {
+ return r < l ? r : l;
+}
+
+template <typename T, typename... Args>
+static constexpr const T& Min(const T& a, const T& b, const Args&... args) {
+ return Min(a, Min(b, args...));
+}
+
+template <class T>
+static constexpr const T& Max(const T& l, const T& r) {
+ return l < r ? r : l;
+}
+
+template <typename T, typename... Args>
+static constexpr const T& Max(const T& a, const T& b, const Args&... args) {
+ return Max(a, Max(b, args...));
+}
+
+// replace with http://en.cppreference.com/w/cpp/algorithm/clamp in c++17
+template <class T>
+constexpr const T& ClampVal(const T& val, const T& min, const T& max) {
+ return val < min ? min : (max < val ? max : val);
+}
+
+template <typename T = double, typename... Args>
+static T Mean(const Args&... other) noexcept {
+ const auto numArgs = sizeof...(other);
+
+ auto sum = T();
+ for (const auto& v : {other...}) {
+ sum += v;
+ }
+
+ return sum / numArgs;
+}
+
+template <class T>
+static inline void Zero(T& t) noexcept {
+ memset((void*)&t, 0, sizeof(t));
+}
+
+/**
+ * Securely zero memory (compiler does not optimize this out)
+ *
+ * @param pointer void pointer to start of memory block to be zeroed
+ * @param count size of memory block to be zeroed (in bytes)
+ */
+void SecureZero(void* pointer, size_t count) noexcept;
+
+/**
+ * Securely zero memory of given object (compiler does not optimize this out)
+ *
+ * @param t reference to object, which must be zeroed
+ */
+template <class T>
+static inline void SecureZero(T& t) noexcept {
+ SecureZero((void*)&t, sizeof(t));
+}
+
+namespace NSwapCheck {
+ Y_HAS_MEMBER(swap);
+ Y_HAS_MEMBER(Swap);
+
+ template <class T, class = void>
+ struct TSwapSelector {
+ static inline void Swap(T& l, T& r) noexcept(std::is_nothrow_move_constructible<T>::value&&
+ std::is_nothrow_move_assignable<T>::value) {
+ T tmp(std::move(l));
+ l = std::move(r);
+ r = std::move(tmp);
+ }
+ };
+
+ template <class T>
+ struct TSwapSelector<T, std::enable_if_t<THasSwap<T>::value>> {
+ static inline void Swap(T& l, T& r) noexcept(noexcept(l.Swap(r))) {
+ l.Swap(r);
+ }
+ };
+
+ template <class T>
+ struct TSwapSelector<T, std::enable_if_t<THasswap<T>::value && !THasSwap<T>::value>> {
+ static inline void Swap(T& l, T& r) noexcept(noexcept(l.swap(r))) {
+ l.swap(r);
+ }
+ };
+}
+
+/*
+ * DoSwap better than ::Swap in member functions...
+ */
+template <class T>
+static inline void DoSwap(T& l, T& r) noexcept(noexcept(NSwapCheck::TSwapSelector<T>::Swap(l, r))) {
+ NSwapCheck::TSwapSelector<T>::Swap(l, r);
+}
+
+template <bool b>
+struct TNullTmpl {
+ template <class T>
+ operator T() const {
+ return (T)0;
+ }
+};
+
+using TNull = TNullTmpl<0>;
+
+/*
+ * Class for zero-initialize padding bytes in derived classes
+ */
+template <typename TDerived>
+class TZeroInit {
+protected:
+ TZeroInit() {
+ // Actually, safe because this as TDerived is not initialized yet.
+ Zero(*static_cast<TDerived*>(this));
+ }
+};
+
+struct TIdentity {
+ template <class T>
+ constexpr decltype(auto) operator()(T&& x) const noexcept {
+ return std::forward<T>(x);
+ }
+};
diff --git a/util/generic/utility_ut.cpp b/util/generic/utility_ut.cpp
new file mode 100644
index 0000000000..8e9d5afff9
--- /dev/null
+++ b/util/generic/utility_ut.cpp
@@ -0,0 +1,180 @@
+#include "utility.h"
+#include "ymath.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+// DO_NOT_STYLE
+
+class TTest {
+public:
+ inline TTest(int val)
+ : Val(val)
+ {
+ }
+
+ inline void Swap(TTest& t) {
+ DoSwap(Val, t.Val);
+ }
+
+ int Val;
+
+private:
+ TTest(const TTest&);
+ TTest& operator=(const TTest&);
+};
+
+struct TUnorderedTag {
+ TStringBuf Tag;
+};
+
+static bool operator<(const TUnorderedTag, const TUnorderedTag) {
+ return false;
+}
+
+static bool operator>(const TUnorderedTag, const TUnorderedTag) = delete;
+
+Y_UNIT_TEST_SUITE(TUtilityTest) {
+
+ Y_UNIT_TEST(TestSwapPrimitive) {
+ int i = 0;
+ int j = 1;
+
+ DoSwap(i, j);
+
+ UNIT_ASSERT_EQUAL(i, 1);
+ UNIT_ASSERT_EQUAL(j, 0);
+ }
+
+ Y_UNIT_TEST(TestSwapClass) {
+ TTest i(0);
+ TTest j(1);
+
+ DoSwap(i, j);
+
+ UNIT_ASSERT_EQUAL(i.Val, 1);
+ UNIT_ASSERT_EQUAL(j.Val, 0);
+ }
+
+ Y_UNIT_TEST(TestMaxMin) {
+ static_assert(Min(10, 3, 8) == 3, "Min doesn't work");
+ static_assert(Max(10, 3, 8) == 10, "Max doesn't work");
+ UNIT_ASSERT_EQUAL(Min(10, 3, 8), 3);
+ UNIT_ASSERT_EQUAL(Max(3.5, 4.2, 8.1, 99.025, 0.33, 29.0), 99.025);
+
+ UNIT_ASSERT_VALUES_EQUAL(Min(TUnorderedTag{"first"}, TUnorderedTag{"second"}).Tag, "first");
+ UNIT_ASSERT_VALUES_EQUAL(Max(TUnorderedTag{"first"}, TUnorderedTag{"second"}).Tag, "first");
+ UNIT_ASSERT_VALUES_EQUAL(Min(TUnorderedTag{"first"}, TUnorderedTag{"second"}, TUnorderedTag{"third"}).Tag, "first");
+ UNIT_ASSERT_VALUES_EQUAL(Max(TUnorderedTag{"first"}, TUnorderedTag{"second"}, TUnorderedTag{"third"}).Tag, "first");
+ }
+
+ Y_UNIT_TEST(TestMean) {
+ UNIT_ASSERT_EQUAL(Mean(5), 5);
+ UNIT_ASSERT_EQUAL(Mean(1, 2, 3), 2);
+ UNIT_ASSERT_EQUAL(Mean(6, 5, 4), 5);
+ UNIT_ASSERT_EQUAL(Mean(1, 2), 1.5);
+ UNIT_ASSERT(Abs(Mean(1., 2., 7.5) - 3.5) < std::numeric_limits<double>::epsilon());
+ }
+
+ Y_UNIT_TEST(TestZeroInitWithDefaultZeros) {
+ struct TStructWithPaddingBytes: public TZeroInit<TStructWithPaddingBytes> {
+ TStructWithPaddingBytes()
+ : TZeroInit<TStructWithPaddingBytes>() {
+ }
+ bool Field1_ = static_cast<bool>(0);
+ // here between Field1_ and Field2_ will be padding bytes
+ i64 Field2_ = 0;
+ };
+
+ TStructWithPaddingBytes foo{};
+
+ // all bytes must be zeroes, and MSAN will not complain about reading from padding bytes
+ const char* const fooPtr = (char*)&foo;
+ for (size_t i = 0; i < sizeof(TStructWithPaddingBytes); ++i) {
+ const char byte = fooPtr[i];
+ UNIT_ASSERT_EQUAL(byte, 0);
+ }
+ }
+
+ Y_UNIT_TEST(TestZeroInitWithDefaultNonZeros) {
+ struct TStructWithPaddingBytes: public TZeroInit<TStructWithPaddingBytes> {
+ TStructWithPaddingBytes()
+ : TZeroInit<TStructWithPaddingBytes>() {
+ }
+ bool Field1_ = true;
+ // here between Field1_ and Field2_ will be padding bytes
+ i64 Field2_ = 100500;
+ };
+
+ TStructWithPaddingBytes foo{};
+
+ // check that default values are set correctly
+ UNIT_ASSERT_EQUAL(foo.Field1_, true);
+ UNIT_ASSERT_EQUAL(foo.Field2_, 100500);
+
+ const char* const fooPtr = (char*)&foo;
+ // just reading all bytes, and MSAN must not complain about reading padding bytes
+ for (size_t i = 0; i < sizeof(TStructWithPaddingBytes); ++i) {
+ const char byte = fooPtr[i];
+ UNIT_ASSERT_EQUAL(byte, byte);
+ }
+ }
+
+ Y_UNIT_TEST(TestClampValNoClamp) {
+ double val = 2;
+ double lo = 1;
+ double hi = 3;
+ const double& clamped = ClampVal(val, lo, hi);
+ UNIT_ASSERT_EQUAL(clamped, val);
+ UNIT_ASSERT_EQUAL(&clamped, &val);
+ }
+
+ Y_UNIT_TEST(TestClampValLo) {
+ double val = 2;
+ double lo = 3;
+ double hi = 4;
+ const double& clamped = ClampVal(val, lo, hi);
+ UNIT_ASSERT_EQUAL(clamped, lo);
+ UNIT_ASSERT_EQUAL(&clamped, &lo);
+ }
+
+ Y_UNIT_TEST(TestClampValHi) {
+ double val = 4;
+ double lo = 3;
+ double hi = 2;
+ const double& clamped = ClampVal(val, lo, hi);
+ UNIT_ASSERT_EQUAL(clamped, hi);
+ UNIT_ASSERT_EQUAL(&clamped, &hi);
+ }
+
+ Y_UNIT_TEST(TestSecureZero) {
+ constexpr size_t checkSize = 128;
+ char test[checkSize];
+
+ // fill with garbage
+ for (size_t i = 0; i < checkSize; ++i) {
+ test[i] = i;
+ }
+
+ SecureZero(test, checkSize);
+
+ for (size_t i = 0; i < checkSize; ++i) {
+ UNIT_ASSERT_EQUAL(test[i], 0);
+ }
+ }
+
+ Y_UNIT_TEST(TestSecureZeroTemplate) {
+ constexpr size_t checkSize = 128;
+ char test[checkSize];
+
+ // fill with garbage
+ for (size_t i = 0; i < checkSize; ++i) {
+ test[i] = i;
+ }
+
+ SecureZero(test);
+
+ for (size_t i = 0; i < checkSize; ++i) {
+ UNIT_ASSERT_EQUAL(test[i], 0);
+ }
+ }
+};
diff --git a/util/generic/va_args.cpp b/util/generic/va_args.cpp
new file mode 100644
index 0000000000..2266d05a0d
--- /dev/null
+++ b/util/generic/va_args.cpp
@@ -0,0 +1,15 @@
+#include "va_args.h"
+
+// Test that it compiles
+#define __DUMMY__(x)
+Y_MAP_ARGS(__DUMMY__, 1, 2, 3);
+#define __DUMMY_LAST__(x)
+Y_MAP_ARGS_WITH_LAST(__DUMMY__, __DUMMY_LAST__, 1, 2, 3);
+#undef __DUMMY_LAST__
+#undef __DUMMY__
+
+#define __MULTI_DUMMY__(x, y)
+#define __MULTI_DUMMY_PROXY__(x) __MULTI_DUMMY__ x
+Y_MAP_ARGS(__MULTI_DUMMY_PROXY__, (1, 2), (3, 4));
+#undef __MULTI_DUMMY_PROXY__
+#undef __MULTI_DUMMY__
diff --git a/util/generic/va_args.h b/util/generic/va_args.h
new file mode 100644
index 0000000000..33498d47ed
--- /dev/null
+++ b/util/generic/va_args.h
@@ -0,0 +1,791 @@
+#pragma once
+
+/// @file va_args.h
+///
+/// Some handy macros for preprocessor metaprogramming.
+
+// NOTE: this file has been generated with "./va_args_gen.py", do not edit -- use the generator instead
+
+// DO_NOT_STYLE
+
+#include <util/system/defaults.h>
+
+/**
+ * Triggers another level of macro expansion, use whenever passing __VA_ARGS__ to another macro.
+ *
+ * Used merely for working around an MSVC++ bug.
+ * See http://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
+ */
+#define Y_PASS_VA_ARGS(x) x
+
+/**
+ * Count number of arguments in `__VA_ARGS__`.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_COUNT_ARGS(...) Y_PASS_VA_ARGS(__Y_COUNT_ARGS(__VA_ARGS__, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
+#define __Y_COUNT_ARGS(_50, _49, _48, _47, _46, _45, _44, _43, _42, _41, _40, _39, _38, _37, _36, _35, _34, _33, _32, _31, _30, _29, _28, _27, _26, _25, _24, _23, _22, _21, _20, _19, _18, _17, _16, _15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
+
+/**
+ * Get the i-th element from `__VA_ARGS__`.
+ */
+#define Y_GET_ARG(N, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_GET_ARG_, N))(__VA_ARGS__))
+#define __Y_GET_ARG_0(_0, ...) _0
+#define __Y_GET_ARG_1(_0, _1, ...) _1
+#define __Y_GET_ARG_2(_0, _1, _2, ...) _2
+#define __Y_GET_ARG_3(_0, _1, _2, _3, ...) _3
+#define __Y_GET_ARG_4(_0, _1, _2, _3, _4, ...) _4
+#define __Y_GET_ARG_5(_0, _1, _2, _3, _4, _5, ...) _5
+#define __Y_GET_ARG_6(_0, _1, _2, _3, _4, _5, _6, ...) _6
+#define __Y_GET_ARG_7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7
+#define __Y_GET_ARG_8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
+#define __Y_GET_ARG_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
+#define __Y_GET_ARG_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _10
+#define __Y_GET_ARG_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) _11
+#define __Y_GET_ARG_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) _12
+#define __Y_GET_ARG_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) _13
+#define __Y_GET_ARG_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) _14
+#define __Y_GET_ARG_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
+#define __Y_GET_ARG_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) _16
+#define __Y_GET_ARG_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) _17
+#define __Y_GET_ARG_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) _18
+#define __Y_GET_ARG_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) _19
+#define __Y_GET_ARG_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, ...) _20
+#define __Y_GET_ARG_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, ...) _21
+#define __Y_GET_ARG_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, ...) _22
+#define __Y_GET_ARG_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, ...) _23
+#define __Y_GET_ARG_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, ...) _24
+#define __Y_GET_ARG_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, ...) _25
+#define __Y_GET_ARG_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, ...) _26
+#define __Y_GET_ARG_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, ...) _27
+#define __Y_GET_ARG_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, ...) _28
+#define __Y_GET_ARG_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, ...) _29
+#define __Y_GET_ARG_30(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, ...) _30
+#define __Y_GET_ARG_31(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, ...) _31
+#define __Y_GET_ARG_32(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, ...) _32
+#define __Y_GET_ARG_33(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, ...) _33
+#define __Y_GET_ARG_34(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, ...) _34
+#define __Y_GET_ARG_35(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, ...) _35
+#define __Y_GET_ARG_36(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, ...) _36
+#define __Y_GET_ARG_37(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, ...) _37
+#define __Y_GET_ARG_38(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, ...) _38
+#define __Y_GET_ARG_39(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, ...) _39
+#define __Y_GET_ARG_40(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, ...) _40
+#define __Y_GET_ARG_41(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, ...) _41
+#define __Y_GET_ARG_42(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, ...) _42
+#define __Y_GET_ARG_43(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, ...) _43
+#define __Y_GET_ARG_44(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, ...) _44
+#define __Y_GET_ARG_45(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, ...) _45
+#define __Y_GET_ARG_46(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, ...) _46
+#define __Y_GET_ARG_47(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, ...) _47
+#define __Y_GET_ARG_48(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, ...) _48
+#define __Y_GET_ARG_49(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, ...) _49
+#define __Y_GET_ARG_50(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, ...) _50
+
+/**
+ * Expands a macro for each of the variable arguments.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_MAP_ARGS(ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_MAP_ARGS_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_0(...)
+#define __Y_MAP_ARGS_1(ACTION, x, ...) ACTION(x)
+#define __Y_MAP_ARGS_2(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_1(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_3(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_2(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_4(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_3(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_5(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_4(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_6(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_5(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_7(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_6(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_8(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_7(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_9(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_8(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_10(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_9(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_11(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_10(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_12(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_11(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_13(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_12(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_14(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_13(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_15(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_14(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_16(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_15(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_17(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_16(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_18(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_17(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_19(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_18(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_20(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_19(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_21(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_20(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_22(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_21(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_23(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_22(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_24(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_23(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_25(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_24(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_26(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_25(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_27(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_26(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_28(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_27(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_29(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_28(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_30(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_29(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_31(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_30(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_32(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_31(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_33(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_32(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_34(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_33(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_35(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_34(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_36(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_35(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_37(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_36(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_38(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_37(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_39(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_38(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_40(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_39(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_41(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_40(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_42(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_41(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_43(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_42(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_44(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_43(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_45(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_44(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_46(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_45(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_47(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_46(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_48(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_47(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_49(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_48(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_50(ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_49(ACTION, __VA_ARGS__))
+
+/**
+ * Expands a macro for each of the variable arguments with it's sequence number and value.
+ * Corresponding sequence numbers will expand in descending order.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_MAP_ARGS_N(ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_MAP_ARGS_N_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_0(...)
+#define __Y_MAP_ARGS_N_1(ACTION, x, ...) ACTION(1, x)
+#define __Y_MAP_ARGS_N_2(ACTION, x, ...) \
+ ACTION(2, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_1(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_3(ACTION, x, ...) \
+ ACTION(3, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_2(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_4(ACTION, x, ...) \
+ ACTION(4, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_3(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_5(ACTION, x, ...) \
+ ACTION(5, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_4(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_6(ACTION, x, ...) \
+ ACTION(6, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_5(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_7(ACTION, x, ...) \
+ ACTION(7, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_6(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_8(ACTION, x, ...) \
+ ACTION(8, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_7(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_9(ACTION, x, ...) \
+ ACTION(9, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_8(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_10(ACTION, x, ...) \
+ ACTION(10, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_9(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_11(ACTION, x, ...) \
+ ACTION(11, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_10(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_12(ACTION, x, ...) \
+ ACTION(12, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_11(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_13(ACTION, x, ...) \
+ ACTION(13, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_12(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_14(ACTION, x, ...) \
+ ACTION(14, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_13(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_15(ACTION, x, ...) \
+ ACTION(15, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_14(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_16(ACTION, x, ...) \
+ ACTION(16, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_15(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_17(ACTION, x, ...) \
+ ACTION(17, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_16(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_18(ACTION, x, ...) \
+ ACTION(18, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_17(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_19(ACTION, x, ...) \
+ ACTION(19, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_18(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_20(ACTION, x, ...) \
+ ACTION(20, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_19(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_21(ACTION, x, ...) \
+ ACTION(21, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_20(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_22(ACTION, x, ...) \
+ ACTION(22, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_21(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_23(ACTION, x, ...) \
+ ACTION(23, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_22(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_24(ACTION, x, ...) \
+ ACTION(24, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_23(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_25(ACTION, x, ...) \
+ ACTION(25, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_24(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_26(ACTION, x, ...) \
+ ACTION(26, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_25(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_27(ACTION, x, ...) \
+ ACTION(27, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_26(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_28(ACTION, x, ...) \
+ ACTION(28, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_27(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_29(ACTION, x, ...) \
+ ACTION(29, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_28(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_30(ACTION, x, ...) \
+ ACTION(30, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_29(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_31(ACTION, x, ...) \
+ ACTION(31, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_30(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_32(ACTION, x, ...) \
+ ACTION(32, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_31(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_33(ACTION, x, ...) \
+ ACTION(33, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_32(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_34(ACTION, x, ...) \
+ ACTION(34, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_33(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_35(ACTION, x, ...) \
+ ACTION(35, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_34(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_36(ACTION, x, ...) \
+ ACTION(36, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_35(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_37(ACTION, x, ...) \
+ ACTION(37, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_36(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_38(ACTION, x, ...) \
+ ACTION(38, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_37(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_39(ACTION, x, ...) \
+ ACTION(39, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_38(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_40(ACTION, x, ...) \
+ ACTION(40, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_39(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_41(ACTION, x, ...) \
+ ACTION(41, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_40(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_42(ACTION, x, ...) \
+ ACTION(42, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_41(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_43(ACTION, x, ...) \
+ ACTION(43, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_42(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_44(ACTION, x, ...) \
+ ACTION(44, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_43(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_45(ACTION, x, ...) \
+ ACTION(45, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_44(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_46(ACTION, x, ...) \
+ ACTION(46, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_45(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_47(ACTION, x, ...) \
+ ACTION(47, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_46(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_48(ACTION, x, ...) \
+ ACTION(48, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_47(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_49(ACTION, x, ...) \
+ ACTION(49, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_48(ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_N_50(ACTION, x, ...) \
+ ACTION(50, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_49(ACTION, __VA_ARGS__))
+
+/**
+ * Expands a macro for each of the variable arguments.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_MAP_ARGS_WITH_LAST(ACTION, LAST_ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_MAP_ARGS_WITH_LAST_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_0(...)
+#define __Y_MAP_ARGS_WITH_LAST_1(ACTION, LAST_ACTION, x, ...) LAST_ACTION(x)
+#define __Y_MAP_ARGS_WITH_LAST_2(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_1(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_3(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_2(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_4(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_3(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_5(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_4(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_6(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_5(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_7(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_6(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_8(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_7(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_9(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_8(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_10(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_9(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_11(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_10(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_12(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_11(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_13(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_12(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_14(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_13(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_15(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_14(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_16(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_15(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_17(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_16(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_18(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_17(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_19(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_18(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_20(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_19(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_21(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_20(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_22(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_21(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_23(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_22(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_24(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_23(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_25(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_24(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_26(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_25(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_27(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_26(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_28(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_27(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_29(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_28(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_30(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_29(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_31(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_30(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_32(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_31(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_33(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_32(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_34(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_33(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_35(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_34(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_36(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_35(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_37(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_36(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_38(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_37(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_39(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_38(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_40(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_39(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_41(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_40(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_42(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_41(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_43(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_42(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_44(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_43(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_45(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_44(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_46(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_45(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_47(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_46(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_48(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_47(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_49(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_48(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_50(ACTION, LAST_ACTION, x, ...) \
+ ACTION(x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_49(ACTION, LAST_ACTION, __VA_ARGS__))
+
+/**
+ * Expands a macro for each of the variable arguments with it's sequence number and value.
+ * Corresponding sequence numbers will expand in descending order.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_MAP_ARGS_WITH_LAST_N(ACTION, LAST_ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_MAP_ARGS_WITH_LAST_N_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_0(...)
+#define __Y_MAP_ARGS_WITH_LAST_N_1(ACTION, LAST_ACTION, x, ...) LAST_ACTION(1, x)
+#define __Y_MAP_ARGS_WITH_LAST_N_2(ACTION, LAST_ACTION, x, ...) \
+ ACTION(2, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_1(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_3(ACTION, LAST_ACTION, x, ...) \
+ ACTION(3, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_2(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_4(ACTION, LAST_ACTION, x, ...) \
+ ACTION(4, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_3(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_5(ACTION, LAST_ACTION, x, ...) \
+ ACTION(5, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_4(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_6(ACTION, LAST_ACTION, x, ...) \
+ ACTION(6, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_5(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_7(ACTION, LAST_ACTION, x, ...) \
+ ACTION(7, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_6(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_8(ACTION, LAST_ACTION, x, ...) \
+ ACTION(8, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_7(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_9(ACTION, LAST_ACTION, x, ...) \
+ ACTION(9, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_8(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_10(ACTION, LAST_ACTION, x, ...) \
+ ACTION(10, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_9(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_11(ACTION, LAST_ACTION, x, ...) \
+ ACTION(11, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_10(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_12(ACTION, LAST_ACTION, x, ...) \
+ ACTION(12, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_11(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_13(ACTION, LAST_ACTION, x, ...) \
+ ACTION(13, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_12(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_14(ACTION, LAST_ACTION, x, ...) \
+ ACTION(14, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_13(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_15(ACTION, LAST_ACTION, x, ...) \
+ ACTION(15, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_14(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_16(ACTION, LAST_ACTION, x, ...) \
+ ACTION(16, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_15(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_17(ACTION, LAST_ACTION, x, ...) \
+ ACTION(17, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_16(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_18(ACTION, LAST_ACTION, x, ...) \
+ ACTION(18, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_17(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_19(ACTION, LAST_ACTION, x, ...) \
+ ACTION(19, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_18(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_20(ACTION, LAST_ACTION, x, ...) \
+ ACTION(20, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_19(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_21(ACTION, LAST_ACTION, x, ...) \
+ ACTION(21, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_20(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_22(ACTION, LAST_ACTION, x, ...) \
+ ACTION(22, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_21(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_23(ACTION, LAST_ACTION, x, ...) \
+ ACTION(23, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_22(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_24(ACTION, LAST_ACTION, x, ...) \
+ ACTION(24, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_23(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_25(ACTION, LAST_ACTION, x, ...) \
+ ACTION(25, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_24(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_26(ACTION, LAST_ACTION, x, ...) \
+ ACTION(26, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_25(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_27(ACTION, LAST_ACTION, x, ...) \
+ ACTION(27, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_26(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_28(ACTION, LAST_ACTION, x, ...) \
+ ACTION(28, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_27(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_29(ACTION, LAST_ACTION, x, ...) \
+ ACTION(29, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_28(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_30(ACTION, LAST_ACTION, x, ...) \
+ ACTION(30, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_29(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_31(ACTION, LAST_ACTION, x, ...) \
+ ACTION(31, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_30(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_32(ACTION, LAST_ACTION, x, ...) \
+ ACTION(32, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_31(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_33(ACTION, LAST_ACTION, x, ...) \
+ ACTION(33, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_32(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_34(ACTION, LAST_ACTION, x, ...) \
+ ACTION(34, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_33(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_35(ACTION, LAST_ACTION, x, ...) \
+ ACTION(35, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_34(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_36(ACTION, LAST_ACTION, x, ...) \
+ ACTION(36, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_35(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_37(ACTION, LAST_ACTION, x, ...) \
+ ACTION(37, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_36(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_38(ACTION, LAST_ACTION, x, ...) \
+ ACTION(38, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_37(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_39(ACTION, LAST_ACTION, x, ...) \
+ ACTION(39, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_38(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_40(ACTION, LAST_ACTION, x, ...) \
+ ACTION(40, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_39(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_41(ACTION, LAST_ACTION, x, ...) \
+ ACTION(41, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_40(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_42(ACTION, LAST_ACTION, x, ...) \
+ ACTION(42, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_41(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_43(ACTION, LAST_ACTION, x, ...) \
+ ACTION(43, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_42(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_44(ACTION, LAST_ACTION, x, ...) \
+ ACTION(44, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_43(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_45(ACTION, LAST_ACTION, x, ...) \
+ ACTION(45, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_44(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_46(ACTION, LAST_ACTION, x, ...) \
+ ACTION(46, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_45(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_47(ACTION, LAST_ACTION, x, ...) \
+ ACTION(47, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_46(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_48(ACTION, LAST_ACTION, x, ...) \
+ ACTION(48, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_47(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_49(ACTION, LAST_ACTION, x, ...) \
+ ACTION(49, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_48(ACTION, LAST_ACTION, __VA_ARGS__))
+#define __Y_MAP_ARGS_WITH_LAST_N_50(ACTION, LAST_ACTION, x, ...) \
+ ACTION(50, x) \
+ Y_PASS_VA_ARGS(__Y_MAP_ARGS_WITH_LAST_N_49(ACTION, LAST_ACTION, __VA_ARGS__))
+
+/**
+ * Get all elements but the last one from `__VA_ARGS__`.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_ALL_BUT_LAST(...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_ALL_BUT_LAST_, Y_COUNT_ARGS(__VA_ARGS__)))(__VA_ARGS__))
+#define __Y_ALL_BUT_LAST_0(...)
+#define __Y_ALL_BUT_LAST_1(...)
+#define __Y_ALL_BUT_LAST_2(_0, ...) _0
+#define __Y_ALL_BUT_LAST_3(_0, _1, ...) _0, _1
+#define __Y_ALL_BUT_LAST_4(_0, _1, _2, ...) _0, _1, _2
+#define __Y_ALL_BUT_LAST_5(_0, _1, _2, _3, ...) _0, _1, _2, _3
+#define __Y_ALL_BUT_LAST_6(_0, _1, _2, _3, _4, ...) _0, _1, _2, _3, _4
+#define __Y_ALL_BUT_LAST_7(_0, _1, _2, _3, _4, _5, ...) _0, _1, _2, _3, _4, _5
+#define __Y_ALL_BUT_LAST_8(_0, _1, _2, _3, _4, _5, _6, ...) _0, _1, _2, _3, _4, _5, _6
+#define __Y_ALL_BUT_LAST_9(_0, _1, _2, _3, _4, _5, _6, _7, ...) _0, _1, _2, _3, _4, _5, _6, _7
+#define __Y_ALL_BUT_LAST_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8
+#define __Y_ALL_BUT_LAST_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9
+#define __Y_ALL_BUT_LAST_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10
+#define __Y_ALL_BUT_LAST_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11
+#define __Y_ALL_BUT_LAST_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12
+#define __Y_ALL_BUT_LAST_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13
+#define __Y_ALL_BUT_LAST_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14
+#define __Y_ALL_BUT_LAST_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15
+#define __Y_ALL_BUT_LAST_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16
+#define __Y_ALL_BUT_LAST_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17
+#define __Y_ALL_BUT_LAST_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18
+#define __Y_ALL_BUT_LAST_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19
+#define __Y_ALL_BUT_LAST_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20
+#define __Y_ALL_BUT_LAST_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21
+#define __Y_ALL_BUT_LAST_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22
+#define __Y_ALL_BUT_LAST_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23
+#define __Y_ALL_BUT_LAST_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24
+#define __Y_ALL_BUT_LAST_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25
+#define __Y_ALL_BUT_LAST_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26
+#define __Y_ALL_BUT_LAST_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27
+#define __Y_ALL_BUT_LAST_30(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28
+#define __Y_ALL_BUT_LAST_31(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29
+#define __Y_ALL_BUT_LAST_32(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30
+#define __Y_ALL_BUT_LAST_33(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31
+#define __Y_ALL_BUT_LAST_34(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32
+#define __Y_ALL_BUT_LAST_35(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33
+#define __Y_ALL_BUT_LAST_36(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34
+#define __Y_ALL_BUT_LAST_37(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35
+#define __Y_ALL_BUT_LAST_38(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36
+#define __Y_ALL_BUT_LAST_39(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37
+#define __Y_ALL_BUT_LAST_40(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38
+#define __Y_ALL_BUT_LAST_41(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39
+#define __Y_ALL_BUT_LAST_42(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40
+#define __Y_ALL_BUT_LAST_43(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41
+#define __Y_ALL_BUT_LAST_44(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42
+#define __Y_ALL_BUT_LAST_45(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43
+#define __Y_ALL_BUT_LAST_46(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44
+#define __Y_ALL_BUT_LAST_47(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45
+#define __Y_ALL_BUT_LAST_48(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46
+#define __Y_ALL_BUT_LAST_49(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47
+#define __Y_ALL_BUT_LAST_50(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, ...) _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48
+
+/**
+ * Get the last element from `__VA_ARGS__`.
+ * Doesn't work with empty arguments list.
+ */
+#define Y_LAST(...) Y_PASS_VA_ARGS(Y_GET_ARG(Y_COUNT_ARGS(__VA_ARGS__), , __VA_ARGS__, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ))
+
+/**
+ * Macros for implementing overload by number of arguments.
+ *
+ * Example usage:
+ *
+ * @code{cpp}
+ * #define I1(arg1) Cout << Y_STRINGIZE(arg1) << Endl;
+ * #define I2(arg1, arg2) Cout << Y_STRINGIZE(arg1) << ';' << Y_STRINGIZE(arg2) << Endl;
+ *
+ * #define Y_PRINT(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, I2, I1)(__VA_ARGS__))
+ * @endcode
+ */
+/// @{
+#define Y_MACRO_IMPL_DISPATCHER_2(_0, _1, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_3(_0, _1, _2, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_4(_0, _1, _2, _3, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_5(_0, _1, _2, _3, _4, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_6(_0, _1, _2, _3, _4, _5, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_7(_0, _1, _2, _3, _4, _5, _6, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_8(_0, _1, _2, _3, _4, _5, _6, _7, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, IMPL, ...) IMPL
+#define Y_MACRO_IMPL_DISPATCHER_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, IMPL, ...) IMPL
+/// }@
diff --git a/util/generic/va_args_gen.py b/util/generic/va_args_gen.py
new file mode 100755
index 0000000000..232b53fca6
--- /dev/null
+++ b/util/generic/va_args_gen.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+"""
+Generates some handy macros for preprocessor metaprogramming.
+
+"""
+
+from __future__ import print_function
+
+import sys
+import textwrap
+
+if sys.version_info >= (3, 0, 0):
+ xrange = range
+
+
+def generate(limit):
+ print('#pragma once')
+ print(textwrap.dedent('''
+ /// @file va_args.h
+ ///
+ /// Some handy macros for preprocessor metaprogramming.
+ '''.rstrip()))
+ print('')
+ command = ' '.join(sys.argv)
+ print('// NOTE: this file has been generated with "{}", do not edit -- use the generator instead'.format(command))
+ print('')
+ print('// DO_NOT_STYLE')
+ print('')
+ print('#include <util/system/defaults.h>')
+ print('')
+
+ pass_va_args()
+ count(limit)
+ get_elem(limit)
+ map_args(limit)
+ map_args_n(limit)
+ map_args_with_last(limit)
+ map_args_with_last_n(limit)
+ all_but_last(limit)
+ last(limit)
+ impl_dispatcher()
+
+
+def pass_va_args():
+ print(textwrap.dedent('''
+ /**
+ * Triggers another level of macro expansion, use whenever passing __VA_ARGS__ to another macro.
+ *
+ * Used merely for working around an MSVC++ bug.
+ * See http://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
+ */
+ '''.rstrip()))
+ print('#define Y_PASS_VA_ARGS(x) x')
+
+
+def count(limit):
+ print(textwrap.dedent('''
+ /**
+ * Count number of arguments in `__VA_ARGS__`.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ numbers = ', '.join(map(str, xrange(limit, -1, -1)))
+ u_numbers = ', '.join(map('_{}'.format, xrange(limit, 0, -1)))
+ print('#define Y_COUNT_ARGS(...) Y_PASS_VA_ARGS('
+ '__Y_COUNT_ARGS(__VA_ARGS__, {}))'.format(numbers))
+ print('#define __Y_COUNT_ARGS({}, N, ...) N'.format(u_numbers))
+
+
+def get_elem(limit):
+ print(textwrap.dedent('''
+ /**
+ * Get the i-th element from `__VA_ARGS__`.
+ */
+ '''.rstrip()))
+ print('#define Y_GET_ARG(N, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_GET_ARG_, '
+ 'N))(__VA_ARGS__))')
+ for i in xrange(0, limit + 1):
+ args = ', '.join(map('_{}'.format, xrange(i + 1)))
+ print('#define __Y_GET_ARG_{}({}, ...) _{}'.format(i, args, i))
+
+
+def map_args(limit):
+ print(textwrap.dedent('''
+ /**
+ * Expands a macro for each of the variable arguments.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ print('#define Y_MAP_ARGS(ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT('
+ '__Y_MAP_ARGS_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, __VA_ARGS__))')
+ print('#define __Y_MAP_ARGS_0(...)')
+ print('#define __Y_MAP_ARGS_1(ACTION, x, ...) ACTION(x)')
+ for i in xrange(2, limit + 1):
+ print('#define __Y_MAP_ARGS_{}(ACTION, x, ...) ACTION(x) Y_PASS_VA_ARGS(__Y_MAP_ARGS_{}('
+ 'ACTION, __VA_ARGS__))'.format(i, i - 1))
+
+
+def map_args_n(limit):
+ print(textwrap.dedent('''
+ /**
+ * Expands a macro for each of the variable arguments with it's sequence number and value.
+ * Corresponding sequence numbers will expand in descending order.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ print('#define Y_MAP_ARGS_N(ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT('
+ '__Y_MAP_ARGS_N_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, __VA_ARGS__))')
+ print('#define __Y_MAP_ARGS_N_0(...)')
+ print('#define __Y_MAP_ARGS_N_1(ACTION, x, ...) ACTION(1, x)')
+ for i in xrange(2, limit + 1):
+ print('#define __Y_MAP_ARGS_N_{}(ACTION, x, ...) ACTION({}, x) Y_PASS_VA_ARGS(__Y_MAP_ARGS_N_{}('
+ 'ACTION, __VA_ARGS__))'.format(i, i, i - 1))
+
+
+def map_args_with_last(limit):
+ print(textwrap.dedent('''
+ /**
+ * Expands a macro for each of the variable arguments.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ print('#define Y_MAP_ARGS_WITH_LAST(ACTION, LAST_ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS('
+ 'Y_CAT(__Y_MAP_ARGS_WITH_LAST_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, LAST_ACTION, '
+ '__VA_ARGS__))')
+ print('#define __Y_MAP_ARGS_WITH_LAST_0(...)')
+ print('#define __Y_MAP_ARGS_WITH_LAST_1(ACTION, LAST_ACTION, x, ...) LAST_ACTION(x)')
+ for i in xrange(2, limit + 1):
+ print('#define __Y_MAP_ARGS_WITH_LAST_{}(ACTION, LAST_ACTION, x, ...) ACTION(x) Y_PASS_VA_ARGS('
+ '__Y_MAP_ARGS_WITH_LAST_{}(ACTION, LAST_ACTION, __VA_ARGS__))'.format(i, i - 1))
+
+
+def map_args_with_last_n(limit):
+ print(textwrap.dedent('''
+ /**
+ * Expands a macro for each of the variable arguments with it's sequence number and value.
+ * Corresponding sequence numbers will expand in descending order.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ print('#define Y_MAP_ARGS_WITH_LAST_N(ACTION, LAST_ACTION, ...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS('
+ 'Y_CAT(__Y_MAP_ARGS_WITH_LAST_N_, Y_COUNT_ARGS(__VA_ARGS__)))(ACTION, LAST_ACTION, '
+ '__VA_ARGS__))')
+ print('#define __Y_MAP_ARGS_WITH_LAST_N_0(...)')
+ print('#define __Y_MAP_ARGS_WITH_LAST_N_1(ACTION, LAST_ACTION, x, ...) LAST_ACTION(1, x)')
+ for i in xrange(2, limit + 1):
+ print('#define __Y_MAP_ARGS_WITH_LAST_N_{}(ACTION, LAST_ACTION, x, ...) ACTION({}, x) Y_PASS_VA_ARGS('
+ '__Y_MAP_ARGS_WITH_LAST_N_{}(ACTION, LAST_ACTION, __VA_ARGS__))'.format(i, i, i - 1))
+
+
+def all_but_last(limit):
+ print(textwrap.dedent('''
+ /**
+ * Get all elements but the last one from `__VA_ARGS__`.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ print('#define Y_ALL_BUT_LAST(...) Y_PASS_VA_ARGS(Y_PASS_VA_ARGS(Y_CAT(__Y_ALL_BUT_LAST_, '
+ 'Y_COUNT_ARGS(__VA_ARGS__)))(__VA_ARGS__))')
+ print('#define __Y_ALL_BUT_LAST_0(...)')
+ print('#define __Y_ALL_BUT_LAST_1(...)')
+ for i in xrange(2, limit + 1):
+ args = ', '.join(map('_{}'.format, xrange(i - 1)))
+ print('#define __Y_ALL_BUT_LAST_{}({}, ...) {}'.format(i, args, args))
+
+
+def last(limit):
+ print(textwrap.dedent('''
+ /**
+ * Get the last element from `__VA_ARGS__`.
+ * Doesn't work with empty arguments list.
+ */
+ '''.rstrip()))
+ print('#define Y_LAST(...) Y_PASS_VA_ARGS('
+ 'Y_GET_ARG(Y_COUNT_ARGS(__VA_ARGS__), , __VA_ARGS__, {}))'.format(',' * limit))
+
+
+def impl_dispatcher():
+ print(textwrap.dedent('''
+ /**
+ * Macros for implementing overload by number of arguments.
+ *
+ * Example usage:
+ *
+ * @code{cpp}
+ * #define I1(arg1) Cout << Y_STRINGIZE(arg1) << Endl;
+ * #define I2(arg1, arg2) Cout << Y_STRINGIZE(arg1) << ';' << Y_STRINGIZE(arg2) << Endl;
+ *
+ * #define Y_PRINT(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, I2, I1)(__VA_ARGS__))
+ * @endcode
+ */
+ '''.rstrip()))
+ print('/// @{')
+ for i in xrange(2, 11):
+ args = ', '.join(map('_{}'.format, xrange(i)))
+ print('#define Y_MACRO_IMPL_DISPATCHER_{}({}, IMPL, ...) IMPL'.format(i, args))
+ print('/// }@')
+
+
+def main():
+ if len(sys.argv) > 2:
+ sys.stderr.write('Usage: {} [limit=50]\n'.format(sys.argv[0]))
+ sys.exit(1)
+ limit = 50
+ if len(sys.argv) == 2:
+ limit = int(sys.argv[1])
+ generate(limit)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/util/generic/va_args_ut.cpp b/util/generic/va_args_ut.cpp
new file mode 100644
index 0000000000..a9c96a0f55
--- /dev/null
+++ b/util/generic/va_args_ut.cpp
@@ -0,0 +1,106 @@
+#include "va_args.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+Y_UNIT_TEST_SUITE(TMacroVarargMapTest) {
+ Y_UNIT_TEST(TestMapArgs) {
+ static const char COMBINED[] = Y_MAP_ARGS(Y_STRINGIZE, 1, 2, 3);
+ UNIT_ASSERT_STRINGS_EQUAL(COMBINED, "123");
+ }
+
+ Y_UNIT_TEST(TestMapArgsWithLast) {
+#define ADD(x) x +
+#define ID(x) x
+ static const int SUM = Y_MAP_ARGS_WITH_LAST(ADD, ID, 1, 2, 3, 4 + 5);
+ UNIT_ASSERT_VALUES_EQUAL(SUM, 1 + 2 + 3 + 4 + 5);
+#undef ADD
+#undef ID
+ }
+
+ Y_UNIT_TEST(TestMapArgsN) {
+#define MAP_ARG(INDEX, X) Y_STRINGIZE(X)
+#define MAP_INDEX(INDEX, X) Y_STRINGIZE(INDEX)
+ static const char COMBINED_ARGS[] = Y_MAP_ARGS_N(MAP_ARG, 1, 2, 3);
+ UNIT_ASSERT_STRINGS_EQUAL(COMBINED_ARGS, "123");
+ static const char COMBINED_INDEXES[] = Y_MAP_ARGS_N(MAP_INDEX, 1, 2, 3);
+ UNIT_ASSERT_STRINGS_EQUAL(COMBINED_INDEXES, "321");
+#undef MAP_INDEX
+#undef MAP_ARG
+ }
+
+ Y_UNIT_TEST(TestMapArgsWithLastN) {
+#define ADD_ARG(INDEX, X) X +
+#define ID_ARG(INDEX, X) X
+#define MAP_INDEX(INDEX, X) Y_STRINGIZE(INDEX)
+ static const int SUM = Y_MAP_ARGS_WITH_LAST_N(ADD_ARG, ID_ARG, 1, 2, 3, 4 + 5);
+ UNIT_ASSERT_VALUES_EQUAL(SUM, 1 + 2 + 3 + 4 + 5);
+ static const char COMBINED_INDEXES[] = Y_MAP_ARGS_WITH_LAST_N(MAP_INDEX, MAP_INDEX, 1, 2, 3, 4 + 5);
+ UNIT_ASSERT_STRINGS_EQUAL(COMBINED_INDEXES, "4321");
+#undef MAP_INDEX
+#undef ADD_ARG
+#undef ID_ARG
+ }
+}
+
+Y_UNIT_TEST_SUITE(TestVaArgs) {
+ Y_UNIT_TEST(Count) {
+ // UNIT_ASSERT((Y_COUNT_ARGS() == 0)); // FIXME: make this case work after __VA_OPT__ (c++20)
+ UNIT_ASSERT((Y_COUNT_ARGS(1) == 1));
+ UNIT_ASSERT((Y_COUNT_ARGS(1, 2) == 2));
+ UNIT_ASSERT((Y_COUNT_ARGS(1, 2, 3) == 3));
+ UNIT_ASSERT((Y_COUNT_ARGS(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0) == 20));
+ }
+
+ Y_UNIT_TEST(GetElem) {
+ UNIT_ASSERT((Y_GET_ARG(0, 1) == 1));
+ UNIT_ASSERT((Y_GET_ARG(0, 0, 1, 2, 3, 4, 5) == 0));
+ UNIT_ASSERT((Y_GET_ARG(1, 0, 1, 2, 3, 4, 5) == 1));
+ UNIT_ASSERT((Y_GET_ARG(2, 0, 1, 2, 3, 4, 5) == 2));
+ UNIT_ASSERT((Y_GET_ARG(3, 0, 1, 2, 3, 4, 5) == 3));
+ UNIT_ASSERT((Y_GET_ARG(4, 0, 1, 2, 3, 4, 5) == 4));
+ UNIT_ASSERT((Y_GET_ARG(5, 0, 1, 2, 3, 4, 5) == 5));
+ }
+
+ Y_UNIT_TEST(MapArgs) {
+#define MAP(x) x + /* NOLINT */
+ // UNIT_ASSERT((Y_MAP_ARGS(MAP) 0 == 0)); // FIXME: make this case work after __VA_OPT__ (c++20)
+ UNIT_ASSERT((Y_MAP_ARGS(MAP, 1, 2, 3, 4) 0 == 10));
+#undef MAP
+ }
+
+ Y_UNIT_TEST(MapArgsWithLast) {
+#define MAP(x) x + /* NOLINT */
+#define MAP_LAST(x) x
+ UNIT_ASSERT((Y_MAP_ARGS_WITH_LAST(MAP, MAP_LAST, 1, 2, 3, 4) == 10));
+#undef MAP_LAST
+#undef MAP
+ }
+
+ Y_UNIT_TEST(AllButLast) {
+ const char array[] = {Y_ALL_BUT_LAST(1, 2, 3, 4, 5)};
+ UNIT_ASSERT((sizeof(array) == 4));
+ UNIT_ASSERT((array[0] == 1));
+ UNIT_ASSERT((array[1] == 2));
+ UNIT_ASSERT((array[2] == 3));
+ UNIT_ASSERT((array[3] == 4));
+ }
+
+ Y_UNIT_TEST(Last) {
+ UNIT_ASSERT((Y_LAST(1) == 1));
+ UNIT_ASSERT((Y_LAST(1, 2, 3) == 3));
+ }
+
+ Y_UNIT_TEST(ImplDispatcher) {
+#define I1(x) (x)
+#define I2(x, y) ((x) + (y))
+#define I3(x, y, z) ((x) + (y) + (z))
+#define I(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_3(__VA_ARGS__, I3, I2, I1)(__VA_ARGS__))
+ UNIT_ASSERT((I(1) == 1));
+ UNIT_ASSERT((I(1, 2) == 3));
+ UNIT_ASSERT((I(1, 2, 3) == 6));
+#undef I
+#undef I3
+#undef I2
+#undef I1
+ }
+};
diff --git a/util/generic/variant.cpp b/util/generic/variant.cpp
new file mode 100644
index 0000000000..1de3ade2ee
--- /dev/null
+++ b/util/generic/variant.cpp
@@ -0,0 +1 @@
+#include "variant.h"
diff --git a/util/generic/variant.h b/util/generic/variant.h
new file mode 100644
index 0000000000..749fc75090
--- /dev/null
+++ b/util/generic/variant.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "hash.h"
+
+#include <variant>
+
+template <class... Ts>
+struct THash<std::variant<Ts...>> {
+public:
+ size_t operator()(const std::variant<Ts...>& v) const noexcept {
+ return CombineHashes(
+ IntHash(v.index()),
+ v.valueless_by_exception() ? 0 : std::visit([](const auto& value) { return ComputeHash(value); }, v));
+ }
+};
+
+template <>
+struct THash<std::monostate> {
+public:
+ constexpr size_t operator()(std::monostate) const noexcept {
+ return 1;
+ }
+};
diff --git a/util/generic/vector.cpp b/util/generic/vector.cpp
new file mode 100644
index 0000000000..7da6cc5e86
--- /dev/null
+++ b/util/generic/vector.cpp
@@ -0,0 +1 @@
+#include "vector.h"
diff --git a/util/generic/vector.h b/util/generic/vector.h
new file mode 100644
index 0000000000..a5b258955a
--- /dev/null
+++ b/util/generic/vector.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "fwd.h"
+#include "reserve.h"
+
+#include <util/memory/alloc.h>
+
+#include <vector>
+#include <initializer_list>
+
+template <class T, class A>
+class TVector: public std::vector<T, TReboundAllocator<A, T>> {
+public:
+ using TBase = std::vector<T, TReboundAllocator<A, T>>;
+ using TSelf = TVector<T, A>;
+ using size_type = typename TBase::size_type;
+
+ inline TVector()
+ : TBase()
+ {
+ }
+
+ inline TVector(const typename TBase::allocator_type& a)
+ : TBase(a)
+ {
+ }
+
+ inline explicit TVector(::NDetail::TReserveTag rt)
+ : TBase()
+ {
+ this->reserve(rt.Capacity);
+ }
+
+ inline explicit TVector(::NDetail::TReserveTag rt, const typename TBase::allocator_type& a)
+ : TBase(a)
+ {
+ this->reserve(rt.Capacity);
+ }
+
+ inline explicit TVector(size_type count)
+ : TBase(count)
+ {
+ }
+
+ inline explicit TVector(size_type count, const typename TBase::allocator_type& a)
+ : TBase(count, a)
+ {
+ }
+
+ inline TVector(size_type count, const T& val)
+ : TBase(count, val)
+ {
+ }
+
+ inline TVector(size_type count, const T& val, const typename TBase::allocator_type& a)
+ : TBase(count, val, a)
+ {
+ }
+
+ inline TVector(std::initializer_list<T> il)
+ : TBase(il)
+ {
+ }
+
+ inline TVector(std::initializer_list<T> il, const typename TBase::allocator_type& a)
+ : TBase(il, a)
+ {
+ }
+
+ inline TVector(const TSelf& src)
+ : TBase(src)
+ {
+ }
+
+ inline TVector(TSelf&& src) noexcept
+ : TBase(std::forward<TSelf>(src))
+ {
+ }
+
+ template <class TIter>
+ inline TVector(TIter first, TIter last)
+ : TBase(first, last)
+ {
+ }
+
+ inline TSelf& operator=(const TSelf& src) {
+ TBase::operator=(src);
+ return *this;
+ }
+
+ inline TSelf& operator=(TSelf&& src) noexcept {
+ TBase::operator=(std::forward<TSelf>(src));
+ return *this;
+ }
+
+ inline TSelf& operator=(std::initializer_list<T> il) {
+ this->assign(il.begin(), il.end());
+ return *this;
+ }
+
+ inline explicit operator bool() const noexcept {
+ return !this->empty();
+ }
+
+ Y_PURE_FUNCTION inline bool empty() const noexcept {
+ return TBase::empty();
+ }
+
+ inline yssize_t ysize() const noexcept {
+ return (yssize_t)TBase::size();
+ }
+
+#ifdef _YNDX_LIBCXX_ENABLE_VECTOR_POD_RESIZE_UNINITIALIZED
+ void yresize(size_type newSize) {
+ if (std::is_pod<T>::value) {
+ TBase::resize_uninitialized(newSize);
+ } else {
+ TBase::resize(newSize);
+ }
+ }
+#else
+ void yresize(size_type newSize) {
+ TBase::resize(newSize);
+ }
+#endif
+
+ inline void crop(size_type size) {
+ if (this->size() > size) {
+ this->erase(this->begin() + size, this->end());
+ }
+ }
+};
diff --git a/util/generic/vector.pxd b/util/generic/vector.pxd
new file mode 100644
index 0000000000..99dde95d48
--- /dev/null
+++ b/util/generic/vector.pxd
@@ -0,0 +1,83 @@
+cdef extern from "<util/generic/vector.h>" nogil:
+ cdef cppclass TVector[T]:
+ cppclass iterator:
+ T& operator*()
+ iterator operator++()
+ iterator operator--()
+ iterator operator+(size_t)
+ iterator operator-(size_t)
+ bint operator==(iterator)
+ bint operator!=(iterator)
+ bint operator<(iterator)
+ bint operator>(iterator)
+ bint operator<=(iterator)
+ bint operator>=(iterator)
+
+ cppclass reverse_iterator:
+ T& operator*()
+ reverse_iterator operator++()
+ reverse_iterator operator--()
+ reverse_iterator operator+(size_t)
+ reverse_iterator operator-(size_t)
+ bint operator==(reverse_iterator)
+ bint operator!=(reverse_iterator)
+ bint operator<(reverse_iterator)
+ bint operator>(reverse_iterator)
+ bint operator<=(reverse_iterator)
+ bint operator>=(reverse_iterator)
+
+ cppclass const_iterator(iterator):
+ pass
+
+ cppclass const_reverse_iterator(reverse_iterator):
+ pass
+
+ TVector() except +
+ TVector(TVector&) except +
+ TVector(size_t) except +
+ TVector(size_t, T&) except +
+
+ bint operator==(TVector&)
+ bint operator!=(TVector&)
+ bint operator<(TVector&)
+ bint operator>(TVector&)
+ bint operator<=(TVector&)
+ bint operator>=(TVector&)
+
+ void assign(size_t, const T&) except +
+ void assign[input_iterator](input_iterator, input_iterator) except +
+
+ T& at(size_t) except +
+ T& operator[](size_t)
+
+ T& back()
+ iterator begin()
+ const_iterator const_begin "begin"()
+ size_t capacity()
+ void clear() except +
+ bint empty()
+ iterator end()
+ const_iterator const_end "end"()
+ iterator erase(iterator) except +
+ iterator erase(iterator, iterator) except +
+ T& front()
+ iterator insert(iterator, const T&) except +
+ void insert(iterator, size_t, const T&) except +
+ void insert[Iter](iterator, Iter, Iter) except +
+ size_t max_size()
+ void pop_back() except +
+ void push_back(T&) except +
+ void emplace_back(...) except +
+ reverse_iterator rbegin()
+ const_reverse_iterator const_rbegin "rbegin"()
+ reverse_iterator rend()
+ const_reverse_iterator const_rend "rend"()
+ void reserve(size_t) except +
+ void resize(size_t) except +
+ void resize(size_t, T&) except +
+ size_t size()
+ void swap(TVector&) except +
+
+ # C++11 methods
+ T* data()
+ void shrink_to_fit() except +
diff --git a/util/generic/vector_ut.cpp b/util/generic/vector_ut.cpp
new file mode 100644
index 0000000000..0f6b4037a0
--- /dev/null
+++ b/util/generic/vector_ut.cpp
@@ -0,0 +1,596 @@
+#include "vector.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <utility>
+#include "yexception.h"
+
+#include <stdexcept>
+
+class TYVectorTest: public TTestBase {
+ UNIT_TEST_SUITE(TYVectorTest);
+ UNIT_TEST(TestConstructorsAndAssignments)
+ UNIT_TEST(TestTildeEmptyToNull)
+ UNIT_TEST(TestTilde)
+ UNIT_TEST(Test1)
+ UNIT_TEST(Test2)
+ UNIT_TEST(Test3)
+ UNIT_TEST(Test4)
+ UNIT_TEST(Test5)
+ UNIT_TEST(Test6)
+ UNIT_TEST(Test7)
+ UNIT_TEST(TestCapacity)
+ UNIT_TEST(TestAt)
+ UNIT_TEST(TestPointer)
+ UNIT_TEST(TestAutoRef)
+ UNIT_TEST(TestIterators)
+ UNIT_TEST(TestShrink)
+ //UNIT_TEST(TestEbo)
+ UNIT_TEST(TestFillInConstructor)
+ UNIT_TEST(TestYResize)
+ UNIT_TEST(TestCrop)
+ UNIT_TEST(TestInitializeList)
+ UNIT_TEST_SUITE_END();
+
+private:
+ void TestConstructorsAndAssignments() {
+ using container = TVector<int>;
+
+ container c1;
+ c1.push_back(100);
+ c1.push_back(200);
+
+ container c2(c1);
+
+ UNIT_ASSERT_VALUES_EQUAL(2, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(100, c1.at(0));
+ UNIT_ASSERT_VALUES_EQUAL(200, c2.at(1));
+
+ container c3(std::move(c1));
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c1.size());
+ UNIT_ASSERT_VALUES_EQUAL(2, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(100, c3.at(0));
+
+ c2.push_back(300);
+ c3 = c2;
+
+ UNIT_ASSERT_VALUES_EQUAL(3, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(3, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(300, c3.at(2));
+
+ c2.push_back(400);
+ c3 = std::move(c2);
+
+ UNIT_ASSERT_VALUES_EQUAL(0, c2.size());
+ UNIT_ASSERT_VALUES_EQUAL(4, c3.size());
+ UNIT_ASSERT_VALUES_EQUAL(400, c3.at(3));
+ }
+
+ inline void TestTildeEmptyToNull() {
+ TVector<int> v;
+ UNIT_ASSERT_EQUAL(nullptr, v.data());
+ }
+
+ inline void TestTilde() {
+ TVector<int> v;
+ v.push_back(10);
+ v.push_back(20);
+
+ UNIT_ASSERT_EQUAL(10, (v.data())[0]);
+ UNIT_ASSERT_EQUAL(20, (v.data())[1]);
+
+ for (int i = 0; i < 10000; ++i) {
+ v.push_back(99);
+ }
+
+ UNIT_ASSERT_EQUAL(10, (v.data())[0]);
+ UNIT_ASSERT_EQUAL(20, (v.data())[1]);
+ UNIT_ASSERT_EQUAL(99, (v.data())[3]);
+ UNIT_ASSERT_EQUAL(99, (v.data())[4]);
+ }
+
+ // Copy-paste of STLPort tests
+
+ void Test1() {
+ TVector<int> v1; // Empty vector of integers.
+
+ UNIT_ASSERT(v1.empty() == true);
+ UNIT_ASSERT(v1.size() == 0);
+ UNIT_ASSERT(!v1);
+
+ // UNIT_ASSERT(v1.max_size() == INT_MAX / sizeof(int));
+ // cout << "max_size = " << v1.max_size() << endl;
+ v1.push_back(42); // Add an integer to the vector.
+
+ UNIT_ASSERT(v1.size() == 1);
+ UNIT_ASSERT(v1);
+
+ UNIT_ASSERT(v1[0] == 42);
+
+ {
+ TVector<TVector<int>> vect(10);
+ TVector<TVector<int>>::iterator it(vect.begin()), end(vect.end());
+ for (; it != end; ++it) {
+ UNIT_ASSERT((*it).empty());
+ UNIT_ASSERT((*it).size() == 0);
+ UNIT_ASSERT((*it).capacity() == 0);
+ UNIT_ASSERT((*it).begin() == (*it).end());
+ }
+ }
+ }
+
+ void Test2() {
+ TVector<double> v1; // Empty vector of doubles.
+ v1.push_back(32.1);
+ v1.push_back(40.5);
+ TVector<double> v2; // Another empty vector of doubles.
+ v2.push_back(3.56);
+
+ UNIT_ASSERT(v1.size() == 2);
+ UNIT_ASSERT(v1[0] == 32.1);
+ UNIT_ASSERT(v1[1] == 40.5);
+
+ UNIT_ASSERT(v2.size() == 1);
+ UNIT_ASSERT(v2[0] == 3.56);
+ v1.swap(v2); // Swap the vector's contents.
+
+ UNIT_ASSERT(v1.size() == 1);
+ UNIT_ASSERT(v1[0] == 3.56);
+
+ UNIT_ASSERT(v2.size() == 2);
+ UNIT_ASSERT(v2[0] == 32.1);
+ UNIT_ASSERT(v2[1] == 40.5);
+
+ v2 = v1; // Assign one vector to another.
+
+ UNIT_ASSERT(v2.size() == 1);
+ UNIT_ASSERT(v2[0] == 3.56);
+ }
+
+ void Test3() {
+ using vec_type = TVector<char>;
+
+ vec_type v1; // Empty vector of characters.
+ v1.push_back('h');
+ v1.push_back('i');
+
+ UNIT_ASSERT(v1.size() == 2);
+ UNIT_ASSERT(v1[0] == 'h');
+ UNIT_ASSERT(v1[1] == 'i');
+
+ vec_type v2(v1.begin(), v1.end());
+ v2[1] = 'o'; // Replace second character.
+
+ UNIT_ASSERT(v2.size() == 2);
+ UNIT_ASSERT(v2[0] == 'h');
+ UNIT_ASSERT(v2[1] == 'o');
+
+ UNIT_ASSERT((v1 == v2) == false);
+
+ UNIT_ASSERT((v1 < v2) == true);
+ }
+
+ void Test4() {
+ TVector<int> v(4);
+
+ v[0] = 1;
+ v[1] = 4;
+ v[2] = 9;
+ v[3] = 16;
+
+ UNIT_ASSERT(v.front() == 1);
+ UNIT_ASSERT(v.back() == 16);
+
+ v.push_back(25);
+
+ UNIT_ASSERT(v.back() == 25);
+ UNIT_ASSERT(v.size() == 5);
+
+ v.pop_back();
+
+ UNIT_ASSERT(v.back() == 16);
+ UNIT_ASSERT(v.size() == 4);
+ }
+
+ void Test5() {
+ int array[] = {1, 4, 9, 16};
+
+ TVector<int> v(array, array + 4);
+
+ UNIT_ASSERT(v.size() == 4);
+
+ UNIT_ASSERT(v[0] == 1);
+ UNIT_ASSERT(v[1] == 4);
+ UNIT_ASSERT(v[2] == 9);
+ UNIT_ASSERT(v[3] == 16);
+ }
+
+ void Test6() {
+ int array[] = {1, 4, 9, 16, 25, 36};
+
+ TVector<int> v(array, array + 6);
+ TVector<int>::iterator vit;
+
+ UNIT_ASSERT(v.size() == 6);
+ UNIT_ASSERT(v[0] == 1);
+ UNIT_ASSERT(v[1] == 4);
+ UNIT_ASSERT(v[2] == 9);
+ UNIT_ASSERT(v[3] == 16);
+ UNIT_ASSERT(v[4] == 25);
+ UNIT_ASSERT(v[5] == 36);
+
+ vit = v.erase(v.begin()); // Erase first element.
+ UNIT_ASSERT(*vit == 4);
+
+ UNIT_ASSERT(v.size() == 5);
+ UNIT_ASSERT(v[0] == 4);
+ UNIT_ASSERT(v[1] == 9);
+ UNIT_ASSERT(v[2] == 16);
+ UNIT_ASSERT(v[3] == 25);
+ UNIT_ASSERT(v[4] == 36);
+
+ vit = v.erase(v.end() - 1); // Erase last element.
+ UNIT_ASSERT(vit == v.end());
+
+ UNIT_ASSERT(v.size() == 4);
+ UNIT_ASSERT(v[0] == 4);
+ UNIT_ASSERT(v[1] == 9);
+ UNIT_ASSERT(v[2] == 16);
+ UNIT_ASSERT(v[3] == 25);
+
+ v.erase(v.begin() + 1, v.end() - 1); // Erase all but first and last.
+
+ UNIT_ASSERT(v.size() == 2);
+ UNIT_ASSERT(v[0] == 4);
+ UNIT_ASSERT(v[1] == 25);
+ }
+
+ void Test7() {
+ int array1[] = {1, 4, 25};
+ int array2[] = {9, 16};
+
+ TVector<int> v(array1, array1 + 3);
+ TVector<int>::iterator vit;
+ vit = v.insert(v.begin(), 0); // Insert before first element.
+ UNIT_ASSERT(*vit == 0);
+
+ vit = v.insert(v.end(), 36); // Insert after last element.
+ UNIT_ASSERT(*vit == 36);
+
+ UNIT_ASSERT(v.size() == 5);
+ UNIT_ASSERT(v[0] == 0);
+ UNIT_ASSERT(v[1] == 1);
+ UNIT_ASSERT(v[2] == 4);
+ UNIT_ASSERT(v[3] == 25);
+ UNIT_ASSERT(v[4] == 36);
+
+ // Insert contents of array2 before fourth element.
+ v.insert(v.begin() + 3, array2, array2 + 2);
+
+ UNIT_ASSERT(v.size() == 7);
+
+ UNIT_ASSERT(v[0] == 0);
+ UNIT_ASSERT(v[1] == 1);
+ UNIT_ASSERT(v[2] == 4);
+ UNIT_ASSERT(v[3] == 9);
+ UNIT_ASSERT(v[4] == 16);
+ UNIT_ASSERT(v[5] == 25);
+ UNIT_ASSERT(v[6] == 36);
+
+ size_t curCapacity = v.capacity();
+ v.clear();
+ UNIT_ASSERT(v.empty());
+
+ //check that clear save reserved data
+ UNIT_ASSERT_EQUAL(curCapacity, v.capacity());
+
+ v.insert(v.begin(), 5, 10);
+ UNIT_ASSERT(v.size() == 5);
+ UNIT_ASSERT(v[0] == 10);
+ UNIT_ASSERT(v[1] == 10);
+ UNIT_ASSERT(v[2] == 10);
+ UNIT_ASSERT(v[3] == 10);
+ UNIT_ASSERT(v[4] == 10);
+ }
+
+ struct TestStruct {
+ unsigned int a[3];
+ };
+
+ void TestCapacity() {
+ {
+ TVector<int> v;
+
+ UNIT_ASSERT(v.capacity() == 0);
+ v.push_back(42);
+ UNIT_ASSERT(v.capacity() >= 1);
+ v.reserve(5000);
+ UNIT_ASSERT(v.capacity() >= 5000);
+ }
+
+ {
+ TVector<int> v(Reserve(100));
+
+ UNIT_ASSERT(v.capacity() >= 100);
+ UNIT_ASSERT(v.size() == 0);
+ }
+
+ {
+ //Test that used to generate an assertion when using __debug_alloc.
+ TVector<TestStruct> va;
+ va.reserve(1);
+ va.reserve(2);
+ }
+ }
+
+ void TestAt() {
+ TVector<int> v;
+ TVector<int> const& cv = v;
+
+ v.push_back(10);
+ UNIT_ASSERT(v.at(0) == 10);
+ v.at(0) = 20;
+ UNIT_ASSERT(cv.at(0) == 20);
+
+ for (;;) {
+ try {
+ v.at(1) = 20;
+ UNIT_ASSERT(false);
+ } catch (std::out_of_range const&) {
+ return;
+ } catch (...) {
+ UNIT_ASSERT(false);
+ }
+ }
+ }
+
+ void TestPointer() {
+ TVector<int*> v1;
+ TVector<int*> v2 = v1;
+ TVector<int*> v3;
+
+ v3.insert(v3.end(), v1.begin(), v1.end());
+ }
+
+ void TestAutoRef() {
+ TVector<int> ref;
+ for (int i = 0; i < 5; ++i) {
+ ref.push_back(i);
+ }
+
+ TVector<TVector<int>> v_v_int(1, ref);
+ v_v_int.push_back(v_v_int[0]);
+ v_v_int.push_back(ref);
+ v_v_int.push_back(v_v_int[0]);
+ v_v_int.push_back(v_v_int[0]);
+ v_v_int.push_back(ref);
+
+ TVector<TVector<int>>::iterator vvit(v_v_int.begin()), vvitEnd(v_v_int.end());
+ for (; vvit != vvitEnd; ++vvit) {
+ UNIT_ASSERT(*vvit == ref);
+ }
+ }
+
+ struct Point {
+ int x, y;
+ };
+
+ struct PointEx: public Point {
+ PointEx()
+ : builtFromBase(false)
+ {
+ }
+ PointEx(const Point&)
+ : builtFromBase(true)
+ {
+ }
+
+ bool builtFromBase;
+ };
+
+ void TestIterators() {
+ TVector<int> vint(10, 0);
+ TVector<int> const& crvint = vint;
+
+ UNIT_ASSERT(vint.begin() == vint.begin());
+ UNIT_ASSERT(crvint.begin() == vint.begin());
+ UNIT_ASSERT(vint.begin() == crvint.begin());
+ UNIT_ASSERT(crvint.begin() == crvint.begin());
+
+ UNIT_ASSERT(vint.begin() != vint.end());
+ UNIT_ASSERT(crvint.begin() != vint.end());
+ UNIT_ASSERT(vint.begin() != crvint.end());
+ UNIT_ASSERT(crvint.begin() != crvint.end());
+
+ UNIT_ASSERT(vint.rbegin() == vint.rbegin());
+ // Not Standard:
+ //UNIT_ASSERT(vint.rbegin() == crvint.rbegin());
+ //UNIT_ASSERT(crvint.rbegin() == vint.rbegin());
+ UNIT_ASSERT(crvint.rbegin() == crvint.rbegin());
+
+ UNIT_ASSERT(vint.rbegin() != vint.rend());
+ // Not Standard:
+ //UNIT_ASSERT(vint.rbegin() != crvint.rend());
+ //UNIT_ASSERT(crvint.rbegin() != vint.rend());
+ UNIT_ASSERT(crvint.rbegin() != crvint.rend());
+ }
+
+ void TestShrink() {
+ TVector<int> v;
+ v.resize(1000);
+ v.resize(10);
+ v.shrink_to_fit();
+ UNIT_ASSERT_EQUAL(v.capacity(), 10);
+ v.push_back(0);
+ v.shrink_to_fit();
+ UNIT_ASSERT_EQUAL(v.capacity(), 11);
+ }
+
+ /* This test check a potential issue with empty base class
+ * optimization. Some compilers (VC6) do not implement it
+ * correctly resulting ina wrong behavior. */
+ void TestEbo() {
+ // We use heap memory as test failure can corrupt vector internal
+ // representation making executable crash on vector destructor invocation.
+ // We prefer a simple memory leak, internal corruption should be reveal
+ // by size or capacity checks.
+ using V = TVector<int>;
+ V* pv1 = new V(1, 1);
+ V* pv2 = new V(10, 2);
+
+ size_t v1Capacity = pv1->capacity();
+ size_t v2Capacity = pv2->capacity();
+
+ pv1->swap(*pv2);
+
+ UNIT_ASSERT(pv1->size() == 10);
+ UNIT_ASSERT(pv1->capacity() == v2Capacity);
+ UNIT_ASSERT((*pv1)[5] == 2);
+
+ UNIT_ASSERT(pv2->size() == 1);
+ UNIT_ASSERT(pv2->capacity() == v1Capacity);
+ UNIT_ASSERT((*pv2)[0] == 1);
+
+ delete pv2;
+ delete pv1;
+ }
+
+ void TestFillInConstructor() {
+ for (int k = 0; k < 3; ++k) {
+ TVector<int> v(100);
+ UNIT_ASSERT_VALUES_EQUAL(100u, v.size());
+ for (size_t i = 0; i < v.size(); ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(0, v[i]);
+ }
+ // fill with garbage for the next iteration
+ for (size_t i = 0; i < v.size(); ++i) {
+ v[i] = 10;
+ }
+ }
+ }
+
+ struct TPod {
+ int x;
+
+ operator int() {
+ return x;
+ }
+ };
+
+ struct TNonPod {
+ int x;
+ TNonPod() {
+ x = 0;
+ }
+
+ operator int() {
+ return x;
+ }
+ };
+
+ template <typename T>
+ class TDebugAlloc: public std::allocator<T> {
+ public:
+ using TBase = std::allocator<T>;
+
+ T* allocate(typename TBase::size_type n) {
+ auto p = TBase::allocate(n);
+ for (size_t i = 0; i < n; ++i) {
+ memset(p + i, 0xab, sizeof(T));
+ }
+ return p;
+ }
+
+ template <class TOther>
+ struct rebind {
+ using other = TDebugAlloc<TOther>;
+ };
+ };
+
+ template <typename T>
+ void TestYResize() {
+#ifdef _YNDX_LIBCXX_ENABLE_VECTOR_POD_RESIZE_UNINITIALIZED
+ constexpr bool ALLOW_UNINITIALIZED = std::is_pod_v<T>;
+#else
+ constexpr bool ALLOW_UNINITIALIZED = false;
+#endif
+
+ TVector<T, TDebugAlloc<T>> v;
+
+ v.reserve(5);
+ auto firstBegin = v.begin();
+
+ v.yresize(5); // No realloc, no initialization if allowed
+ UNIT_ASSERT(firstBegin == v.begin());
+ for (int i = 0; i < 5; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(bool(v[i]), ALLOW_UNINITIALIZED);
+ }
+
+ v.yresize(20); // Realloc, still no initialization
+ UNIT_ASSERT(firstBegin != v.begin());
+ for (int i = 0; i < 20; ++i) {
+ UNIT_ASSERT_VALUES_EQUAL(bool(v[i]), ALLOW_UNINITIALIZED);
+ }
+ }
+
+ struct TNoDefaultConstructor {
+ TNoDefaultConstructor() = delete;
+ explicit TNoDefaultConstructor(int val)
+ : Val(val)
+ {
+ }
+
+ int Val;
+ };
+
+ void TestCrop() {
+ TVector<TNoDefaultConstructor> vec;
+ vec.emplace_back(42);
+ vec.emplace_back(1337);
+ vec.emplace_back(8888);
+ vec.crop(1); // Should not require default constructor
+ UNIT_ASSERT(vec.size() == 1);
+ UNIT_ASSERT(vec[0].Val == 42);
+ vec.crop(50); // Does nothing if new size is greater than the current size()
+ UNIT_ASSERT(vec.size() == 1);
+ UNIT_ASSERT(vec[0].Val == 42);
+ }
+
+ void TestYResize() {
+ TestYResize<int>();
+ TestYResize<TPod>();
+ TestYResize<TNonPod>();
+ }
+
+ void CheckInitializeList(const TVector<int>& v) {
+ for (size_t i = 0; i < v.size(); ++i) {
+ UNIT_ASSERT_EQUAL(v[i], static_cast<int>(i));
+ }
+ }
+
+ void TestInitializeList() {
+ {
+ TVector<int> v;
+ v.assign({0, 1, 2});
+ CheckInitializeList(v);
+ }
+ {
+ TVector<int> v = {0, 1, 2};
+ CheckInitializeList(v);
+ }
+ {
+ TVector<int> v;
+ v = {0, 1, 2};
+ CheckInitializeList(v);
+ }
+ {
+ TVector<int> v = {0, 3};
+ v.insert(v.begin() + 1, {1, 2});
+ CheckInitializeList(v);
+ }
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TYVectorTest);
diff --git a/util/generic/vector_ut.pyx b/util/generic/vector_ut.pyx
new file mode 100644
index 0000000000..904e67733b
--- /dev/null
+++ b/util/generic/vector_ut.pyx
@@ -0,0 +1,221 @@
+# cython: c_string_type=str, c_string_encoding=utf8
+
+from util.generic.vector cimport TVector
+from util.generic.string cimport TString
+
+import pytest
+import unittest
+
+
+def _check_convert(TVector[TString] x):
+ return x
+
+
+class TestVector(unittest.TestCase):
+
+ def test_ctor1(self):
+ cdef TVector[int] tmp = TVector[int]()
+ self.assertEqual(tmp.size(), 0)
+
+ def test_ctor2(self):
+ cdef TVector[int] tmp = TVector[int](10)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp[0], 0)
+
+ def test_ctor3(self):
+ cdef TVector[int] tmp = TVector[int](10, 42)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp[0], 42)
+
+ def test_ctor4(self):
+ cdef TVector[int] tmp = TVector[int](10, 42)
+ cdef TVector[int] tmp2 = TVector[int](tmp)
+ self.assertEqual(tmp2.size(), 10)
+ self.assertEqual(tmp2[0], 42)
+
+ def test_operator_assign(self):
+ cdef TVector[int] tmp2
+ tmp2.push_back(1)
+ tmp2.push_back(2)
+
+ cdef TVector[int] tmp3
+ tmp3.push_back(1)
+ tmp3.push_back(3)
+
+ self.assertEqual(tmp2[1], 2)
+ self.assertEqual(tmp3[1], 3)
+
+ tmp3 = tmp2
+
+ self.assertEqual(tmp2[1], 2)
+ self.assertEqual(tmp3[1], 2)
+
+ def test_compare(self):
+ cdef TVector[int] tmp1
+ tmp1.push_back(1)
+ tmp1.push_back(2)
+
+ cdef TVector[int] tmp2
+ tmp2.push_back(1)
+ tmp2.push_back(2)
+
+ cdef TVector[int] tmp3
+ tmp3.push_back(1)
+ tmp3.push_back(3)
+
+ self.assertTrue(tmp1 == tmp2)
+ self.assertTrue(tmp1 != tmp3)
+
+ self.assertTrue(tmp1 < tmp3)
+ self.assertTrue(tmp1 <= tmp3)
+
+ self.assertTrue(tmp3 > tmp1)
+ self.assertTrue(tmp3 >= tmp1)
+
+ def test_index(self):
+ cdef TVector[int] tmp = TVector[int](10, 42)
+
+ self.assertEqual(tmp[0], 42)
+ self.assertEqual(tmp[5], 42)
+
+ self.assertEqual(tmp.data()[0], 42)
+ self.assertEqual(tmp.data()[5], 42)
+
+ self.assertEqual(tmp.at(0), 42)
+ self.assertEqual(tmp.at(5), 42)
+
+ with pytest.raises(IndexError):
+ tmp.at(100)
+
+ def test_push_pop_back(self):
+ cdef TVector[int] tmp
+ self.assertEqual(tmp.size(), 0)
+
+ tmp.push_back(42)
+ self.assertEqual(tmp.size(), 1)
+ self.assertEqual(tmp.back(), 42)
+
+ tmp.push_back(77)
+ self.assertEqual(tmp.size(), 2)
+ self.assertEqual(tmp.back(), 77)
+
+ tmp.pop_back()
+ self.assertEqual(tmp.size(), 1)
+ self.assertEqual(tmp.back(), 42)
+
+ tmp.pop_back()
+ self.assertEqual(tmp.size(), 0)
+
+ def test_front(self):
+ cdef TVector[int] tmp
+ tmp.push_back(42)
+ self.assertEqual(tmp.front(), 42)
+
+ def test_empty(self):
+ cdef TVector[int] tmp
+ self.assertTrue(tmp.empty())
+ tmp.push_back(42)
+ self.assertFalse(tmp.empty())
+
+ def test_max_size(self):
+ cdef TVector[int] tmp
+ self.assertTrue(tmp.max_size() > 0)
+
+ def test_reserve_resize(self):
+ cdef TVector[int] tmp
+ tmp.reserve(1000)
+ self.assertEqual(tmp.capacity(), 1000)
+
+ tmp.resize(100)
+ self.assertEqual(tmp.size(), 100)
+ self.assertEqual(tmp.front(), 0)
+ self.assertEqual(tmp.back(), 0)
+
+ tmp.shrink_to_fit()
+ tmp.clear()
+
+ tmp.resize(100, 42)
+ self.assertEqual(tmp.size(), 100)
+ self.assertEqual(tmp.front(), 42)
+ self.assertEqual(tmp.back(), 42)
+
+ def test_iter(self):
+ cdef TVector[int] tmp
+ tmp.push_back(1)
+ tmp.push_back(20)
+ tmp.push_back(300)
+
+ self.assertEqual([i for i in tmp], [1, 20, 300])
+
+ def test_iterator(self):
+ cdef TVector[int] tmp
+
+ self.assertTrue(tmp.begin() == tmp.end())
+ self.assertTrue(tmp.rbegin() == tmp.rend())
+ self.assertTrue(tmp.const_begin() == tmp.const_end())
+ self.assertTrue(tmp.const_rbegin() == tmp.const_rend())
+
+ tmp.push_back(1)
+
+ self.assertTrue(tmp.begin() != tmp.end())
+ self.assertTrue(tmp.rbegin() != tmp.rend())
+ self.assertTrue(tmp.const_begin() != tmp.const_end())
+ self.assertTrue(tmp.const_rbegin() != tmp.const_rend())
+
+ self.assertTrue(tmp.begin() < tmp.end())
+ self.assertTrue(tmp.rbegin() < tmp.rend())
+ self.assertTrue(tmp.const_begin() < tmp.const_end())
+ self.assertTrue(tmp.const_rbegin() < tmp.const_rend())
+
+ self.assertTrue(tmp.begin() + 1 == tmp.end())
+ self.assertTrue(tmp.rbegin() + 1 == tmp.rend())
+ self.assertTrue(tmp.const_begin() + 1 == tmp.const_end())
+ self.assertTrue(tmp.const_rbegin() + 1 == tmp.const_rend())
+
+ def test_assign(self):
+ cdef TVector[int] tmp
+
+ tmp.assign(10, 42)
+ self.assertEqual(tmp.size(), 10)
+ self.assertEqual(tmp.front(), 42)
+ self.assertEqual(tmp.back(), 42)
+
+ def test_insert(self):
+ cdef TVector[int] tmp
+ tmp.push_back(1)
+ tmp.push_back(2)
+ tmp.push_back(3)
+
+ cdef TVector[int] tmp2
+ tmp2.push_back(7)
+ tmp2.push_back(9)
+
+ tmp.insert(tmp.begin(), 8)
+ self.assertEqual([i for i in tmp], [8, 1, 2, 3])
+
+ tmp.insert(tmp.begin(), 2, 6)
+ self.assertEqual([i for i in tmp], [6, 6, 8, 1, 2, 3])
+
+ tmp.insert(tmp.begin(), tmp2.begin(), tmp2.end())
+ self.assertEqual([i for i in tmp], [7, 9, 6, 6, 8, 1, 2, 3])
+
+ def test_erase(self):
+ cdef TVector[int] tmp
+ tmp.push_back(1)
+ tmp.push_back(2)
+ tmp.push_back(3)
+ tmp.push_back(4)
+
+ tmp.erase(tmp.begin() + 1)
+ self.assertEqual([i for i in tmp], [1, 3, 4])
+
+ tmp.erase(tmp.begin(), tmp.begin() + 2)
+ self.assertEqual([i for i in tmp], [4])
+
+ def test_convert(self):
+ src = ['foo', 'bar', 'baz']
+ self.assertEqual(_check_convert(src), src)
+
+ bad_src = ['foo', 42]
+ with self.assertRaises(TypeError):
+ _check_convert(bad_src)
diff --git a/util/generic/xrange.cpp b/util/generic/xrange.cpp
new file mode 100644
index 0000000000..74ee8a40fe
--- /dev/null
+++ b/util/generic/xrange.cpp
@@ -0,0 +1 @@
+#include "xrange.h"
diff --git a/util/generic/xrange.h b/util/generic/xrange.h
new file mode 100644
index 0000000000..5fc8c82912
--- /dev/null
+++ b/util/generic/xrange.h
@@ -0,0 +1,268 @@
+#pragma once
+
+#include "typetraits.h"
+#include "utility.h"
+#include <util/system/yassert.h>
+#include <iterator>
+
+/** @file
+ * Some similar for python xrange(): https://docs.python.org/2/library/functions.html#xrange
+ * Discussion: https://clubs.at.yandex-team.ru/arcadia/6124
+ *
+ * Example usage:
+ * for (auto i: xrange(MyVector.size())) { // instead for (size_t i = 0; i < MyVector.size(); ++i)
+ * DoSomething(i, MyVector[i]);
+ * }
+ *
+ * TVector<int> arithmeticSeq = xrange(10); // instead: TVector<int> arithmeticSeq; for(size_t i = 0; i < 10; ++i) { arithmeticSeq.push_back(i); }
+ *
+ */
+
+namespace NPrivate {
+ template <typename T>
+ class TSimpleXRange {
+ using TDiff = decltype(T() - T());
+
+ public:
+ constexpr TSimpleXRange(T start, T finish) noexcept
+ : Start(start)
+ , Finish(Max(start, finish))
+ {
+ }
+
+ class TIterator {
+ public:
+ using value_type = T;
+ using difference_type = TDiff;
+ using pointer = const T*;
+ using reference = const T&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ constexpr TIterator(T value) noexcept
+ : Value(value)
+ {
+ }
+
+ constexpr T operator*() const noexcept {
+ return Value;
+ }
+
+ constexpr bool operator!=(const TIterator& other) const noexcept {
+ return Value != other.Value;
+ }
+
+ constexpr bool operator==(const TIterator& other) const noexcept {
+ return Value == other.Value;
+ }
+
+ TIterator& operator++() noexcept {
+ ++Value;
+ return *this;
+ }
+
+ TIterator& operator--() noexcept {
+ --Value;
+ return *this;
+ }
+
+ constexpr TDiff operator-(const TIterator& b) const noexcept {
+ return Value - b.Value;
+ }
+
+ template <typename IntType>
+ constexpr TIterator operator+(const IntType& b) const noexcept {
+ return TIterator(Value + b);
+ }
+
+ template <typename IntType>
+ TIterator& operator+=(const IntType& b) noexcept {
+ Value += b;
+ return *this;
+ }
+
+ template <typename IntType>
+ constexpr TIterator operator-(const IntType& b) const noexcept {
+ return TIterator(Value - b);
+ }
+
+ template <typename IntType>
+ TIterator& operator-=(const IntType& b) noexcept {
+ Value -= b;
+ return *this;
+ }
+
+ constexpr bool operator<(const TIterator& b) const noexcept {
+ return Value < b.Value;
+ }
+
+ private:
+ T Value;
+ };
+
+ using value_type = T;
+ using iterator = TIterator;
+ using const_iterator = TIterator;
+
+ constexpr TIterator begin() const noexcept {
+ return TIterator(Start);
+ }
+
+ constexpr TIterator end() const noexcept {
+ return TIterator(Finish);
+ }
+
+ constexpr T size() const noexcept {
+ return Finish - Start;
+ }
+
+ template <class Container>
+ operator Container() const {
+ return Container(begin(), end());
+ }
+
+ private:
+ T Start;
+ T Finish;
+ };
+
+ template <typename T>
+ class TSteppedXRange {
+ using TDiff = decltype(T() - T());
+
+ public:
+ constexpr TSteppedXRange(T start, T finish, TDiff step) noexcept
+ : Start_(start)
+ , Step_(step)
+ , Finish_(CalcRealFinish(Start_, finish, Step_))
+ {
+ static_assert(std::is_integral<T>::value || std::is_pointer<T>::value, "T should be integral type or pointer");
+ }
+
+ class TIterator {
+ public:
+ using value_type = T;
+ using difference_type = TDiff;
+ using pointer = const T*;
+ using reference = const T&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ constexpr TIterator(T value, const TSteppedXRange& parent) noexcept
+ : Value_(value)
+ , Parent_(&parent)
+ {
+ }
+
+ constexpr T operator*() const noexcept {
+ return Value_;
+ }
+
+ constexpr bool operator!=(const TIterator& other) const noexcept {
+ return Value_ != other.Value_;
+ }
+
+ constexpr bool operator==(const TIterator& other) const noexcept {
+ return Value_ == other.Value_;
+ }
+
+ TIterator& operator++() noexcept {
+ Value_ += Parent_->Step_;
+ return *this;
+ }
+
+ TIterator& operator--() noexcept {
+ Value_ -= Parent_->Step_;
+ return *this;
+ }
+
+ constexpr TDiff operator-(const TIterator& b) const noexcept {
+ return (Value_ - b.Value_) / Parent_->Step_;
+ }
+
+ template <typename IntType>
+ constexpr TIterator operator+(const IntType& b) const noexcept {
+ return TIterator(*this) += b;
+ }
+
+ template <typename IntType>
+ TIterator& operator+=(const IntType& b) noexcept {
+ Value_ += b * Parent_->Step_;
+ return *this;
+ }
+
+ template <typename IntType>
+ constexpr TIterator operator-(const IntType& b) const noexcept {
+ return TIterator(*this) -= b;
+ }
+
+ template <typename IntType>
+ TIterator& operator-=(const IntType& b) noexcept {
+ Value_ -= b * Parent_->Step_;
+ return *this;
+ }
+
+ private:
+ T Value_;
+ const TSteppedXRange* Parent_;
+ };
+
+ using value_type = T;
+ using iterator = TIterator;
+ using const_iterator = TIterator;
+
+ constexpr TIterator begin() const noexcept {
+ return TIterator(Start_, *this);
+ }
+
+ constexpr TIterator end() const noexcept {
+ return TIterator(Finish_, *this);
+ }
+
+ static T CalcRealFinish(T start, T expFinish, TDiff step) {
+ Y_ASSERT(step != 0);
+ if (step > 0) {
+ if (expFinish > start) {
+ return start + step * ((expFinish - 1 - start) / step + 1);
+ }
+ return start;
+ }
+ return start - TSteppedXRange<TDiff>::CalcRealFinish(0, start - expFinish, -step);
+ }
+
+ constexpr T size() const noexcept {
+ return (Finish_ - Start_) / Step_;
+ }
+
+ template <class Container>
+ operator Container() const {
+ return Container(begin(), end());
+ }
+
+ private:
+ const T Start_;
+ const TDiff Step_;
+ const T Finish_;
+ };
+
+}
+
+/**
+ * generate arithmetic progression that starts at start with certain step and stop at finish (not including)
+ *
+ * @param step must be non-zero
+ */
+template <typename T>
+constexpr ::NPrivate::TSteppedXRange<T> xrange(T start, T finish, decltype(T() - T()) step) noexcept {
+ return {start, finish, step};
+}
+
+/// generate sequence [start; finish)
+template <typename T>
+constexpr ::NPrivate::TSimpleXRange<T> xrange(T start, T finish) noexcept {
+ return {start, finish};
+}
+
+/// generate sequence [0; finish)
+template <typename T>
+constexpr auto xrange(T finish) noexcept -> decltype(xrange(T(), finish)) {
+ return xrange(T(), finish);
+}
diff --git a/util/generic/xrange_ut.cpp b/util/generic/xrange_ut.cpp
new file mode 100644
index 0000000000..8106da03e7
--- /dev/null
+++ b/util/generic/xrange_ut.cpp
@@ -0,0 +1,217 @@
+#include "xrange.h"
+
+#include "algorithm.h"
+#include "maybe.h"
+#include "vector.h"
+#include <library/cpp/testing/unittest/registar.h>
+#include <util/string/builder.h>
+
+Y_UNIT_TEST_SUITE(XRange) {
+ void TestXRangeImpl(size_t begin, size_t end) {
+ size_t count = 0;
+ size_t sum = 0;
+ size_t first = 42;
+ bool firstInited = false;
+ size_t last = 0;
+
+ for (auto i : xrange(begin, end)) {
+ ++count;
+ sum += i;
+ last = i;
+ if (!firstInited) {
+ first = i;
+ firstInited = true;
+ }
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(count, end - begin);
+ UNIT_ASSERT_VALUES_EQUAL(first, begin);
+ UNIT_ASSERT_VALUES_EQUAL(last, end - 1);
+ UNIT_ASSERT_VALUES_EQUAL(sum, count * (first + last) / 2);
+ }
+
+ void TestSteppedXRangeImpl(int begin, int end, int step, const TVector<int>& expected) {
+ size_t expInd = 0;
+ for (auto i : xrange(begin, end, step)) {
+ UNIT_ASSERT(expInd < expected.size());
+ UNIT_ASSERT_VALUES_EQUAL(i, expected[expInd]);
+ ++expInd;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(expInd, expected.size());
+ }
+
+ Y_UNIT_TEST(IncrementWorks) {
+ TestXRangeImpl(0, 10);
+ TestXRangeImpl(10, 20);
+ }
+
+ Y_UNIT_TEST(DecrementWorks) {
+ TestSteppedXRangeImpl(10, 0, -1, {10, 9, 8, 7, 6, 5, 4, 3, 2, 1});
+ TestSteppedXRangeImpl(10, -1, -1, {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0});
+ TestSteppedXRangeImpl(20, 9, -1, {20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10});
+ }
+
+ Y_UNIT_TEST(StepWorks) {
+ TestSteppedXRangeImpl(0, 0, 1, {});
+ TestSteppedXRangeImpl(0, 9, 3, {0, 3, 6});
+ TestSteppedXRangeImpl(0, 10, 3, {0, 3, 6, 9});
+ TestSteppedXRangeImpl(0, 11, 3, {0, 3, 6, 9});
+ TestSteppedXRangeImpl(0, 12, 3, {0, 3, 6, 9});
+ TestSteppedXRangeImpl(0, 13, 3, {0, 3, 6, 9, 12});
+ TestSteppedXRangeImpl(0, 10, 2, {0, 2, 4, 6, 8});
+ TestSteppedXRangeImpl(15, 0, -4, {15, 11, 7, 3});
+ TestSteppedXRangeImpl(15, -1, -4, {15, 11, 7, 3});
+ TestSteppedXRangeImpl(15, -2, -4, {15, 11, 7, 3, -1});
+ }
+
+ Y_UNIT_TEST(PointersWorks) {
+ TVector<size_t> data = {3, 1, 4, 1, 5, 9, 2, 6};
+ const size_t digSumExpected = Accumulate(data.begin(), data.end(), static_cast<size_t>(0));
+ size_t digSumByIt = 0;
+ for (auto ptr : xrange(data.begin(), data.end())) {
+ digSumByIt += *ptr;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(digSumByIt, digSumExpected);
+ size_t digSumByPtr = 0;
+ for (auto ptr : xrange(&data[0], &data[0] + data.size())) {
+ digSumByPtr += *ptr;
+ }
+ UNIT_ASSERT_VALUES_EQUAL(digSumByPtr, digSumExpected);
+ }
+
+ Y_UNIT_TEST(SizeMethodCheck) {
+ UNIT_ASSERT_VALUES_EQUAL(xrange(5).size(), 5);
+ UNIT_ASSERT_VALUES_EQUAL(xrange(0, 5, 2).size(), 3);
+ UNIT_ASSERT_VALUES_EQUAL(xrange(0, 6, 2).size(), 3);
+ }
+
+ class TVectorChild: public TVector<size_t> {
+ public:
+ template <typename TIterator>
+ TVectorChild(TIterator a, TIterator b)
+ : TVector<size_t>(a, b)
+ {
+ }
+ };
+
+ Y_UNIT_TEST(ConvertionWorks) {
+ TVector<size_t> data = {0, 1, 2, 3, 4, 5, 6, 7, 8};
+
+ TVector<size_t> convertionResults[] = {xrange<size_t>(9),
+ xrange<ui32>(0, 9),
+ xrange(0, 9, 1)};
+
+ for (const auto& arr : convertionResults) {
+ UNIT_ASSERT(arr == data);
+ }
+
+ TVectorChild sons[] = {xrange(0, 9),
+ xrange(0, 9, 1)};
+
+ for (const auto& arr : sons) {
+ UNIT_ASSERT(arr == data);
+ }
+ }
+
+ template <class XRangeContainer>
+ void TestEmptyRanges(const XRangeContainer& c) {
+ for (const auto& emptyRange : c) {
+ UNIT_ASSERT_VALUES_EQUAL(emptyRange.size(), 0);
+
+ for (auto i : emptyRange) {
+ Y_UNUSED(i);
+ UNIT_ASSERT(false);
+ }
+
+ using TValueType = decltype(*emptyRange.begin());
+ const TVector<TValueType> asVector = emptyRange;
+ UNIT_ASSERT(asVector.empty());
+ }
+ }
+
+ Y_UNIT_TEST(EmptySimpleRange) {
+ using TSimpleRange = decltype(xrange(1));
+
+ const TSimpleRange emptySimpleRanges[] = {
+ xrange(-1),
+ xrange(-10),
+ xrange(0, -5),
+ xrange(10, 10),
+ xrange(10, 9),
+ };
+
+ TestEmptyRanges(emptySimpleRanges);
+ }
+
+ Y_UNIT_TEST(EmptySteppedRange) {
+ using TSteppedRange = decltype(xrange(1, 10, 1));
+
+ const TSteppedRange emptySteppedRanges[] = {
+ xrange(5, 5, 1),
+ xrange(5, 0, 5),
+ xrange(0, -1, 5),
+ xrange(0, 1, -1),
+ xrange(0, -10, 10),
+ };
+
+ TestEmptyRanges(emptySteppedRanges);
+ }
+
+ template <class TRange>
+ static void TestIteratorDifferenceImpl(TRange range, int a, int b, TMaybe<int> step) {
+ auto fmtCase = [&]() -> TString { return TStringBuilder() << "xrange(" << a << ", " << b << (step ? ", " + ToString(*step) : TString{}) << ")"; };
+ auto begin = std::begin(range);
+ auto end = std::end(range);
+ auto distance = end - begin;
+ UNIT_ASSERT_VALUES_EQUAL_C(range.size(), distance, fmtCase());
+ UNIT_ASSERT_EQUAL_C(end, begin + distance, fmtCase());
+ }
+
+ Y_UNIT_TEST(IteratorDifference) {
+ for (int a = -20; a <= 20; ++a) {
+ for (int b = -20; b <= 20; ++b) {
+ for (int step = -25; step <= 25; ++step) {
+ if (step != 0) {
+ TestIteratorDifferenceImpl(xrange(a, b, step), a, b, step);
+ }
+ }
+ TestIteratorDifferenceImpl(xrange(a, b), a, b, Nothing());
+ }
+ }
+ }
+
+ Y_UNIT_TEST(Advance) {
+ {
+ auto range = xrange(30, 160, 7);
+ auto it = std::begin(range);
+ it += 5;
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 5), *it);
+ UNIT_ASSERT_VALUES_EQUAL(65, *it);
+ it -= 2;
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 3), *it);
+ UNIT_ASSERT_VALUES_EQUAL(51, *it);
+ std::advance(it, 10);
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 13), *it);
+ UNIT_ASSERT_VALUES_EQUAL(121, *it);
+ std::advance(it, -5);
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 8), *it);
+ UNIT_ASSERT_VALUES_EQUAL(86, *it);
+ }
+ {
+ auto range = xrange(-20, 100);
+ auto it = std::begin(range);
+ it += 5;
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 5), *it);
+ UNIT_ASSERT_VALUES_EQUAL(-15, *it);
+ it -= 2;
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 3), *it);
+ UNIT_ASSERT_VALUES_EQUAL(-17, *it);
+ std::advance(it, 30);
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 33), *it);
+ UNIT_ASSERT_VALUES_EQUAL(13, *it);
+ std::advance(it, -8);
+ UNIT_ASSERT_VALUES_EQUAL(*(std::begin(range) + 25), *it);
+ UNIT_ASSERT_VALUES_EQUAL(5, *it);
+ }
+ }
+}
diff --git a/util/generic/ya.make b/util/generic/ya.make
new file mode 100644
index 0000000000..79c9498ddd
--- /dev/null
+++ b/util/generic/ya.make
@@ -0,0 +1,6 @@
+OWNER(g:util)
+SUBSCRIBER(g:util-subscribers)
+
+RECURSE_FOR_TESTS(
+ ut
+)
diff --git a/util/generic/yexception.cpp b/util/generic/yexception.cpp
new file mode 100644
index 0000000000..2ce6c4369d
--- /dev/null
+++ b/util/generic/yexception.cpp
@@ -0,0 +1,122 @@
+#include "bt_exception.h"
+#include "yexception.h"
+
+#include <util/system/backtrace.h>
+#include <util/system/type_name.h>
+
+#include <cxxabi.h>
+
+#include <stdexcept>
+
+#include <cstdio>
+
+TString FormatExc(const std::exception& exception) {
+ return TString::Join(TStringBuf("("), TypeName(exception), TStringBuf(") "), exception.what());
+}
+
+TString CurrentExceptionMessage() {
+ auto exceptionPtr = std::current_exception();
+ if (exceptionPtr) {
+ try {
+ std::rethrow_exception(exceptionPtr);
+ } catch (const yexception& e) {
+ const TBackTrace* bt = e.BackTrace();
+
+ if (bt) {
+ return TString::Join(bt->PrintToString(), TStringBuf("\n"), FormatExc(e));
+ }
+
+ return FormatExc(e);
+ } catch (const std::exception& e) {
+ return FormatExc(e);
+ } catch (...) {
+ }
+
+ return "unknown error";
+ }
+
+ return "(NO EXCEPTION)";
+}
+
+bool UncaughtException() noexcept {
+// FIXME: use std::uncaught_exceptions() unconditionally after DEVTOOLS-8811
+#if defined(__cpp_lib_uncaught_exceptions) && !defined(_LIBCPP_AVAILABILITY_UNCAUGHT_EXCEPTIONS)
+ return std::uncaught_exceptions() > 0;
+#else
+ return std::uncaught_exception();
+#endif
+}
+
+std::string CurrentExceptionTypeName() {
+#if defined(_linux_) || defined(_darwin_)
+ std::type_info* currentExceptionTypePtr = abi::__cxa_current_exception_type();
+ if (currentExceptionTypePtr) {
+ return TypeName(*currentExceptionTypePtr);
+ }
+#endif
+ //There is no abi::__cxa_current_exception_type() on Windows.
+ //Emulated it with rethrow - catch construction.
+ std::exception_ptr currentException = std::current_exception();
+ Y_ASSERT(currentException != nullptr);
+ try {
+ std::rethrow_exception(currentException);
+ } catch (const std::exception& e) {
+ return TypeName(typeid(e));
+ } catch (...) {
+ return "unknown type";
+ }
+}
+
+void TSystemError::Init() {
+ yexception& exc = *this;
+
+ exc << TStringBuf("(");
+ exc << TStringBuf(LastSystemErrorText(Status_));
+ exc << TStringBuf(") ");
+}
+
+NPrivateException::yexception::yexception() {
+ ZeroTerminate();
+}
+
+TStringBuf NPrivateException::yexception::AsStrBuf() const {
+ if (Buf_.Left()) {
+ return TStringBuf(Buf_.Data(), Buf_.Filled());
+ }
+
+ return TStringBuf(Buf_.Data(), Buf_.Filled() - 1);
+}
+
+void NPrivateException::yexception::ZeroTerminate() noexcept {
+ char* end = (char*)Buf_.Current();
+
+ if (!Buf_.Left()) {
+ --end;
+ }
+
+ *end = 0;
+}
+
+const char* NPrivateException::yexception::what() const noexcept {
+ return Buf_.Data();
+}
+
+const TBackTrace* NPrivateException::yexception::BackTrace() const noexcept {
+ return nullptr;
+}
+
+void fputs(const std::exception& e, FILE* f) {
+ char message[256];
+ size_t len = Min(sizeof(message) - 2, strlcpy(message, e.what(), sizeof(message) - 1));
+ message[len++] = '\n';
+ message[len] = 0;
+ fputs(message, f);
+}
+
+void ::NPrivate::ThrowYException(const ::NPrivate::TSimpleExceptionMessage& sm) {
+ throw sm.Location + yexception() << sm.Message;
+}
+
+void ::NPrivate::ThrowYExceptionWithBacktrace(const ::NPrivate::TSimpleExceptionMessage& sm) {
+ throw sm.Location + TWithBackTrace<yexception>() << sm.Message;
+}
diff --git a/util/generic/yexception.h b/util/generic/yexception.h
new file mode 100644
index 0000000000..b0c604e8c4
--- /dev/null
+++ b/util/generic/yexception.h
@@ -0,0 +1,231 @@
+#pragma once
+
+#include "bt_exception.h"
+#include "strbuf.h"
+#include "string.h"
+#include "utility.h"
+#include "va_args.h"
+#include <utility>
+
+#include <util/stream/tempbuf.h>
+#include <util/system/compat.h>
+#include <util/system/compiler.h>
+#include <util/system/defaults.h>
+#include <util/system/error.h>
+#include <util/system/src_location.h>
+#include <util/system/platform.h>
+
+#include <exception>
+
+#include <cstdio>
+
+class TBackTrace;
+
+namespace NPrivateException {
+ class TTempBufCuttingWrapperOutput: public IOutputStream {
+ public:
+ TTempBufCuttingWrapperOutput(TTempBuf& tempbuf)
+ : TempBuf_(tempbuf)
+ {
+ }
+
+ void DoWrite(const void* data, size_t len) override {
+ TempBuf_.Append(data, Min(len, TempBuf_.Left()));
+ }
+
+ private:
+ TTempBuf& TempBuf_;
+ };
+
+ class yexception: public std::exception {
+ public:
+ yexception();
+ yexception(const yexception&) = default;
+ yexception(yexception&&) = default;
+
+ yexception& operator=(const yexception&) = default;
+ yexception& operator=(yexception&&) = default;
+
+ const char* what() const noexcept override;
+ virtual const TBackTrace* BackTrace() const noexcept;
+
+ template <class T>
+ inline void Append(const T& t) {
+ TTempBufCuttingWrapperOutput tempBuf(Buf_);
+ static_cast<IOutputStream&>(tempBuf) << t;
+ ZeroTerminate();
+ }
+
+ TStringBuf AsStrBuf() const;
+
+ private:
+ void ZeroTerminate() noexcept;
+
+ private:
+ TTempBuf Buf_;
+ };
+
+ template <class E, class T>
+ static inline std::enable_if_t<std::is_base_of<yexception, std::decay_t<E>>::value, E&&>
+ operator<<(E&& e, const T& t) {
+ e.Append(t);
+
+ return std::forward<E>(e);
+ }
+
+ template <class T>
+ static inline T&& operator+(const TSourceLocation& sl, T&& t) {
+ return std::forward<T>(t << sl << TStringBuf(": "));
+ }
+}
+
+class yexception: public NPrivateException::yexception {
+};
+
+Y_DECLARE_OUT_SPEC(inline, yexception, stream, value) {
+ stream << value.AsStrBuf();
+}
+
+class TSystemError: public yexception {
+public:
+ TSystemError(int status)
+ : Status_(status)
+ {
+ Init();
+ }
+
+ TSystemError()
+ : TSystemError(LastSystemError())
+ {
+ }
+
+ int Status() const noexcept {
+ return Status_;
+ }
+
+private:
+ void Init();
+
+private:
+ int Status_;
+};
+
+class TIoException: public TSystemError {
+};
+
+class TIoSystemError: public TIoException {
+};
+
+class TFileError: public TIoSystemError {
+};
+
+/**
+ * TBadArgumentException should be thrown when an argument supplied to some function (or constructor)
+ * is invalid or incorrect.
+ *
+ * \note
+ * A special case when such argument is given to a function which performs type casting
+ * (e.g. integer from string) is covered by the TBadCastException class which is derived from
+ * TBadArgumentException.
+ */
+struct TBadArgumentException: public virtual yexception {
+};
+
+/**
+ * TBadCastException should be thrown to indicate the failure of some type casting procedure
+ * (e.g. reading an integer parameter from string).
+ */
+struct TBadCastException: public virtual TBadArgumentException {
+};
+
+#define ythrow throw __LOCATION__ +
+
+namespace NPrivate {
+ /// Encapsulates data for one of the most common case in which
+ /// exception message contists of single constant string
+ struct TSimpleExceptionMessage {
+ TSourceLocation Location;
+ TStringBuf Message;
+ };
+
+ [[noreturn]] void ThrowYException(const TSimpleExceptionMessage& sm);
+ [[noreturn]] void ThrowYExceptionWithBacktrace(const TSimpleExceptionMessage& sm);
+}
+
+void fputs(const std::exception& e, FILE* f = stderr);
+
+TString CurrentExceptionMessage();
+
+/*
+ * A neat method that detects wrether stack unwinding is in progress.
+ * As its std counterpart (that is std::uncaught_exception())
+ * was removed from the standard, this method uses std::uncaught_exceptions() internally.
+ *
+ * If you are struggling to use this method, please, consider reading
+ *
+ * http://www.gotw.ca/gotw/047.htm
+ * and
+ * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4152.pdf
+ *
+ * DO NOT USE THIS METHOD IN DESTRUCTORS.
+ */
+bool UncaughtException() noexcept;
+
+std::string CurrentExceptionTypeName();
+
+TString FormatExc(const std::exception& exception);
+
+#define Y_ENSURE_EX(CONDITION, THROW_EXPRESSION) \
+ do { \
+ if (Y_UNLIKELY(!(CONDITION))) { \
+ ythrow THROW_EXPRESSION; \
+ } \
+ } while (false)
+
+/// @def Y_ENSURE_SIMPLE
+/// This macro works like the Y_ENSURE, but requires the second argument to be a constant string view.
+/// Should not be used directly.
+#define Y_ENSURE_SIMPLE(CONDITION, MESSAGE, THROW_FUNCTION) \
+ do { \
+ if (Y_UNLIKELY(!(CONDITION))) { \
+ /* use variable to guarantee evaluation at compile time */ \
+ static constexpr const ::NPrivate::TSimpleExceptionMessage __SIMPLE_EXCEPTION_MESSAGE{__LOCATION__, (MESSAGE)}; \
+ THROW_FUNCTION(__SIMPLE_EXCEPTION_MESSAGE); \
+ } \
+ } while (false)
+
+#define Y_ENSURE_IMPL_1(CONDITION) Y_ENSURE_SIMPLE(CONDITION, ::TStringBuf("Condition violated: `" Y_STRINGIZE(CONDITION) "'"), ::NPrivate::ThrowYException)
+#define Y_ENSURE_IMPL_2(CONDITION, MESSAGE) Y_ENSURE_EX(CONDITION, yexception() << MESSAGE)
+
+#define Y_ENSURE_BT_IMPL_1(CONDITION) Y_ENSURE_SIMPLE(CONDITION, ::TStringBuf("Condition violated: `" Y_STRINGIZE(CONDITION) "'"), ::NPrivate::ThrowYExceptionWithBacktrace)
+#define Y_ENSURE_BT_IMPL_2(CONDITION, MESSAGE) Y_ENSURE_EX(CONDITION, TWithBackTrace<yexception>() << MESSAGE)
+
+/**
+ * @def Y_ENSURE
+ *
+ * This macro is inteded to use as a shortcut for `if () { throw }`.
+ *
+ * @code
+ * void DoSomethingLovely(const int x, const int y) {
+ * Y_ENSURE(x > y, "`x` must be greater than `y`");
+ * Y_ENSURE(x > y); // if you are too lazy
+ * // actually doing something nice here
+ * }
+ * @endcode
+ */
+#define Y_ENSURE(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, Y_ENSURE_IMPL_2, Y_ENSURE_IMPL_1)(__VA_ARGS__))
+
+/**
+ * @def Y_ENSURE_BT
+ *
+ * This macro is inteded to use as a shortcut for `if () { throw TWithBackTrace<yexception>() << "message"; }`.
+ *
+ * @code
+ * void DoSomethingLovely(const int x, const int y) {
+ * Y_ENSURE_BT(x > y, "`x` must be greater than `y`");
+ * Y_ENSURE_BT(x > y); // if you are too lazy
+ * // actually doing something nice here
+ * }
+ * @endcode
+ */
+#define Y_ENSURE_BT(...) Y_PASS_VA_ARGS(Y_MACRO_IMPL_DISPATCHER_2(__VA_ARGS__, Y_ENSURE_BT_IMPL_2, Y_ENSURE_BT_IMPL_1)(__VA_ARGS__))
diff --git a/util/generic/yexception_ut.c b/util/generic/yexception_ut.c
new file mode 100644
index 0000000000..3880c82e29
--- /dev/null
+++ b/util/generic/yexception_ut.c
@@ -0,0 +1,5 @@
+#include "yexception_ut.h"
+
+void TestCallback(TCallbackFun f, int i) {
+ f(i);
+}
diff --git a/util/generic/yexception_ut.cpp b/util/generic/yexception_ut.cpp
new file mode 100644
index 0000000000..cb3e29fed8
--- /dev/null
+++ b/util/generic/yexception_ut.cpp
@@ -0,0 +1,392 @@
+#include "yexception.h"
+
+static inline void Throw1DontMove() {
+ ythrow yexception() << "blabla"; // don't move this line
+}
+
+static inline void Throw2DontMove() {
+ ythrow yexception() << 1 << " qw " << 12.1; // don't move this line
+}
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/generic/algorithm.h>
+#include <util/memory/tempbuf.h>
+#include <util/random/mersenne.h>
+#include <util/stream/output.h>
+#include <util/string/subst.h>
+
+#include "yexception_ut.h"
+#include "bt_exception.h"
+
+#if defined(_MSC_VER)
+ #pragma warning(disable : 4702) /*unreachable code*/
+#endif
+
+static void CallbackFun(int i) {
+ throw i;
+}
+
+static IOutputStream* OUTS = nullptr;
+
+namespace NOuter::NInner {
+ void Compare10And20() {
+ Y_ENSURE(10 > 20);
+ }
+}
+
+class TExceptionTest: public TTestBase {
+ UNIT_TEST_SUITE(TExceptionTest);
+ UNIT_TEST_EXCEPTION(TestException, yexception)
+ UNIT_TEST_EXCEPTION(TestLineInfo, yexception)
+ UNIT_TEST(TestCurrentExceptionMessageWhenThereisNoException)
+ UNIT_TEST(TestFormat1)
+ UNIT_TEST(TestRaise1)
+ UNIT_TEST(TestVirtuality)
+ UNIT_TEST(TestVirtualInheritance)
+ UNIT_TEST(TestMixedCode)
+ UNIT_TEST(TestBackTrace)
+ UNIT_TEST(TestEnsureWithBackTrace1)
+ UNIT_TEST(TestEnsureWithBackTrace2)
+ UNIT_TEST(TestRethrowAppend)
+ UNIT_TEST(TestMacroOverload)
+ UNIT_TEST(TestMessageCrop)
+ UNIT_TEST(TestTIoSystemErrorSpecialMethods)
+ UNIT_TEST(TestCurrentExceptionTypeNameMethod)
+ UNIT_TEST_SUITE_END();
+
+private:
+ inline void TestRethrowAppend() {
+ try {
+ try {
+ ythrow yexception() << "it";
+ } catch (yexception& e) {
+ e << "happens";
+
+ throw;
+ }
+ } catch (...) {
+ UNIT_ASSERT(CurrentExceptionMessage().Contains("ithappens"));
+ }
+ }
+
+ inline void TestCurrentExceptionMessageWhenThereisNoException() {
+ UNIT_ASSERT(CurrentExceptionMessage() == "(NO EXCEPTION)");
+ }
+
+ inline void TestBackTrace() {
+ try {
+ ythrow TWithBackTrace<TIoSystemError>() << "test";
+ } catch (...) {
+ UNIT_ASSERT(CurrentExceptionMessage().find('\n') != TString::npos);
+
+ return;
+ }
+
+ UNIT_ASSERT(false);
+ }
+
+ template <typename TException>
+ static void EnsureCurrentExceptionHasBackTrace() {
+ auto exceptionPtr = std::current_exception();
+ UNIT_ASSERT_C(exceptionPtr != nullptr, "No exception");
+ try {
+ std::rethrow_exception(exceptionPtr);
+ } catch (const TException& e) {
+ const TBackTrace* bt = e.BackTrace();
+ UNIT_ASSERT(bt != nullptr);
+ } catch (...) {
+ UNIT_ASSERT_C(false, "Unexpected exception type");
+ }
+ };
+
+ inline void TestEnsureWithBackTrace1() {
+ try {
+ Y_ENSURE_BT(4 > 6);
+ } catch (...) {
+ const TString msg = CurrentExceptionMessage();
+ UNIT_ASSERT(msg.Contains("4 > 6"));
+ UNIT_ASSERT(msg.Contains("\n"));
+ EnsureCurrentExceptionHasBackTrace<yexception>();
+ return;
+ }
+ UNIT_ASSERT(false);
+ }
+
+ inline void TestEnsureWithBackTrace2() {
+ try {
+ Y_ENSURE_BT(4 > 6, "custom "
+ << "message");
+ } catch (...) {
+ const TString msg = CurrentExceptionMessage();
+ UNIT_ASSERT(!msg.Contains("4 > 6"));
+ UNIT_ASSERT(msg.Contains("custom message"));
+ UNIT_ASSERT(msg.Contains("\n"));
+ EnsureCurrentExceptionHasBackTrace<yexception>();
+ return;
+ }
+ UNIT_ASSERT(false);
+ }
+
+ inline void TestVirtualInheritance() {
+ TStringStream ss;
+
+ OUTS = &ss;
+
+ class TA {
+ public:
+ inline TA() {
+ *OUTS << "A";
+ }
+ };
+
+ class TB {
+ public:
+ inline TB() {
+ *OUTS << "B";
+ }
+ };
+
+ class TC: public virtual TB, public virtual TA {
+ public:
+ inline TC() {
+ *OUTS << "C";
+ }
+ };
+
+ class TD: public virtual TA {
+ public:
+ inline TD() {
+ *OUTS << "D";
+ }
+ };
+
+ class TE: public TC, public TD {
+ public:
+ inline TE() {
+ *OUTS << "E";
+ }
+ };
+
+ TE e;
+
+ UNIT_ASSERT_EQUAL(ss.Str(), "BACDE");
+ }
+
+ inline void TestVirtuality() {
+ try {
+ ythrow TFileError() << "1";
+ UNIT_ASSERT(false);
+ } catch (const TIoException&) {
+ } catch (...) {
+ UNIT_ASSERT(false);
+ }
+
+ try {
+ ythrow TFileError() << 1;
+ UNIT_ASSERT(false);
+ } catch (const TSystemError&) {
+ } catch (...) {
+ UNIT_ASSERT(false);
+ }
+
+ try {
+ ythrow TFileError() << '1';
+ UNIT_ASSERT(false);
+ } catch (const yexception&) {
+ } catch (...) {
+ UNIT_ASSERT(false);
+ }
+
+ try {
+ ythrow TFileError() << 1.0;
+ UNIT_ASSERT(false);
+ } catch (const TFileError&) {
+ } catch (...) {
+ UNIT_ASSERT(false);
+ }
+ }
+
+ inline void TestFormat1() {
+ try {
+ throw yexception() << 1 << " qw " << 12.1;
+ UNIT_ASSERT(false);
+ } catch (...) {
+ const TString err = CurrentExceptionMessage();
+
+ UNIT_ASSERT(err.Contains("1 qw 12.1"));
+ }
+ }
+
+ static inline void CheckCurrentExceptionContains(const char* message) {
+ TString err = CurrentExceptionMessage();
+ SubstGlobal(err, '\\', '/'); // remove backslashes from path in message
+ UNIT_ASSERT(err.Contains(message));
+ }
+
+ inline void TestRaise1() {
+ try {
+ Throw2DontMove();
+ UNIT_ASSERT(false);
+ } catch (...) {
+ CheckCurrentExceptionContains("util/generic/yexception_ut.cpp:8: 1 qw 12.1");
+ }
+ }
+
+ inline void TestException() {
+ ythrow yexception() << "blablabla";
+ }
+
+ inline void TestLineInfo() {
+ try {
+ Throw1DontMove();
+ UNIT_ASSERT(false);
+ } catch (...) {
+ CheckCurrentExceptionContains("util/generic/yexception_ut.cpp:4: blabla");
+
+ throw;
+ }
+ }
+
+ //! tests propagation of an exception through C code
+ //! @note on some platforms, for example GCC on 32-bit Linux without -fexceptions option,
+ //! throwing an exception from a C++ callback through C code aborts program
+ inline void TestMixedCode() {
+ const int N = 26082009;
+ try {
+ TestCallback(&CallbackFun, N);
+ UNIT_ASSERT(false);
+ } catch (int i) {
+ UNIT_ASSERT_VALUES_EQUAL(i, N);
+ }
+ }
+
+ void TestMacroOverload() {
+ try {
+ Y_ENSURE(10 > 20);
+ } catch (const yexception& e) {
+ UNIT_ASSERT(e.AsStrBuf().Contains("10 > 20"));
+ }
+
+ try {
+ Y_ENSURE(10 > 20, "exception message to search for");
+ } catch (const yexception& e) {
+ UNIT_ASSERT(e.AsStrBuf().Contains("exception message to search for"));
+ }
+
+ try {
+ NOuter::NInner::Compare10And20();
+ } catch (const yexception& e) {
+ UNIT_ASSERT(e.AsStrBuf().Contains("10 > 20"));
+ }
+ }
+
+ void TestMessageCrop() {
+ TTempBuf tmp;
+ size_t size = tmp.Size() * 1.5;
+ TString s;
+ s.reserve(size);
+ TMersenne<ui64> generator(42);
+ for (int j = 0; j < 50; ++j) {
+ for (size_t i = 0; i < size; ++i) {
+ s += static_cast<char>('a' + generator() % 26);
+ }
+ yexception e;
+ e << s;
+ UNIT_ASSERT_EQUAL(e.AsStrBuf(), s.substr(0, tmp.Size() - 1));
+ }
+ }
+
+ void TestTIoSystemErrorSpecialMethods() {
+ TString testStr{"systemError"};
+ TIoSystemError err;
+ err << testStr;
+ UNIT_ASSERT(err.AsStrBuf().Contains(testStr));
+
+ TIoSystemError errCopy{err};
+ UNIT_ASSERT(err.AsStrBuf().Contains(testStr));
+ UNIT_ASSERT(errCopy.AsStrBuf().Contains(testStr));
+
+ TIoSystemError errAssign;
+ errAssign = err;
+ UNIT_ASSERT(err.AsStrBuf().Contains(testStr));
+ UNIT_ASSERT(errAssign.AsStrBuf().Contains(testStr));
+
+ TIoSystemError errMove{std::move(errCopy)};
+ UNIT_ASSERT(errMove.AsStrBuf().Contains(testStr));
+
+ TIoSystemError errMoveAssign;
+ errMoveAssign = std::move(errMove);
+ UNIT_ASSERT(errMoveAssign.AsStrBuf().Contains(testStr));
+ }
+ inline void TestCurrentExceptionTypeNameMethod() {
+ //Basic test of getting the correct exception type name.
+ try {
+ throw std::runtime_error("Test Runtime Error Exception");
+ } catch (...) {
+ UNIT_ASSERT_STRING_CONTAINS(CurrentExceptionTypeName(), "std::runtime_error");
+ }
+ //Test when exception has an unusual type. Under Linux it should return "int" and under other OSs "unknown type".
+ try {
+ throw int(1);
+ } catch (...) {
+#if defined(LIBCXX_BUILDING_LIBCXXRT) || defined(LIBCXX_BUILDING_LIBGCC)
+ UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "int");
+#else
+ UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "unknown type");
+#endif
+ }
+ //Test when the caught exception is rethrown with std::rethrow_exception.
+ try {
+ throw std::logic_error("Test Logic Error Exception");
+ } catch (...) {
+ try {
+ std::rethrow_exception(std::current_exception());
+ } catch (...) {
+ UNIT_ASSERT_STRING_CONTAINS(CurrentExceptionTypeName(), "std::logic_error");
+ }
+ }
+ //Test when the caught exception is rethrown with throw; .
+ //This test is different from the previous one because of the interaction with cxxabi specifics.
+ try {
+ throw std::bad_alloc();
+ } catch (...) {
+ try {
+ throw;
+ } catch (...) {
+ UNIT_ASSERT_STRING_CONTAINS(CurrentExceptionTypeName(), "std::bad_alloc");
+ }
+ }
+ // For exceptions thrown by std::rethrow_exception() a nullptr will be returned by libcxxrt's __cxa_current_exception_type().
+ // Adding an explicit test for the case.
+ try {
+ throw int(1);
+ } catch (...) {
+ try {
+ std::rethrow_exception(std::current_exception());
+ } catch (...) {
+#if defined(LIBCXX_BUILDING_LIBGCC)
+ UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "int");
+#else
+ UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "unknown type");
+#endif
+ }
+ }
+ //Test when int is rethrown with throw; .
+ try {
+ throw int(1);
+ } catch (...) {
+ try {
+ throw;
+ } catch (...) {
+#if defined(LIBCXX_BUILDING_LIBCXXRT) || defined(LIBCXX_BUILDING_LIBGCC)
+ UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "int");
+#else
+ UNIT_ASSERT_VALUES_EQUAL(CurrentExceptionTypeName(), "unknown type");
+#endif
+ }
+ }
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TExceptionTest);
diff --git a/util/generic/yexception_ut.h b/util/generic/yexception_ut.h
new file mode 100644
index 0000000000..acf6f27e99
--- /dev/null
+++ b/util/generic/yexception_ut.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef void (*TCallbackFun)(int);
+
+ //! just calls callback with parameter @c i
+ void TestCallback(TCallbackFun f, int i);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/util/generic/ylimits.cpp b/util/generic/ylimits.cpp
new file mode 100644
index 0000000000..bcacf965e3
--- /dev/null
+++ b/util/generic/ylimits.cpp
@@ -0,0 +1 @@
+#include "ylimits.h"
diff --git a/util/generic/ylimits.h b/util/generic/ylimits.h
new file mode 100644
index 0000000000..fe42b4dfc0
--- /dev/null
+++ b/util/generic/ylimits.h
@@ -0,0 +1,92 @@
+#pragma once
+
+#include <limits>
+
+#if defined(max) || defined(min)
+ #error "stop defining 'min' and 'max' macros, evil people"
+#endif
+
+template <class T>
+static constexpr T Max() noexcept {
+ return std::numeric_limits<T>::max();
+}
+
+template <class T>
+static constexpr T Min() noexcept {
+ return std::numeric_limits<T>::min();
+}
+
+namespace NPrivate {
+ struct TMax {
+ template <class T>
+ constexpr operator T() const {
+ return Max<T>();
+ }
+ };
+
+ struct TMin {
+ template <class T>
+ constexpr operator T() const {
+ return Min<T>();
+ }
+ };
+}
+
+static constexpr ::NPrivate::TMax Max() noexcept {
+ return {};
+}
+
+static constexpr ::NPrivate::TMin Min() noexcept {
+ return {};
+}
+
+namespace NPrivate {
+ template <unsigned long long N>
+ static constexpr double MaxFloorValue() {
+ return N;
+ }
+ template <unsigned long long N>
+ static constexpr double MaxCeilValue() {
+ return N;
+ }
+ template <>
+ constexpr double MaxFloorValue<0x7FFF'FFFF'FFFF'FFFFull>() {
+ return 9223372036854774784.0; // 0x7FFFFFFFFFFFFC00p0
+ }
+ template <>
+ constexpr double MaxCeilValue<0x7FFF'FFFF'FFFF'FFFFull>() {
+ return 9223372036854775808.0; // 0x8000000000000000p0
+ }
+ template <>
+ constexpr double MaxFloorValue<0xFFFF'FFFF'FFFF'FFFFull>() {
+ return 18446744073709549568.0; // 0xFFFFFFFFFFFFF800p0
+ }
+ template <>
+ constexpr double MaxCeilValue<0xFFFF'FFFF'FFFF'FFFFull>() {
+ return 18446744073709551616.0; // 0x10000000000000000p0
+ }
+}
+
+// MaxFloor<T> is the greatest double within the range of T.
+//
+// 1. If Max<T> is an exact double, MaxFloor<T> = Max<T> = MaxCeil<T>.
+// In this case some doubles above MaxFloor<T> cast to T may round
+// to Max<T> depending on the rounding mode.
+//
+// 2. Otherwise Max<T> is between MaxFloor<T> and MaxCeil<T>, and
+// MaxFloor<T> is the largest double that does not overflow T.
+template <class T>
+static constexpr double MaxFloor() noexcept {
+ return ::NPrivate::MaxFloorValue<Max<T>()>();
+}
+
+// MaxCeil<T> is the smallest double not lesser than Max<T>.
+//
+// 1. If Max<T> is an exact double, MaxCeil<T> = Max<T> = MaxFloor<T>.
+//
+// 2. Otherwise Max<T> is between MaxFloor<T> and MaxCeil<T>, and
+// MaxCeil<T> is the smallest double that overflows T.
+template <class T>
+static constexpr double MaxCeil() noexcept {
+ return ::NPrivate::MaxCeilValue<Max<T>()>();
+}
diff --git a/util/generic/ylimits_ut.cpp b/util/generic/ylimits_ut.cpp
new file mode 100644
index 0000000000..f1b3c6858c
--- /dev/null
+++ b/util/generic/ylimits_ut.cpp
@@ -0,0 +1,157 @@
+#include "cast.h"
+#include "ylimits.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/string/cast.h>
+#include <util/system/valgrind.h>
+
+class TLimitTest: public TTestBase {
+ UNIT_TEST_SUITE(TLimitTest);
+ UNIT_TEST(TestLimits);
+ UNIT_TEST(TestNan);
+ UNIT_TEST(TestMaxDouble);
+ UNIT_TEST_SUITE_END();
+
+protected:
+ void TestLimits();
+ void TestNan();
+ void TestMaxDouble();
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TLimitTest);
+
+#define CHECK_COND(X) UNIT_ASSERT(X)
+
+static inline bool ValidSignInfo(bool, bool) {
+ return true;
+}
+
+template <class T>
+static inline bool ValidSignInfo(bool limitIsSigned, const T&) {
+ return limitIsSigned && IsNegative(T(-1)) || !limitIsSigned && !IsNegative(T(-1));
+}
+
+template <class T>
+static inline bool TestIntegralLimits(const T&, bool unknownSign = true, bool isSigned = true) {
+ using lim = std::numeric_limits<T>;
+
+ CHECK_COND(lim::is_specialized);
+ CHECK_COND(lim::is_integer);
+ CHECK_COND(lim::min() < lim::max());
+ CHECK_COND((unknownSign && ((lim::is_signed && (lim::min() != 0)) || (!lim::is_signed && (lim::min() == 0)))) ||
+ (!unknownSign && ((lim::is_signed && isSigned) || (!lim::is_signed && !isSigned))));
+
+ T min = Min();
+ UNIT_ASSERT_EQUAL(lim::min(), min);
+ T max = Max();
+ UNIT_ASSERT_EQUAL(lim::max(), max);
+
+ if (unknownSign) {
+ CHECK_COND(ValidSignInfo(lim::is_signed, T()));
+ }
+
+ return true;
+}
+
+template <class T>
+static inline bool TestSignedIntegralLimits(const T& val) {
+ return TestIntegralLimits(val, false, true);
+}
+
+template <class T>
+static inline bool TestUnsignedIntegralLimits(const T& val) {
+ return TestIntegralLimits(val, false, false);
+}
+
+template <class T>
+static inline bool TestFloatLimits(const T&) {
+ using lim = std::numeric_limits<T>;
+
+ CHECK_COND(lim::is_specialized);
+ CHECK_COND(!lim::is_modulo);
+ CHECK_COND(!lim::is_integer);
+ CHECK_COND(lim::is_signed);
+
+ CHECK_COND(lim::max() > 1000);
+ CHECK_COND(lim::min() > 0);
+ CHECK_COND(lim::min() < 0.001);
+ CHECK_COND(lim::epsilon() > 0);
+
+ if (lim::is_iec559) {
+ CHECK_COND(lim::has_infinity);
+ CHECK_COND(lim::has_quiet_NaN);
+ CHECK_COND(lim::has_signaling_NaN);
+ }
+
+ if (lim::has_infinity) {
+ const T infinity = lim::infinity();
+
+ CHECK_COND(infinity > lim::max());
+ CHECK_COND(-infinity < -lim::max());
+ }
+
+ return true;
+}
+
+template <class T>
+static inline bool TestNan(const T&) {
+ using lim = std::numeric_limits<T>;
+
+ if (lim::has_quiet_NaN) {
+ const T qnan = lim::quiet_NaN();
+
+ CHECK_COND(!(qnan == 42));
+ CHECK_COND(!(qnan == qnan));
+ CHECK_COND(qnan != 42);
+ CHECK_COND(qnan != qnan);
+ }
+
+ return true;
+}
+
+void TLimitTest::TestLimits() {
+ UNIT_ASSERT(TestIntegralLimits(bool()));
+ UNIT_ASSERT(TestIntegralLimits(char()));
+ using signed_char = signed char;
+ UNIT_ASSERT(TestSignedIntegralLimits(signed_char()));
+ using unsigned_char = unsigned char;
+ UNIT_ASSERT(TestUnsignedIntegralLimits(unsigned_char()));
+ UNIT_ASSERT(TestSignedIntegralLimits(short()));
+ using unsigned_short = unsigned short;
+ UNIT_ASSERT(TestUnsignedIntegralLimits(unsigned_short()));
+ UNIT_ASSERT(TestSignedIntegralLimits(int()));
+ using unsigned_int = unsigned int;
+ UNIT_ASSERT(TestUnsignedIntegralLimits(unsigned_int()));
+ UNIT_ASSERT(TestSignedIntegralLimits(long()));
+ using unsigned_long = unsigned long;
+ UNIT_ASSERT(TestUnsignedIntegralLimits(unsigned_long()));
+ using long_long = long long;
+ UNIT_ASSERT(TestSignedIntegralLimits(long_long()));
+ using unsigned_long_long = unsigned long long;
+ UNIT_ASSERT(TestUnsignedIntegralLimits(unsigned_long_long()));
+ UNIT_ASSERT(TestFloatLimits(float()));
+ UNIT_ASSERT(TestFloatLimits(double()));
+ using long_double = long double;
+ UNIT_ASSERT(RUNNING_ON_VALGRIND || TestFloatLimits(long_double()));
+}
+
+void TLimitTest::TestNan() {
+ UNIT_ASSERT(::TestNan(float()));
+ UNIT_ASSERT(::TestNan(double()));
+ using long_double = long double;
+ UNIT_ASSERT(::TestNan(long_double()));
+}
+
+void TLimitTest::TestMaxDouble() {
+ UNIT_ASSERT_VALUES_EQUAL(MaxCeil<i8>(), 127.0);
+ UNIT_ASSERT_VALUES_EQUAL(MaxFloor<i8>(), 127.0);
+ UNIT_ASSERT_VALUES_EQUAL(MaxCeil<ui8>(), 255.0);
+ UNIT_ASSERT_VALUES_EQUAL(MaxFloor<ui8>(), 255.0);
+ double d = 1ull << 63;
+ UNIT_ASSERT_VALUES_EQUAL(MaxCeil<i64>(), d);
+ UNIT_ASSERT_VALUES_EQUAL(MaxFloor<i64>(), nextafter(d, 0));
+ d *= 2;
+ UNIT_ASSERT_VALUES_EQUAL(MaxCeil<ui64>(), d);
+ UNIT_ASSERT_VALUES_EQUAL(MaxFloor<ui64>(), nextafter(d, 0));
+}
diff --git a/util/generic/ymath.cpp b/util/generic/ymath.cpp
new file mode 100644
index 0000000000..31270728f4
--- /dev/null
+++ b/util/generic/ymath.cpp
@@ -0,0 +1,56 @@
+#include "ymath.h"
+
+double Exp2(double x) {
+ return pow(2.0, x);
+}
+
+float Exp2f(float x) {
+ return powf(2.0f, x);
+}
+
+#ifdef _MSC_VER
+
+double Erf(double x) {
+ static constexpr double _M_2_SQRTPI = 1.12837916709551257390;
+ static constexpr double eps = 1.0e-7;
+ if (fabs(x) >= 3.75)
+ return x > 0 ? 1.0 : -1.0;
+ double r = _M_2_SQRTPI * x;
+ double f = r;
+ for (int i = 1;; ++i) {
+ r *= -x * x / i;
+ f += r / (2 * i + 1);
+ if (fabs(r) < eps * (2 * i + 1))
+ break;
+ }
+ return f;
+}
+
+#endif // _MSC_VER
+
+double LogGammaImpl(double x) {
+ static constexpr double lnSqrt2Pi = 0.91893853320467274178; // log(sqrt(2.0 * PI))
+ static constexpr double coeff9 = 1.0 / 1188.0;
+ static constexpr double coeff7 = -1.0 / 1680.0;
+ static constexpr double coeff5 = 1.0 / 1260.0;
+ static constexpr double coeff3 = -1.0 / 360.0;
+ static constexpr double coeff1 = 1.0 / 12.0;
+
+ if ((x == 1.0) || (x == 2.0)) {
+ return 0.0; // 0! = 1
+ }
+ double bonus = 0.0;
+ while (x < 3.0) {
+ bonus -= log(x);
+ x += 1.0;
+ }
+ double lnX = log(x);
+ double sqrXInv = 1.0 / (x * x);
+ double res = coeff9 * sqrXInv + coeff7;
+ res = res * sqrXInv + coeff5;
+ res = res * sqrXInv + coeff3;
+ res = res * sqrXInv + coeff1;
+ res /= x;
+ res += x * lnX - x + lnSqrt2Pi - 0.5 * lnX;
+ return res + bonus;
+}
diff --git a/util/generic/ymath.h b/util/generic/ymath.h
new file mode 100644
index 0000000000..9ff9ae2abe
--- /dev/null
+++ b/util/generic/ymath.h
@@ -0,0 +1,206 @@
+#pragma once
+
+#include <util/system/yassert.h>
+#include <util/system/defaults.h>
+
+#include <cmath>
+#include <cfloat>
+#include <cstdlib>
+
+#include "typetraits.h"
+#include "utility.h"
+
+constexpr double PI = M_PI;
+constexpr double M_LOG2_10 = 3.32192809488736234787; // log2(10)
+constexpr double M_LN2_INV = M_LOG2E; // 1 / ln(2) == log2(e)
+
+/**
+ * \returns Absolute value of the provided argument.
+ */
+template <class T>
+constexpr T Abs(T value) {
+ return std::abs(value);
+}
+
+/**
+ * @returns Base 2 logarithm of the provided double
+ * precision floating point value.
+ */
+inline double Log2(double value) {
+ return log(value) * M_LN2_INV;
+}
+
+/**
+ * @returns Base 2 logarithm of the provided
+ * floating point value.
+ */
+inline float Log2(float value) {
+ return logf(value) * static_cast<float>(M_LN2_INV);
+}
+
+/**
+ * @returns Base 2 logarithm of the provided integral value.
+ */
+template <class T>
+inline std::enable_if_t<std::is_integral<T>::value, double>
+Log2(T value) {
+ return Log2(static_cast<double>(value));
+}
+
+/** Returns 2^x */
+double Exp2(double);
+float Exp2f(float);
+
+template <class T>
+static constexpr T Sqr(const T t) noexcept {
+ return t * t;
+}
+
+inline double Sigmoid(double x) {
+ return 1.0 / (1.0 + std::exp(-x));
+}
+
+inline float Sigmoid(float x) {
+ return 1.0f / (1.0f + std::exp(-x));
+}
+
+static inline bool IsFinite(double f) {
+#if defined(isfinite)
+ return isfinite(f);
+#elif defined(_win_)
+ return _finite(f) != 0;
+#elif defined(_darwin_)
+ return isfinite(f);
+#elif defined(__GNUC__)
+ return __builtin_isfinite(f);
+#elif defined(_STLP_VENDOR_STD)
+ return _STLP_VENDOR_STD::isfinite(f);
+#else
+ return std::isfinite(f);
+#endif
+}
+
+static inline bool IsNan(double f) {
+#if defined(_win_)
+ return _isnan(f) != 0;
+#else
+ return std::isnan(f);
+#endif
+}
+
+inline bool IsValidFloat(double f) {
+ return IsFinite(f) && !IsNan(f);
+}
+
+#ifdef _MSC_VER
+double Erf(double x);
+#else
+inline double Erf(double x) {
+ return erf(x);
+}
+#endif
+
+/**
+ * @returns Natural logarithm of the absolute value
+ * of the gamma function of provided argument.
+ */
+inline double LogGamma(double x) noexcept {
+#if defined(_glibc_)
+ int sign;
+
+ (void)sign;
+
+ return lgamma_r(x, &sign);
+#elif defined(__GNUC__)
+ return __builtin_lgamma(x);
+#elif defined(_unix_)
+ return lgamma(x);
+#else
+ extern double LogGammaImpl(double);
+ return LogGammaImpl(x);
+#endif
+}
+
+/**
+ * @returns x^n for integer n, n >= 0.
+ */
+template <class T, class Int>
+T Power(T x, Int n) {
+ static_assert(std::is_integral<Int>::value, "only integer powers are supported");
+ Y_ASSERT(n >= 0);
+ if (n == 0) {
+ return T(1);
+ }
+ while ((n & 1) == 0) {
+ x = x * x;
+ n >>= 1;
+ }
+ T result = x;
+ n >>= 1;
+ while (n > 0) {
+ x = x * x;
+ if (n & 1) {
+ result = result * x;
+ }
+ n >>= 1;
+ }
+ return result;
+};
+
+/**
+ * Compares two floating point values and returns true if they are considered equal.
+ * The two numbers are compared in a relative way, where the exactness is stronger
+ * the smaller the numbers are.
+ *
+ * Note that comparing values where either one is 0.0 will not work.
+ * The solution to this is to compare against values greater than or equal to 1.0.
+ *
+ * @code
+ * // Instead of comparing with 0.0
+ * FuzzyEquals(0.0, 1.0e-200); // This will return false
+ * // Compare adding 1 to both values will fix the problem
+ * FuzzyEquals(1 + 0.0, 1 + 1.0e-200); // This will return true
+ * @endcode
+ */
+inline bool FuzzyEquals(double p1, double p2, double eps = 1.0e-13) {
+ return (Abs(p1 - p2) <= eps * Min(Abs(p1), Abs(p2)));
+}
+
+/**
+ * @see FuzzyEquals(double, double, double)
+ */
+inline bool FuzzyEquals(float p1, float p2, float eps = 1.0e-6) {
+ return (Abs(p1 - p2) <= eps * Min(Abs(p1), Abs(p2)));
+}
+
+namespace NUtilMathPrivate {
+ template <bool IsSigned>
+ struct TCeilDivImpl {};
+
+ template <>
+ struct TCeilDivImpl<true> {
+ template <class T>
+ static inline T Do(T x, T y) noexcept {
+ return x / y + (((x < 0) ^ (y > 0)) && (x % y));
+ }
+ };
+
+ template <>
+ struct TCeilDivImpl<false> {
+ template <class T>
+ static inline T Do(T x, T y) noexcept {
+ auto quot = x / y;
+ return (x % y) ? (quot + 1) : quot;
+ }
+ };
+}
+
+/**
+ * @returns Equivalent to ceil((double) x / (double) y) but using only integer arithmetic operations
+ */
+template <class T>
+inline T CeilDiv(T x, T y) noexcept {
+ static_assert(std::is_integral<T>::value, "Integral type required.");
+ Y_ASSERT(y != 0);
+ return ::NUtilMathPrivate::TCeilDivImpl<std::is_signed<T>::value>::Do(x, y);
+}
diff --git a/util/generic/ymath_ut.cpp b/util/generic/ymath_ut.cpp
new file mode 100644
index 0000000000..29190b55eb
--- /dev/null
+++ b/util/generic/ymath_ut.cpp
@@ -0,0 +1,220 @@
+#include "bitops.h"
+#include "ymath.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/stream/output.h>
+#include <util/datetime/cputimer.h>
+
+#include <limits>
+
+template <class T>
+static inline T SlowClp2(T t) noexcept {
+ Y_ASSERT(t > 0);
+
+ T ret = 1;
+
+ while (ret < t) {
+ ret *= 2;
+ }
+
+ return ret;
+}
+
+class TMathTest: public TTestBase {
+ UNIT_TEST_SUITE(TMathTest);
+ UNIT_TEST(TestClp2)
+ UNIT_TEST(TestClpSimple)
+ UNIT_TEST(TestSqr)
+ UNIT_TEST(TestLog2)
+ UNIT_TEST(ValueBitCount)
+ UNIT_TEST(TestErf);
+ UNIT_TEST(TestLogGamma);
+ UNIT_TEST(TestIsValidFloat);
+ UNIT_TEST(TestAbs);
+ UNIT_TEST(TestPower);
+ UNIT_TEST(TestSigmoid);
+ UNIT_TEST(TestCeilDiv);
+ UNIT_TEST_SUITE_END();
+
+private:
+ void TestClp2();
+ void TestSqr();
+ void TestErf();
+ void TestLogGamma();
+ void TestAbs();
+ void TestPower();
+ void TestSigmoid();
+ void TestCeilDiv();
+
+ inline void TestIsValidFloat() {
+ UNIT_ASSERT(IsValidFloat(-Max<double>() / 2.));
+ }
+
+ inline void TestClpSimple() {
+ UNIT_ASSERT_EQUAL(FastClp2<ui32>(12), 16);
+ UNIT_ASSERT_EQUAL(FastClp2<ui16>(11), 16);
+ UNIT_ASSERT_EQUAL(FastClp2<ui8>(10), 16);
+
+ UNIT_ASSERT_EQUAL(FastClp2<ui32>(15), 16);
+ UNIT_ASSERT_EQUAL(FastClp2<ui32>(16), 16);
+ UNIT_ASSERT_EQUAL(FastClp2<ui32>(17), 32);
+ }
+
+ inline void TestLog2() {
+ UNIT_ASSERT_DOUBLES_EQUAL(Log2(2.0), 1.0, 1e-10);
+ UNIT_ASSERT_DOUBLES_EQUAL(Log2(2ull), 1.0, 1e-10);
+ UNIT_ASSERT_DOUBLES_EQUAL(Log2(2.0f), 1.0f, 1e-7f);
+ }
+
+ inline void ValueBitCount() {
+ UNIT_ASSERT_VALUES_EQUAL(GetValueBitCount(1), 1u);
+ UNIT_ASSERT_VALUES_EQUAL(GetValueBitCount(2), 2u);
+ UNIT_ASSERT_VALUES_EQUAL(GetValueBitCount(3), 2u);
+ UNIT_ASSERT_VALUES_EQUAL(GetValueBitCount(257), 9u);
+ }
+};
+
+UNIT_TEST_SUITE_REGISTRATION(TMathTest);
+
+void TMathTest::TestSqr() {
+ UNIT_ASSERT_EQUAL(Sqr(2), 4);
+ UNIT_ASSERT_EQUAL(Sqr(2.0), 4.0);
+}
+
+void TMathTest::TestClp2() {
+ for (ui8 i = 1; i < 127; ++i) {
+ UNIT_ASSERT_EQUAL(SlowClp2(i), FastClp2(i));
+ }
+
+ for (ui16 i = 1; i < 255; ++i) {
+ UNIT_ASSERT_EQUAL(SlowClp2(i), FastClp2(i));
+ }
+
+ for (ui32 i = 1; i < 255; ++i) {
+ UNIT_ASSERT_EQUAL(SlowClp2(i), FastClp2(i));
+ }
+
+ for (ui64 i = 1; i < 255; ++i) {
+ UNIT_ASSERT_EQUAL(SlowClp2(i), FastClp2(i));
+ }
+
+ if (0) {
+ {
+ TFuncTimer timer("fast");
+ size_t ret = 0;
+
+ for (size_t i = 0; i < 10000000; ++i) {
+ ret += FastClp2(i);
+ }
+
+ Cerr << ret << Endl;
+ }
+
+ {
+ TFuncTimer timer("slow");
+ size_t ret = 0;
+
+ for (size_t i = 0; i < 10000000; ++i) {
+ ret += SlowClp2(i);
+ }
+
+ Cerr << ret << Endl;
+ }
+ }
+}
+
+void TMathTest::TestErf() {
+ static const double a = -5.0;
+ static const double b = 5.0;
+ static const int n = 50;
+ static const double step = (b - a) / n;
+
+ static const double values[n + 1] = {
+ -1.0000000, -1.0000000, -1.0000000, -1.0000000, -1.0000000,
+ -1.0000000, -0.9999999, -0.9999996, -0.9999985, -0.9999940,
+ -0.9999779, -0.9999250, -0.9997640, -0.9993115, -0.9981372,
+ -0.9953223, -0.9890905, -0.9763484, -0.9522851, -0.9103140,
+ -0.8427008, -0.7421010, -0.6038561, -0.4283924, -0.2227026,
+ 0.0000000,
+ 0.2227026, 0.4283924, 0.6038561, 0.7421010, 0.8427008,
+ 0.9103140, 0.9522851, 0.9763484, 0.9890905, 0.9953223,
+ 0.9981372, 0.9993115, 0.9997640, 0.9999250, 0.9999779,
+ 0.9999940, 0.9999985, 0.9999996, 0.9999999, 1.0000000,
+ 1.0000000, 1.0000000, 1.0000000, 1.0000000, 1.0000000};
+
+ double x = a;
+ for (int i = 0; i <= n; ++i, x += step) {
+ double f = Erf(x);
+ UNIT_ASSERT_DOUBLES_EQUAL(f, values[i], 1e-7);
+ }
+}
+
+void TMathTest::TestLogGamma() {
+ double curVal = 0.0;
+ for (int i = 1; i <= 20; i++) {
+ curVal += log((double)i);
+ UNIT_ASSERT_DOUBLES_EQUAL(curVal, LogGamma((double)(i + 1)), 1e-6);
+ }
+ curVal = log(M_PI) / 2.0;
+ for (int i = 1; i <= 20; i++) {
+ UNIT_ASSERT_DOUBLES_EQUAL(curVal, LogGamma(i - 0.5), 1e-6);
+ curVal += log(i - 0.5);
+ }
+}
+
+void TMathTest::TestAbs() {
+ UNIT_ASSERT_VALUES_EQUAL(Abs(1), 1);
+ UNIT_ASSERT_VALUES_EQUAL(Abs(-1), 1);
+ UNIT_ASSERT_VALUES_EQUAL(Abs(-1000000000000ll), 1000000000000ll);
+ UNIT_ASSERT_VALUES_EQUAL(Abs(0), 0);
+ UNIT_ASSERT_VALUES_EQUAL(Abs(1.0), 1.0);
+ UNIT_ASSERT_VALUES_EQUAL(Abs(-1.0), 1.0);
+ UNIT_ASSERT_VALUES_EQUAL(Abs(0.0), 0.0);
+}
+
+void TMathTest::TestPower() {
+ UNIT_ASSERT_VALUES_EQUAL(Power(0, 0), 1);
+ UNIT_ASSERT_VALUES_EQUAL(Power(-1, 1), -1);
+ UNIT_ASSERT_VALUES_EQUAL(Power(-1, 2), 1);
+ UNIT_ASSERT_VALUES_EQUAL(Power(2LL, 32), 1LL << 32);
+ UNIT_ASSERT_DOUBLES_EQUAL(Power(0.0, 0), 1.0, 1e-9);
+ UNIT_ASSERT_DOUBLES_EQUAL(Power(0.1, 3), 1e-3, 1e-9);
+}
+
+void TMathTest::TestSigmoid() {
+ UNIT_ASSERT_EQUAL(Sigmoid(0.f), 0.5f);
+ UNIT_ASSERT_EQUAL(Sigmoid(-5000.f), 0.0f);
+ UNIT_ASSERT_EQUAL(Sigmoid(5000.f), 1.0f);
+
+ UNIT_ASSERT_EQUAL(Sigmoid(0.), 0.5);
+ UNIT_ASSERT_EQUAL(Sigmoid(-5000.), 0.0);
+ UNIT_ASSERT_EQUAL(Sigmoid(5000.), 1.0);
+}
+
+void TMathTest::TestCeilDiv() {
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui8>(2, 3), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui8>(3, 3), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui32>(12, 2), 6);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui64>(10, 3), 4);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui64>(0, 10), 0);
+
+ // negative numbers
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(0, -10), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-1, 2), 0);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-1, -2), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(10, -5), -2);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-3, -4), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-6, -4), 2);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-6, 4), -1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-13, 4), -3);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv(-14, -4), 4);
+
+ // check values close to overflow
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui8>(255, 10), 26);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<ui32>(std::numeric_limits<ui32>::max() - 3, std::numeric_limits<ui32>::max()), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<i32>(std::numeric_limits<i32>::max() - 3, std::numeric_limits<i32>::max()), 1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<i32>(std::numeric_limits<i32>::min(), std::numeric_limits<i32>::max()), -1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<i8>(std::numeric_limits<i8>::max(), std::numeric_limits<i8>::min() + 1), -1);
+ UNIT_ASSERT_VALUES_EQUAL(CeilDiv<i64>(std::numeric_limits<i64>::max() - 2, -(std::numeric_limits<i64>::min() + 1)), 1);
+}