From 8a68d085b412f244dd388af349795347ccdccbd7 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 9 Jun 2022 09:47:48 +0200 Subject: [PATCH] util/IntrusiveList: add option "constant_time_size" --- src/util/IntrusiveList.hxx | 47 +++++++++++++++++++++-- src/util/OptionalCounter.hxx | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 src/util/OptionalCounter.hxx diff --git a/src/util/IntrusiveList.hxx b/src/util/IntrusiveList.hxx index 84d874e47..fdfba06fe 100644 --- a/src/util/IntrusiveList.hxx +++ b/src/util/IntrusiveList.hxx @@ -34,6 +34,7 @@ #include "Cast.hxx" #include "MemberPointer.hxx" +#include "OptionalCounter.hxx" #include #include @@ -46,7 +47,7 @@ struct IntrusiveListNode { class IntrusiveListHook { template friend struct IntrusiveListBaseHookTraits; template friend struct IntrusiveListMemberHookTraits; - template friend class IntrusiveList; + template friend class IntrusiveList; protected: IntrusiveListNode siblings; @@ -128,6 +129,10 @@ struct IntrusiveListBaseHookTraits { template using Hook = typename IntrusiveListHookDetection::type; + static constexpr bool IsAutoUnlink() noexcept { + return std::is_base_of_v; + } + static constexpr T *Cast(IntrusiveListNode *node) noexcept { auto *hook = &Hook::Cast(*node); return static_cast(hook); @@ -156,6 +161,10 @@ struct IntrusiveListMemberHookTraits { using _Hook = MemberPointerType; using Hook = typename IntrusiveListHookDetection<_Hook>::type; + static constexpr bool IsAutoUnlink() noexcept { + return std::is_base_of_v; + } + static constexpr T *Cast(IntrusiveListNode *node) noexcept { auto &hook = Hook::Cast(*node); return &ContainerCast(hook, member); @@ -175,13 +184,22 @@ struct IntrusiveListMemberHookTraits { } }; -template> +/** + * @param constant_time_size make size() constant-time by caching the + * number of items in a field? + */ +template, + bool constant_time_size=false> class IntrusiveList { template using Hook = typename IntrusiveListHookDetection::type; IntrusiveListNode head{&head, &head}; + [[no_unique_address]] + OptionalCounter counter; + static constexpr T *Cast(IntrusiveListNode *node) noexcept { return HookTraits::Cast(node); } @@ -221,6 +239,9 @@ public: src.head.next = &src.head; src.head.prev = &src.head; + + using std::swap; + swap(counter, src.counter); } ~IntrusiveList() noexcept { @@ -251,6 +272,8 @@ public: b.head.next->prev = &b.head; b.head.prev->next = &b.head; } + + swap(a.counter, b.counter); } constexpr bool empty() const noexcept { @@ -258,7 +281,10 @@ public: } constexpr size_type size() const noexcept { - return std::distance(begin(), end()); + if constexpr (constant_time_size) + return counter; + else + return std::distance(begin(), end()); } void clear() noexcept { @@ -268,8 +294,10 @@ public: is_linked() method will not work */ while (!empty()) pop_front(); - } else + } else { head = {&head, &head}; + counter.reset(); + } } template @@ -291,6 +319,7 @@ public: if (pred(*i)) { ToHook(*i).unlink(); + --counter; dispose(i); } } @@ -306,12 +335,14 @@ public: void pop_front() noexcept { ToHook(front()).unlink(); + --counter; } template void pop_front_and_dispose(D &&disposer) noexcept { auto &i = front(); ToHook(i).unlink(); + --counter; disposer(&i); } @@ -321,6 +352,7 @@ public: void pop_back() noexcept { ToHook(back()).unlink(); + --counter; } class const_iterator; @@ -434,6 +466,7 @@ public: iterator erase(iterator i) noexcept { auto result = std::next(i); ToHook(*i).unlink(); + --counter; return result; } @@ -453,6 +486,10 @@ public: } 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 &new_node = ToNode(t); @@ -460,5 +497,7 @@ public: new_node.prev = existing_node.prev; existing_node.prev = &new_node; new_node.next = &existing_node; + + ++counter; } }; diff --git a/src/util/OptionalCounter.hxx b/src/util/OptionalCounter.hxx new file mode 100644 index 000000000..ad4a43988 --- /dev/null +++ b/src/util/OptionalCounter.hxx @@ -0,0 +1,74 @@ +/* + * Copyright 2022 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * 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 +#include + +template class OptionalCounter; + +template<> +class OptionalCounter +{ +public: + constexpr void reset() noexcept {} + constexpr auto &operator++() noexcept { return *this; } + constexpr auto &operator--() noexcept { return *this; } +}; + +template<> +class OptionalCounter +{ + 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; + } +};