util/IntrusiveList: add option "constant_time_size"

This commit is contained in:
Max Kellermann 2022-06-09 09:47:48 +02:00 committed by Max Kellermann
parent e437cc4faf
commit 8a68d085b4
2 changed files with 117 additions and 4 deletions

View File

@ -34,6 +34,7 @@
#include "Cast.hxx" #include "Cast.hxx"
#include "MemberPointer.hxx" #include "MemberPointer.hxx"
#include "OptionalCounter.hxx"
#include <iterator> #include <iterator>
#include <type_traits> #include <type_traits>
@ -46,7 +47,7 @@ struct IntrusiveListNode {
class IntrusiveListHook { class IntrusiveListHook {
template<typename T> friend struct IntrusiveListBaseHookTraits; template<typename T> friend struct IntrusiveListBaseHookTraits;
template<auto member> friend struct IntrusiveListMemberHookTraits; template<auto member> friend struct IntrusiveListMemberHookTraits;
template<typename T, typename HookTraits> friend class IntrusiveList; template<typename T, typename HookTraits, bool> friend class IntrusiveList;
protected: protected:
IntrusiveListNode siblings; IntrusiveListNode siblings;
@ -128,6 +129,10 @@ struct IntrusiveListBaseHookTraits {
template<typename U> template<typename U>
using Hook = typename IntrusiveListHookDetection<U>::type; using Hook = typename IntrusiveListHookDetection<U>::type;
static constexpr bool IsAutoUnlink() noexcept {
return std::is_base_of_v<AutoUnlinkIntrusiveListHook, T>;
}
static constexpr T *Cast(IntrusiveListNode *node) noexcept { static constexpr T *Cast(IntrusiveListNode *node) noexcept {
auto *hook = &Hook<T>::Cast(*node); auto *hook = &Hook<T>::Cast(*node);
return static_cast<T *>(hook); return static_cast<T *>(hook);
@ -156,6 +161,10 @@ struct IntrusiveListMemberHookTraits {
using _Hook = MemberPointerType<decltype(member)>; using _Hook = MemberPointerType<decltype(member)>;
using Hook = typename IntrusiveListHookDetection<_Hook>::type; using Hook = typename IntrusiveListHookDetection<_Hook>::type;
static constexpr bool IsAutoUnlink() noexcept {
return std::is_base_of_v<AutoUnlinkIntrusiveListHook, _Hook>;
}
static constexpr T *Cast(IntrusiveListNode *node) noexcept { static constexpr T *Cast(IntrusiveListNode *node) noexcept {
auto &hook = Hook::Cast(*node); auto &hook = Hook::Cast(*node);
return &ContainerCast(hook, member); return &ContainerCast(hook, member);
@ -175,13 +184,22 @@ struct IntrusiveListMemberHookTraits {
} }
}; };
template<typename T, typename HookTraits=IntrusiveListBaseHookTraits<T>> /**
* @param constant_time_size make size() constant-time by caching the
* number of items in a field?
*/
template<typename T,
typename HookTraits=IntrusiveListBaseHookTraits<T>,
bool constant_time_size=false>
class IntrusiveList { class IntrusiveList {
template<typename U> template<typename U>
using Hook = typename IntrusiveListHookDetection<U>::type; using Hook = typename IntrusiveListHookDetection<U>::type;
IntrusiveListNode head{&head, &head}; IntrusiveListNode head{&head, &head};
[[no_unique_address]]
OptionalCounter<constant_time_size> counter;
static constexpr T *Cast(IntrusiveListNode *node) noexcept { static constexpr T *Cast(IntrusiveListNode *node) noexcept {
return HookTraits::Cast(node); return HookTraits::Cast(node);
} }
@ -221,6 +239,9 @@ public:
src.head.next = &src.head; src.head.next = &src.head;
src.head.prev = &src.head; src.head.prev = &src.head;
using std::swap;
swap(counter, src.counter);
} }
~IntrusiveList() noexcept { ~IntrusiveList() noexcept {
@ -251,6 +272,8 @@ public:
b.head.next->prev = &b.head; b.head.next->prev = &b.head;
b.head.prev->next = &b.head; b.head.prev->next = &b.head;
} }
swap(a.counter, b.counter);
} }
constexpr bool empty() const noexcept { constexpr bool empty() const noexcept {
@ -258,6 +281,9 @@ public:
} }
constexpr size_type size() const noexcept { constexpr size_type size() const noexcept {
if constexpr (constant_time_size)
return counter;
else
return std::distance(begin(), end()); return std::distance(begin(), end());
} }
@ -268,8 +294,10 @@ public:
is_linked() method will not work */ is_linked() method will not work */
while (!empty()) while (!empty())
pop_front(); pop_front();
} else } else {
head = {&head, &head}; head = {&head, &head};
counter.reset();
}
} }
template<typename D> template<typename D>
@ -291,6 +319,7 @@ public:
if (pred(*i)) { if (pred(*i)) {
ToHook(*i).unlink(); ToHook(*i).unlink();
--counter;
dispose(i); dispose(i);
} }
} }
@ -306,12 +335,14 @@ public:
void pop_front() noexcept { void pop_front() noexcept {
ToHook(front()).unlink(); ToHook(front()).unlink();
--counter;
} }
template<typename D> template<typename D>
void pop_front_and_dispose(D &&disposer) noexcept { void pop_front_and_dispose(D &&disposer) noexcept {
auto &i = front(); auto &i = front();
ToHook(i).unlink(); ToHook(i).unlink();
--counter;
disposer(&i); disposer(&i);
} }
@ -321,6 +352,7 @@ public:
void pop_back() noexcept { void pop_back() noexcept {
ToHook(back()).unlink(); ToHook(back()).unlink();
--counter;
} }
class const_iterator; class const_iterator;
@ -434,6 +466,7 @@ public:
iterator erase(iterator i) noexcept { iterator erase(iterator i) noexcept {
auto result = std::next(i); auto result = std::next(i);
ToHook(*i).unlink(); ToHook(*i).unlink();
--counter;
return result; return result;
} }
@ -453,6 +486,10 @@ public:
} }
void insert(iterator p, T &t) noexcept { void insert(iterator p, T &t) noexcept {
static_assert(!constant_time_size ||
!HookTraits::IsAutoUnlink(),
"Can't use auto-unlink hooks with constant_time_size");
auto &existing_node = ToNode(*p); auto &existing_node = ToNode(*p);
auto &new_node = ToNode(t); auto &new_node = ToNode(t);
@ -460,5 +497,7 @@ public:
new_node.prev = existing_node.prev; new_node.prev = existing_node.prev;
existing_node.prev = &new_node; existing_node.prev = &new_node;
new_node.next = &existing_node; new_node.next = &existing_node;
++counter;
} }
}; };

View File

@ -0,0 +1,74 @@
/*
* Copyright 2022 CM4all GmbH
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <cassert>
#include <cstddef>
template<bool enable> class OptionalCounter;
template<>
class OptionalCounter<false>
{
public:
constexpr void reset() noexcept {}
constexpr auto &operator++() noexcept { return *this; }
constexpr auto &operator--() noexcept { return *this; }
};
template<>
class OptionalCounter<true>
{
std::size_t value = 0;
public:
constexpr operator std::size_t() const noexcept {
return value;
}
constexpr void reset() noexcept {
value = 0;
}
constexpr auto &operator++() noexcept {
++value;
return *this;
}
constexpr auto &operator--() noexcept {
assert(value > 0);
--value;
return *this;
}
};