diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /util/generic | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/generic')
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); +} |