util/IntrusiveForwardList: add option cache_last

This commit is contained in:
Max Kellermann 2023-09-12 20:13:00 +02:00 committed by Max Kellermann
parent 64b0587e78
commit f448bfd3f3
2 changed files with 156 additions and 3 deletions

View File

@ -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;
}

View File

@ -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]);
}