From dcec96f0b49c1debe8f361ce201a2540a488e61f Mon Sep 17 00:00:00 2001 From: Andreas Reischuck Date: Fri, 30 Aug 2024 01:53:19 +0200 Subject: [PATCH] Fix OrderedSetOf to make it ready for usage --- src/lookup19.lib/lookup19/OrderedSetOf.h | 268 ++++++++++-------- .../lookup19/OrderedSetOf.test.cpp | 161 ++++++++++- src/lookup19.lib/lookup19/OrderedSliceOf.h | 27 +- 3 files changed, 332 insertions(+), 124 deletions(-) diff --git a/src/lookup19.lib/lookup19/OrderedSetOf.h b/src/lookup19.lib/lookup19/OrderedSetOf.h index b3abc0f2..00bb09bb 100644 --- a/src/lookup19.lib/lookup19/OrderedSetOf.h +++ b/src/lookup19.lib/lookup19/OrderedSetOf.h @@ -23,6 +23,7 @@ template struct OrderedSetOf { using Slice = OrderedSliceOf; using AmendableSlice = SliceOf; using UnorderedSlice = SliceOf; + using MoveSlice = array19::MoveSliceOf; static_assert(std::is_trivial_v, "Only works for trivial types!"); @@ -34,39 +35,68 @@ template struct OrderedSetOf { Count m_capacity{}; public: - OrderedSetOf() = default; - ~OrderedSetOf() noexcept { + constexpr OrderedSetOf() = default; + explicit constexpr OrderedSetOf(Slice ordered) noexcept + : m_pointer{Utils::allocate(ordered.count())} + , m_count{ordered.count()} + , m_capacity{ordered.count()} { + Utils::copyAssign(m_pointer, ordered); + } + constexpr OrderedSetOf(const OrderedSetOf& o) noexcept + : m_pointer{Utils::allocate(o.m_count)} + , m_count{o.m_count} + , m_capacity{o.m_count} { + Utils::copyAssign(m_pointer, Slice{o}); + } + constexpr OrderedSetOf(OrderedSetOf&& o) noexcept + : m_pointer{std::exchange(o.m_pointer, nullptr)} + , m_count{o.m_count} + , m_capacity{o.m_count} {} + constexpr auto operator=(const OrderedSetOf& o) noexcept -> OrderedSetOf& { + if (o.m_count > m_capacity) { + if (m_pointer) Utils::deallocate(SliceOf{m_pointer, m_capacity}); + m_pointer = Utils::allocate(o.m_count); + m_capacity = o.m_count; + } + Utils::copyAssign(m_pointer, Slice{o}); + m_count = o.m_count; + return *this; + } + constexpr auto operator=(OrderedSetOf&& o) noexcept -> OrderedSetOf& { + if (m_pointer) Utils::deallocate(SliceOf{m_pointer, m_capacity}); + m_pointer = std::exchange(o.m_pointer, nullptr); + m_count = o.m_count; + m_capacity = o.m_capacity; + return *this; + } + constexpr ~OrderedSetOf() noexcept { if (m_pointer) Utils::deallocate(SliceOf{m_pointer, m_capacity}); } - [[nodiscard]] constexpr auto isEmpty() const noexcept -> bool { return m_count == 0; } - [[nodiscard]] auto count() const -> Count { return m_count; } - [[nodiscard]] auto totalCapacity() const -> Count { return m_capacity; } - [[nodiscard]] auto unusedCapacity() const -> Count { return m_capacity - m_count; } + [[nodiscard]] constexpr auto isEmpty() const -> bool { return m_count == 0; } + [[nodiscard]] constexpr auto count() const -> Count { return m_count; } + [[nodiscard]] constexpr auto totalCapacity() const -> Count { return m_capacity; } + [[nodiscard]] constexpr auto unusedCapacity() const -> Count { return m_capacity - m_count; } - [[nodiscard]] auto front() const -> const T& { return *begin(); } - [[nodiscard]] auto back() const -> const T& { return *(begin() + m_count - 1); } + [[nodiscard]] constexpr auto front() const -> const T& { return *begin(); } + [[nodiscard]] constexpr auto back() const -> const T& { return *(begin() + m_count - 1); } - [[nodiscard]] auto begin() const noexcept -> ConstIterator { - return std::launder(reinterpret_cast(m_pointer)); - } - [[nodiscard]] auto end() const noexcept -> ConstIterator { return begin() + m_count; } - [[nodiscard]] auto operator[](Index index) const noexcept -> const Element& { - return *std::launder(reinterpret_cast(m_pointer + index)); - } - [[nodiscard]] operator Slice() const noexcept { return Slice{begin(), m_count}; } + [[nodiscard]] constexpr auto begin() const noexcept -> ConstIterator { return m_pointer; } + [[nodiscard]] constexpr auto end() const noexcept -> ConstIterator { return begin() + m_count; } + [[nodiscard]] constexpr auto operator[](Index index) const -> const Element& { return m_pointer[index]; } + [[nodiscard]] constexpr operator Slice() const { return Slice{m_pointer, m_count}; } - void ensureCapacity(Count count) { + constexpr void ensureCapacity(Count count) { if (totalCapacity() < count) growBy(static_cast(count - totalCapacity())); } - void ensureUnusedCapacity(Count count) { + constexpr void ensureUnusedCapacity(Count count) { if (unusedCapacity() < count) growBy(static_cast(count - unusedCapacity())); } /// inserts a single value to the set if it is not yet present /// note: /// * if you want to insert muliple values use merge - bool insert(T v) { + constexpr auto insert(T v) -> bool { auto it = const_cast(static_cast(*this).lowerBound(v)); if (it != end() && *it == v) { return false; @@ -76,12 +106,12 @@ template struct OrderedSetOf { auto nPtr = newStorage.begin(); auto fCount = static_cast(it - m_pointer); if (0 != fCount) { - memcpy(nPtr, m_pointer, fCount); + Utils::moveConstruct(nPtr, MoveSlice{m_pointer, fCount}); nPtr += fCount; } *nPtr++ = v; if (fCount != m_count) { - memcpy(nPtr, m_pointer + fCount, m_count - fCount); + Utils::moveConstruct(nPtr, MoveSlice{m_pointer + fCount, m_count - fCount}); } Utils::deallocate(SliceOf{m_pointer, m_capacity}); m_pointer = newStorage.begin(); @@ -90,7 +120,7 @@ template struct OrderedSetOf { return true; } if (it != end()) { - memmove(it + 1, it, static_cast(end() - it)); + Utils::moveAssignReverse(it + 1, MoveSlice{it, static_cast(end() - it)}); } *it = v; m_count++; @@ -100,136 +130,142 @@ template struct OrderedSetOf { /// removes a single element from the set /// preconditions: /// * cIt has to point between begin() and before end() - void remove(ConstIterator cIt) { + constexpr void remove(ConstIterator cIt) { auto it = const_cast(cIt); if (it + 1 != end()) { - memmove(it, it + 1, static_cast(end() - it - 1)); + Utils::moveAssignForward(it, MoveSlice{it + 1, static_cast(end() - it)}); } m_count--; } /// adds multiple elements to the set if they are not already present - /// note: - /// * assumes that enough capacity for inserting all elements is required - void merge(Slice elems) { + /// preconditions: + /// * assumes that elems are unique and ordered + constexpr void merge(Slice elems) { if (elems.isEmpty()) return; - auto less = Less{}; + if (isEmpty()) { + *this = OrderedSetOf{elems}; + return; + } auto nCount = elems.count(); - if (unusedCapacity() < nCount) { // merge into new storage + if (unusedCapacity() < nCount) { auto newStorage = grownStorage(nCount); - auto dPtr = newStorage.begin(); - auto nPtr = elems.begin(); - auto nEnd = elems.end(); - if (isEmpty()) { - memcpy(dPtr, nPtr, nCount); - Utils::deallocate(SliceOf{m_pointer, m_capacity}); - m_pointer = newStorage.begin(); - m_capacity = newStorage.count(); - m_count = nCount; - return; - } - auto n = *nPtr; - auto oPtr = begin(); - auto o = *oPtr; - auto oEnd = end(); - while (true) { - if (less(n, o)) { - *dPtr++ = n; - nPtr++; - if (nPtr == nEnd) { - memcpy(dPtr, oPtr, oEnd - oPtr); - break; - } - n = *nPtr; - } - else { - *dPtr++ = o; - oPtr++; - if (!less(o, n)) { - nPtr++; - if (nPtr == nEnd) { - if (oPtr != oEnd) memcpy(dPtr, oPtr, oEnd - oPtr); - break; - } - n = *nPtr; - } - if (oPtr == oEnd) { - memcpy(dPtr, nPtr, nEnd - nPtr); - break; - } - o = *oPtr; - } - } + auto ptr = mergeInto(newStorage, elems); Utils::deallocate(SliceOf{m_pointer, m_capacity}); m_pointer = newStorage.begin(); m_capacity = newStorage.count(); - m_count += nCount; - return; + m_count = static_cast(ptr - m_pointer); } - if (isEmpty()) { - memcpy(m_pointer, elems.begin(), nCount); - m_count = nCount; - return; + else { + m_count = mergeBackwards(elems); } - auto oBegin = m_pointer; - auto oIt = oBegin + m_count - 1; - auto o = *oIt; - auto nBegin = elems.begin(); + } + +private: + [[nodiscard]] constexpr auto grownStorage(size_t growBy) const -> AmendableSlice { + auto cur = m_capacity; + auto res = (cur << 1) - (cur >> 1) + (cur >> 4); // * 1.563 + if (res < 5) res = 5; + if (res < m_capacity + growBy) res = m_capacity + growBy; + auto ptr = Utils::allocate(res); + return AmendableSlice{ptr, res}; + } + constexpr void growBy(size_t by) { + auto newStorage = grownStorage(by); + Utils::moveConstruct(newStorage.begin(), MoveSlice{m_pointer, m_count}); + Utils::deallocate(SliceOf{m_pointer, m_capacity}); + m_pointer = newStorage.begin(); + m_capacity = newStorage.count(); + } + constexpr auto mergeInto(AmendableSlice storage, Slice elems) -> T* { + auto less = Less{}; + auto dPtr = storage.begin(); + auto nPtr = elems.begin(); + auto const nEnd = elems.end(); + auto n = *nPtr; + auto oPtr = begin(); + auto o = *oPtr; + auto const oEnd = end(); + while (true) { + if (less(n, o)) { + *dPtr++ = n; + nPtr++; + if (nPtr == nEnd) { + auto r = static_cast(oEnd - oPtr); + Utils::copyAssign(dPtr, UnorderedSlice{oPtr, r}); + return dPtr + r; + } + n = *nPtr; + } + else { + if (less(o, n)) { + *dPtr++ = o; + } + oPtr++; + if (oPtr == oEnd) { + auto r = static_cast(nEnd - nPtr); + Utils::copyAssign(dPtr, UnorderedSlice{nPtr, r}); + return dPtr + r; + } + o = *oPtr; + } + } + } + constexpr auto mergeBackwards(Slice elems) -> size_t { + auto less = Less{}; + auto const oBegin = m_pointer; + auto const oCount = m_count; + auto const nBegin = elems.begin(); + auto const nCount = elems.count(); + auto oIt = oBegin + oCount - 1; auto nIt = nBegin + nCount - 1; - auto n = *nIt; - auto dIt = oBegin + m_count + nCount - 1; + auto dCount = oCount + nCount; + auto dIt = oBegin + dCount - 1; + auto o = *oIt; + auto n = *nIt; while (true) { if (less(o, n)) { *dIt-- = n; - nIt--; if (nIt == nBegin) { + dIt++; + oIt++; + if (oIt != dIt) { // move by skipped elements + dCount -= (dIt - oIt); + Utils::moveAssignForward(oIt, MoveSlice{dIt, static_cast(dCount - (oIt - oBegin))}); + } // remaining old values are in place - break; + return dCount; } + nIt--; + n = *nIt; } else { - *dIt-- = o; - oIt--; - if (!less(n, o)) { - nIt--; - if (nIt == nBegin) { - // remaining old values are in place - break; - } - n = *nIt; + if (less(n, o)) { + *dIt-- = o; } if (oIt == oBegin) { - memcpy(oBegin, nBegin, nIt - nBegin); - break; + auto const remaining = static_cast(nIt + 1 - nBegin); + Utils::copyAssign(oBegin, UnorderedSlice{nBegin, remaining}); + dIt++; + if (oBegin + remaining != dIt) { // move by skipped elements + dCount -= (dIt - (oBegin + remaining)); + Utils::moveAssignForward( + oBegin + remaining, + MoveSlice{dIt, static_cast(dCount - remaining)}); + } + return dCount; } + oIt--; o = *oIt; } } - m_count += nCount; - } - -private: - [[nodiscard]] auto grownStorage(size_t growBy) const -> AmendableSlice { - auto cur = m_capacity; - auto res = (cur << 1) - (cur >> 1) + (cur >> 4); // * 1.563 - if (res < 5) res = 5; - if (res < m_capacity + growBy) res = m_capacity + growBy; - auto ptr = Utils::allocate(res); - return AmendableSlice{ptr, res}; - } - void growBy(size_t by) { - auto newStorage = grownStorage(by); - memcpy(newStorage.begin(), m_pointer, m_count); - Utils::deallocate(SliceOf{m_pointer, m_capacity}); - m_pointer = newStorage.begin(); - m_capacity = newStorage.count(); } }; /// deduce OrderedSliceOf from OrderedSetOf /// usage: -/// auto a = OrderedSetOf{1,2,3; +/// auto a = OrderedSetOf{1,2,3}; /// auto slice = OrderedSliceOf{a}; template OrderedSliceOf(const OrderedSetOf&) -> OrderedSliceOf; diff --git a/src/lookup19.lib/lookup19/OrderedSetOf.test.cpp b/src/lookup19.lib/lookup19/OrderedSetOf.test.cpp index 819acbc2..66539b36 100644 --- a/src/lookup19.lib/lookup19/OrderedSetOf.test.cpp +++ b/src/lookup19.lib/lookup19/OrderedSetOf.test.cpp @@ -1,12 +1,20 @@ #include "OrderedSetOf.h" +#include "array19/Array.h" +#include "array19/SliceOf.equals.h" +#include "array19/SliceOf.ostream.h" + #include using namespace lookup19; +using array19::Array; +using array19::SliceOf; TEST(OrderedSetOf, intExample) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; - auto v = OrderedSetOf{}; + auto v = Set{}; EXPECT_TRUE(v.isEmpty()); EXPECT_EQ(v.totalCapacity(), 0u); @@ -18,12 +26,18 @@ TEST(OrderedSetOf, intExample) { EXPECT_GE(v.totalCapacity(), 1u); EXPECT_GE(v.unusedCapacity(), 1u); ASSERT_EQ(v.count(), 0u); + ASSERT_FALSE(OrderedSlice{v}.has(12)); v.insert(12); v.insert(23); ASSERT_EQ(v.count(), 2u); EXPECT_GE(v.totalCapacity(), 2u); + ASSERT_FALSE(OrderedSlice{v}.has(11)); + ASSERT_TRUE(OrderedSlice{v}.has(12)); + ASSERT_FALSE(OrderedSlice{v}.has(22)); + ASSERT_TRUE(OrderedSlice{v}.has(23)); + ASSERT_FALSE(OrderedSlice{v}.has(24)); v.insert(42); v.insert(12); @@ -35,3 +49,148 @@ TEST(OrderedSetOf, intExample) { ASSERT_EQ(v.count(), 2u); } + +TEST(OrderedSetOf, unorderedInsert) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; + using Slice = array19::SliceOf; + auto asSlice = [](auto& x) -> Slice { return OrderedSlice{x}; }; + + auto v = Set{}; + + v.insert(2); + ASSERT_EQ(asSlice(v), (Slice{Array{2}})); + + v.insert(1); + ASSERT_EQ(asSlice(v), (Slice{Array{1, 2}})); + + v.insert(3); + ASSERT_EQ(asSlice(v), (Slice{Array{1, 2, 3}})); + + v.insert(0); + ASSERT_EQ(asSlice(v), (Slice{Array{0, 1, 2, 3}})); + + v.insert(5); + ASSERT_EQ(asSlice(v), (Slice{Array{0, 1, 2, 3, 5}})); + + v.insert(4); + ASSERT_EQ(asSlice(v), (Slice{Array{0, 1, 2, 3, 4, 5}})); + + v.remove(OrderedSliceOf{v}.lowerBound(2)); + ASSERT_EQ(asSlice(v), (Slice{Array{0, 1, 3, 4, 5}})); + + v.remove(OrderedSliceOf{v}.lowerBound(3)); + ASSERT_EQ(asSlice(v), (Slice{Array{0, 1, 4, 5}})); + + v.remove(OrderedSliceOf{v}.lowerBound(0)); + ASSERT_EQ(asSlice(v), (Slice{Array{1, 4, 5}})); +} + +TEST(OrderedSetOf, merge) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; + using Slice = array19::SliceOf; + auto asSlice = [](auto& x) -> Slice { return OrderedSlice{x}; }; + + auto v1 = Set{OrderedSlice{Array{10, 20, 30}}}; + ASSERT_EQ(asSlice(v1), (Slice{Array{10, 20, 30}})); + + auto v2 = Set{OrderedSlice{Array{5, 10, 11, 12, 30, 35}}}; + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 30, 35}})); + + auto v3 = v1; + + v1.merge(v2); + ASSERT_EQ(asSlice(v1), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); + + v2.merge(v3); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); +} + +TEST(OrderedSetOf, mergeUnique) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; + using Slice = array19::SliceOf; + auto asSlice = [](auto& x) -> Slice { return OrderedSlice{x}; }; + + auto v1 = Set{OrderedSlice{Array{10, 20, 30}}}; + ASSERT_EQ(asSlice(v1), (Slice{Array{10, 20, 30}})); + + auto v2 = Set{OrderedSlice{Array{5, 11, 12, 35}}}; + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 11, 12, 35}})); + + auto v3 = v1; + + v1.merge(v2); + ASSERT_EQ(asSlice(v1), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); + + v2.merge(v3); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); +} + +TEST(OrderedSetOf, mergeEndDuplicate) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; + using Slice = array19::SliceOf; + auto asSlice = [](auto& x) -> Slice { return OrderedSlice{x}; }; + + auto v1 = Set{OrderedSlice{Array{10, 20, 30, 35}}}; + ASSERT_EQ(asSlice(v1), (Slice{Array{10, 20, 30, 35}})); + + auto v2 = Set{OrderedSlice{Array{5, 10, 11, 12, 35}}}; + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 35}})); + + auto v3 = v1; + + v1.merge(v2); + ASSERT_EQ(asSlice(v1), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); + + v2.merge(v3); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); +} + +TEST(OrderedSetOf, mergeWithCapacity) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; + using Slice = array19::SliceOf; + auto asSlice = [](auto& x) -> Slice { return static_cast(static_cast(x)); }; + + auto v1 = Set{OrderedSlice{Array{10, 20, 30}}}; + v1.ensureCapacity(10); + ASSERT_EQ(asSlice(v1), (Slice{Array{10, 20, 30}})); + + auto v2 = Set{OrderedSlice{Array{5, 10, 11, 12, 35}}}; + v2.ensureCapacity(10); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 35}})); + + auto v3 = v1; + + v1.merge(v2); + ASSERT_EQ(asSlice(v1), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); + + v2.merge(v3); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); +} + +TEST(OrderedSetOf, mergeUniqueWithCapacity) { + using Set = OrderedSetOf; + using OrderedSlice = Set::Slice; + using Slice = array19::SliceOf; + auto asSlice = [](auto& x) -> Slice { return OrderedSlice{x}; }; + + auto v1 = Set{OrderedSlice{Array{10, 20, 30}}}; + v1.ensureCapacity(10); + ASSERT_EQ(asSlice(v1), (Slice{Array{10, 20, 30}})); + + auto v2 = Set{OrderedSlice{Array{5, 11, 12, 35}}}; + v2.ensureCapacity(10); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 11, 12, 35}})); + + auto v3 = v1; + + v1.merge(v2); + ASSERT_EQ(asSlice(v1), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); + + v2.merge(v3); + ASSERT_EQ(asSlice(v2), (Slice{Array{5, 10, 11, 12, 20, 30, 35}})); +} diff --git a/src/lookup19.lib/lookup19/OrderedSliceOf.h b/src/lookup19.lib/lookup19/OrderedSliceOf.h index f73558e0..789f739a 100644 --- a/src/lookup19.lib/lookup19/OrderedSliceOf.h +++ b/src/lookup19.lib/lookup19/OrderedSliceOf.h @@ -15,6 +15,7 @@ template struct OrderedSliceOf { using Element = const T; using Count = size_t; using Index = size_t; + using Unordered = SliceOf; private: Element* m_data{}; @@ -22,16 +23,26 @@ template struct OrderedSliceOf { public: constexpr OrderedSliceOf() = default; - constexpr explicit OrderedSliceOf(Element* data, Element* end) noexcept : m_data(data), m_count(end - data) {} - constexpr explicit OrderedSliceOf(Element* data, Count count) noexcept : m_data(data), m_count(count) {} + constexpr explicit OrderedSliceOf(Unordered slice) : m_data{slice.begin()}, m_count{slice.count()} {} + constexpr explicit OrderedSliceOf(Element* data, Element* end) + : m_data{data} + , m_count{static_cast(end - data)} {} + constexpr explicit OrderedSliceOf(Element* data, Count count) : m_data{data}, m_count{count} {} + [[nodiscard]] constexpr auto isEmpty() const -> bool { return m_count == 0; } [[nodiscard]] constexpr auto count() const -> Count { return m_count; } - [[nodiscard]] constexpr auto begin() const& noexcept -> Element* { return m_data; } + [[nodiscard]] constexpr auto begin() const& -> Element* { return m_data; } [[nodiscard]] constexpr auto end() const& -> Element* { return m_data + m_count; } + [[nodiscard]] constexpr auto operator[](Index index) const -> Element& { return m_data[index]; } - [[nodiscard]] constexpr operator SliceOf() const noexcept { return SliceOf{m_data, m_count}; } + [[nodiscard]] constexpr operator Unordered() const { return Unordered{m_data, m_count}; } - template [[nodiscard]] constexpr auto lowerBound(K&& key) -> Element* { + template [[nodiscard]] constexpr bool has(K&& key) const { + auto it = lowerBound((K&&)key); + return it != end() && *it == key; + } + + template [[nodiscard]] constexpr auto lowerBound(K&& key) const -> Element* { auto less = Less{}; auto lower_bound = m_data; auto count = m_count; @@ -48,7 +59,7 @@ template struct OrderedSliceOf { return lower_bound; } - template [[nodiscard]] constexpr auto upperBound(K&& key) -> Element* { + template [[nodiscard]] constexpr auto upperBound(K&& key) const -> Element* { auto less = Less{}; auto upper_bound = m_data; auto count = m_count; @@ -65,7 +76,7 @@ template struct OrderedSliceOf { return upper_bound; } - template [[nodiscard]] constexpr auto equalRange(K&& key) -> OrderedSliceOf { + template [[nodiscard]] constexpr auto equalRange(K&& key) const -> OrderedSliceOf { auto less = Less{}; auto count = m_count; auto lower_bound = m_data; @@ -90,4 +101,6 @@ template struct OrderedSliceOf { } }; +template struct OrderedSliceOf; // use MoveSliceOf for that + } // namespace lookup19