util/IntrusiveForwardList: add option cache_last
This commit is contained in:
parent
64b0587e78
commit
f448bfd3f3
@ -8,6 +8,7 @@
|
||||
#include "Concepts.hxx"
|
||||
#include "MemberPointer.hxx"
|
||||
#include "OptionalCounter.hxx"
|
||||
#include "OptionalField.hxx"
|
||||
#include "ShallowCopy.hxx"
|
||||
|
||||
#include <iterator>
|
||||
@ -16,6 +17,12 @@
|
||||
|
||||
struct IntrusiveForwardListOptions {
|
||||
bool constant_time_size = false;
|
||||
|
||||
/**
|
||||
* Cache a pointer to the last item? This makes back() and
|
||||
* push_back() run in constant time.
|
||||
*/
|
||||
bool cache_last = false;
|
||||
};
|
||||
|
||||
struct IntrusiveForwardListNode {
|
||||
@ -103,6 +110,9 @@ class IntrusiveForwardList {
|
||||
|
||||
IntrusiveForwardListNode head{nullptr};
|
||||
|
||||
[[no_unique_address]]
|
||||
OptionalField<IntrusiveForwardListNode *, options.cache_last> last_cache{&head};
|
||||
|
||||
[[no_unique_address]]
|
||||
OptionalCounter<constant_time_size> counter;
|
||||
|
||||
@ -144,6 +154,7 @@ public:
|
||||
:head{std::exchange(src.head.next, nullptr)}
|
||||
{
|
||||
using std::swap;
|
||||
swap(last_cache, src.last_cache);
|
||||
swap(counter, src.counter);
|
||||
}
|
||||
|
||||
@ -152,11 +163,13 @@ public:
|
||||
{
|
||||
// shallow copies mess with the counter
|
||||
static_assert(!options.constant_time_size);
|
||||
static_assert(!options.cache_last);
|
||||
}
|
||||
|
||||
IntrusiveForwardList &operator=(IntrusiveForwardList &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(head, src.head);
|
||||
swap(last_cache, src.last_cache);
|
||||
swap(counter, src.counter);
|
||||
return *this;
|
||||
}
|
||||
@ -174,6 +187,7 @@ public:
|
||||
|
||||
void clear() noexcept {
|
||||
head = {};
|
||||
last_cache = {};
|
||||
counter.reset();
|
||||
}
|
||||
|
||||
@ -183,6 +197,8 @@ public:
|
||||
pop_front();
|
||||
disposer(item);
|
||||
}
|
||||
|
||||
last_cache = {};
|
||||
}
|
||||
|
||||
const_reference front() const noexcept {
|
||||
@ -196,6 +212,11 @@ public:
|
||||
reference pop_front() noexcept {
|
||||
auto &i = front();
|
||||
head.next = head.next->next;
|
||||
|
||||
if constexpr (options.cache_last)
|
||||
if (head.next == nullptr)
|
||||
last_cache.value = &head;
|
||||
|
||||
--counter;
|
||||
return i;
|
||||
}
|
||||
@ -205,6 +226,16 @@ public:
|
||||
disposer(&i);
|
||||
}
|
||||
|
||||
const_reference back() const noexcept
|
||||
requires(options.cache_last) {
|
||||
return *Cast(last_cache.value);
|
||||
}
|
||||
|
||||
reference back() noexcept
|
||||
requires(options.cache_last) {
|
||||
return *Cast(last_cache.value);
|
||||
}
|
||||
|
||||
class const_iterator;
|
||||
|
||||
class iterator final {
|
||||
@ -265,6 +296,11 @@ public:
|
||||
return {nullptr};
|
||||
}
|
||||
|
||||
constexpr iterator last() noexcept
|
||||
requires(options.cache_last) {
|
||||
return {last_cache.value};
|
||||
}
|
||||
|
||||
class const_iterator final {
|
||||
friend IntrusiveForwardList;
|
||||
|
||||
@ -317,15 +353,35 @@ public:
|
||||
return {head.next};
|
||||
}
|
||||
|
||||
constexpr const_iterator last() const noexcept
|
||||
requires(options.cache_last) {
|
||||
return {last_cache.value};
|
||||
}
|
||||
|
||||
void push_front(reference t) noexcept {
|
||||
auto &new_node = ToNode(t);
|
||||
|
||||
if constexpr (options.cache_last)
|
||||
if (empty())
|
||||
last_cache.value = &new_node;
|
||||
|
||||
new_node.next = head.next;
|
||||
head.next = &new_node;
|
||||
++counter;
|
||||
}
|
||||
|
||||
void push_back(reference t) noexcept
|
||||
requires(options.cache_last) {
|
||||
auto &new_node = ToNode(t);
|
||||
new_node.next = nullptr;
|
||||
last_cache.value->next = &new_node;
|
||||
last_cache.value = &new_node;
|
||||
|
||||
++counter;
|
||||
}
|
||||
|
||||
static iterator insert_after(iterator pos, reference t) noexcept
|
||||
requires(!constant_time_size) {
|
||||
requires(!constant_time_size && !options.cache_last) {
|
||||
/* if we have no counter, then this method is allowed
|
||||
to be static */
|
||||
|
||||
@ -336,9 +392,15 @@ public:
|
||||
return &new_node;
|
||||
}
|
||||
|
||||
iterator insert_after(iterator pos, reference t) noexcept {
|
||||
iterator insert_after(iterator pos, reference t) noexcept
|
||||
requires(constant_time_size || options.cache_last) {
|
||||
auto &pos_node = *pos.cursor;
|
||||
auto &new_node = ToNode(t);
|
||||
|
||||
if constexpr (options.cache_last)
|
||||
if (pos_node.next == nullptr)
|
||||
last_cache.value = &new_node;
|
||||
|
||||
new_node.next = pos_node.next;
|
||||
pos_node.next = &new_node;
|
||||
++counter;
|
||||
@ -347,6 +409,11 @@ public:
|
||||
|
||||
void erase_after(iterator pos) noexcept {
|
||||
pos.cursor->next = pos.cursor->next->next;
|
||||
|
||||
if constexpr (options.cache_last)
|
||||
if (pos.cursor->next == nullptr)
|
||||
last_cache.value = pos.cursor;
|
||||
|
||||
--counter;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ struct CharItem final : IntrusiveForwardListHook {
|
||||
};
|
||||
|
||||
static std::string
|
||||
ToString(const IntrusiveForwardList<CharItem> &list) noexcept
|
||||
ToString(const auto &list) noexcept
|
||||
{
|
||||
std::string result;
|
||||
for (const auto &i : list)
|
||||
@ -50,3 +50,89 @@ TEST(IntrusiveForwardList, Basic)
|
||||
list.reverse();
|
||||
ASSERT_EQ(ToString(list), "cb");
|
||||
}
|
||||
|
||||
TEST(IntrusiveForwardList, CacheLast)
|
||||
{
|
||||
using Item = CharItem;
|
||||
|
||||
Item items[]{'a', 'b', 'c'};
|
||||
|
||||
using List =IntrusiveForwardList<
|
||||
CharItem, IntrusiveForwardListBaseHookTraits<CharItem>,
|
||||
IntrusiveForwardListOptions{.cache_last = true}>;
|
||||
List list;
|
||||
ASSERT_EQ(ToString(list), "");
|
||||
list.reverse();
|
||||
ASSERT_EQ(ToString(list), "");
|
||||
|
||||
for (auto &i : items)
|
||||
list.push_front(i);
|
||||
|
||||
ASSERT_EQ(ToString(list), "cba");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.erase_after(list.begin());
|
||||
ASSERT_EQ(ToString(list), "ca");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.reverse();
|
||||
ASSERT_EQ(ToString(list), "ac");
|
||||
ASSERT_EQ(&list.back(), &items[2]);
|
||||
|
||||
list.erase_after(list.begin());
|
||||
ASSERT_EQ(ToString(list), "a");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.reverse();
|
||||
ASSERT_EQ(ToString(list), "a");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.pop_front();
|
||||
ASSERT_EQ(ToString(list), "");
|
||||
|
||||
list.insert_after(list.before_begin(), items[0]);
|
||||
ASSERT_EQ(ToString(list), "a");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.insert_after(list.before_begin(), items[1]);
|
||||
ASSERT_EQ(ToString(list), "ba");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.pop_front();
|
||||
ASSERT_EQ(ToString(list), "a");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
|
||||
list.pop_front();
|
||||
ASSERT_EQ(ToString(list), "");
|
||||
|
||||
for (auto &i : items)
|
||||
list.push_back(i);
|
||||
|
||||
ASSERT_EQ(ToString(list), "abc");
|
||||
ASSERT_EQ(&list.back(), &items[2]);
|
||||
|
||||
/* move constructor */
|
||||
auto list2 = std::move(list);
|
||||
ASSERT_EQ(ToString(list2), "abc");
|
||||
ASSERT_EQ(&list2.back(), &items[2]);
|
||||
ASSERT_EQ(ToString(list), "");
|
||||
|
||||
/* move operator */
|
||||
list = std::move(list2);
|
||||
ASSERT_EQ(ToString(list), "abc");
|
||||
ASSERT_EQ(&list.back(), &items[2]);
|
||||
ASSERT_EQ(ToString(list2), "");
|
||||
|
||||
list.erase_after(list.begin());
|
||||
ASSERT_EQ(ToString(list), "ac");
|
||||
ASSERT_EQ(&list.back(), &items[2]);
|
||||
|
||||
list.erase_after(list.before_begin());
|
||||
ASSERT_EQ(ToString(list), "c");
|
||||
ASSERT_EQ(&list.back(), &items[2]);
|
||||
|
||||
// insert_after()
|
||||
list.insert_after(list.begin(), items[0]);
|
||||
ASSERT_EQ(ToString(list), "ca");
|
||||
ASSERT_EQ(&list.back(), &items[0]);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user