util/IntrusiveTreeSet: new class

This commit is contained in:
Max Kellermann 2024-04-02 11:37:50 +02:00 committed by Max Kellermann
parent 6a99f20828
commit 5a0bad3b2f
4 changed files with 1045 additions and 0 deletions

View File

@ -0,0 +1,498 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <mk@cm4all.com>
#pragma once
#include "RedBlackTree.hxx"
#include "Cast.hxx"
#include "Concepts.hxx"
#include "IntrusiveHookMode.hxx"
#include "MemberPointer.hxx"
#include "OptionalCounter.hxx"
#include <cassert>
#include <compare> // for std::weak_ordering
#include <concepts> // for std::regular_invocable
#include <optional>
#include <utility> // for std::exchange()
struct IntrusiveTreeSetOptions {
bool constant_time_size = false;
};
template<IntrusiveHookMode _mode=IntrusiveHookMode::NORMAL>
class IntrusiveTreeSetHook {
template<typename T> friend struct IntrusiveTreeSetBaseHookTraits;
template<auto member> friend struct IntrusiveTreeSetMemberHookTraits;
template<typename T, typename Operators, typename HookTraits, IntrusiveTreeSetOptions> friend class IntrusiveTreeSet;
protected:
RedBlackTreeNode node;
public:
static constexpr IntrusiveHookMode mode = _mode;
constexpr IntrusiveTreeSetHook() noexcept {
if constexpr (mode >= IntrusiveHookMode::TRACK)
node.parent = nullptr;
}
constexpr ~IntrusiveTreeSetHook() noexcept {
if constexpr (mode >= IntrusiveHookMode::AUTO_UNLINK)
if (is_linked())
unlink();
}
IntrusiveTreeSetHook(const IntrusiveTreeSetHook &) = delete;
IntrusiveTreeSetHook &operator=(const IntrusiveTreeSetHook &) = delete;
constexpr void unlink() noexcept {
if constexpr (mode >= IntrusiveHookMode::TRACK) {
assert(is_linked());
}
node.Unlink();
if constexpr (mode >= IntrusiveHookMode::TRACK)
node.parent = nullptr;
}
bool is_linked() const noexcept {
static_assert(mode >= IntrusiveHookMode::TRACK);
return node.parent != nullptr;
}
private:
static constexpr auto &Cast(RedBlackTreeNode &node) noexcept {
return ContainerCast(node, &IntrusiveTreeSetHook::node);
}
static constexpr const auto &Cast(const RedBlackTreeNode &node) noexcept {
return ContainerCast(node, &IntrusiveTreeSetHook::node);
}
};
/**
* Detect the hook type.
*/
template<typename U>
struct IntrusiveTreeSetHookDetection {
/* TODO can this be simplified somehow, without checking for
all possible enum values? */
using type = std::conditional_t<std::is_base_of_v<IntrusiveTreeSetHook<IntrusiveHookMode::NORMAL>, U>,
IntrusiveTreeSetHook<IntrusiveHookMode::NORMAL>,
std::conditional_t<std::is_base_of_v<IntrusiveTreeSetHook<IntrusiveHookMode::TRACK>, U>,
IntrusiveTreeSetHook<IntrusiveHookMode::TRACK>,
std::conditional_t<std::is_base_of_v<IntrusiveTreeSetHook<IntrusiveHookMode::AUTO_UNLINK>, U>,
IntrusiveTreeSetHook<IntrusiveHookMode::AUTO_UNLINK>,
void>>>;
};
/**
* For classes which embed #IntrusiveTreeSetHook as base class.
*/
template<typename T>
struct IntrusiveTreeSetBaseHookTraits {
template<typename U>
using Hook = typename IntrusiveTreeSetHookDetection<U>::type;
static constexpr T *Cast(RedBlackTreeNode *node) noexcept {
auto *hook = &Hook<T>::Cast(*node);
return static_cast<T *>(hook);
}
static constexpr auto &ToHook(T &t) noexcept {
return static_cast<Hook<T> &>(t);
}
};
/**
* For classes which embed #IntrusiveTreeSetHook as member.
*/
template<auto member>
struct IntrusiveTreeSetMemberHookTraits {
using T = MemberPointerContainerType<decltype(member)>;
using _Hook = MemberPointerType<decltype(member)>;
template<typename Dummy>
using Hook = _Hook;
static constexpr T *Cast(RedBlackTreeNode *node) noexcept {
auto &hook = Hook<T>::Cast(*node);
return &ContainerCast(hook, member);
}
static constexpr auto &ToHook(T &t) noexcept {
return t.*member;
}
};
/**
* @param GetKey a function object which extracts the "key" part of an
* item
*/
template<typename T,
std::regular_invocable<const T &> GetKey=std::identity,
std::regular_invocable<std::invoke_result_t<GetKey, const T &>,
std::invoke_result_t<GetKey, const T &>> Compare=std::compare_three_way>
struct IntrusiveTreeSetOperators {
[[no_unique_address]]
GetKey get_key;
[[no_unique_address]]
Compare compare;
};
/**
* A binary tree implementation which stores pointers to items which
* have an embedded #IntrusiveTreeSetHook.
*/
template<typename T,
typename Operators=IntrusiveTreeSetOperators<T>,
typename HookTraits=IntrusiveTreeSetBaseHookTraits<T>,
IntrusiveTreeSetOptions options=IntrusiveTreeSetOptions{}>
class IntrusiveTreeSet {
static constexpr bool constant_time_size = options.constant_time_size;
[[no_unique_address]]
OptionalCounter<constant_time_size> counter;
[[no_unique_address]]
Operators ops;
RedBlackTreeNode head{RedBlackTreeNode::Head{}};
public:
using value_type = T;
using reference = T &;
using const_reference = const T &;
using pointer = T *;
using const_pointer = const T *;
using size_type = std::size_t;
[[nodiscard]]
IntrusiveTreeSet() noexcept = default;
[[nodiscard]]
constexpr bool empty() const noexcept {
return GetRoot() == nullptr;
}
[[nodiscard]]
constexpr size_type size() const noexcept {
if constexpr (constant_time_size)
return counter;
else
return std::distance(begin(), end());
}
constexpr void clear() noexcept {
SetRoot(nullptr);
counter.reset();
}
constexpr void clear_and_dispose(Disposer<value_type> auto disposer) noexcept {
dispose_all(GetRoot(), disposer);
clear();
}
class iterator {
friend IntrusiveTreeSet;
RedBlackTreeNode *node;
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
explicit constexpr iterator(RedBlackTreeNode *_node) noexcept
:node(_node) {}
constexpr bool operator==(const iterator &) const noexcept = default;
constexpr bool operator!=(const iterator &) const noexcept = default;
constexpr reference operator*() const noexcept {
return *Cast(node);
}
constexpr pointer operator->() const noexcept {
return Cast(node);
}
constexpr auto &operator++() noexcept {
node = RedBlackTreeNode::GetNextNode(node);
return *this;
}
};
[[nodiscard]]
constexpr iterator begin() noexcept {
auto *root = GetRoot();
return root != nullptr
? iterator{&root->GetLeftMost()}
: end();
}
[[nodiscard]]
constexpr iterator end() noexcept {
return iterator{nullptr};
}
class const_iterator {
friend IntrusiveTreeSet;
const RedBlackTreeNode *node;
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = const T;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
explicit constexpr const_iterator(RedBlackTreeNode *_node) noexcept
:node(_node) {}
constexpr const_iterator(iterator i) noexcept
:node(i.node) {}
constexpr bool operator==(const const_iterator &) const noexcept = default;
constexpr bool operator!=(const const_iterator &) const noexcept = default;
constexpr reference operator*() const noexcept {
return *Cast(node);
}
constexpr pointer operator->() const noexcept {
return Cast(node);
}
constexpr auto &operator++() noexcept {
node = RedBlackTreeNode::GetNextNode(const_cast<RedBlackTreeNode *>(node));
return *this;
}
};
[[nodiscard]]
constexpr const_iterator begin() const noexcept {
auto *root = GetRoot();
return root != nullptr
? const_iterator{&root->GetLeftMost()}
: end();
}
[[nodiscard]]
constexpr const_iterator end() const noexcept {
return const_iterator{nullptr};
}
const_reference front() const noexcept {
auto i = begin();
assert(i != end());
return *i;
}
reference front() noexcept {
auto i = begin();
assert(i != end());
return *i;
}
[[nodiscard]]
static constexpr iterator iterator_to(reference item) noexcept {
return iterator{&ToNode(item)};
}
[[nodiscard]]
constexpr iterator find(const auto &key) const noexcept {
auto *node = GetRoot();
#ifndef NDEBUG
bool previous_red = false;
#endif
while (node != nullptr) {
#ifndef NDEBUG
const bool current_red = node->color == RedBlackTreeNode::Color::RED;
assert(!previous_red || !current_red);
previous_red = current_red;
#endif
const auto &item = *Cast(node);
const std::weak_ordering compare_result = ops.compare(key, ops.get_key(item));
if (compare_result == std::weak_ordering::less)
node = node->GetLeft();
else if (compare_result == std::weak_ordering::greater)
node = node->GetRight();
else
break;
}
return iterator{node};
}
constexpr iterator insert(reference value) noexcept {
static_assert(!constant_time_size ||
GetHookMode() < IntrusiveHookMode::AUTO_UNLINK,
"Can't use auto-unlink hooks with constant_time_size");
auto *root = GetRoot();
if (root == nullptr) {
root = &ToNode(value);
root->Init(RedBlackTreeNode::Color::BLACK);
} else {
root = &insert(root, value);
}
SetRoot(root);
return iterator_to(value);
}
iterator erase(iterator i) noexcept {
assert(i.node != nullptr);
assert(!empty());
auto *next = RedBlackTreeNode::GetNextNode(i.node);
Cast(i.node)->unlink();
return iterator{next};
}
void pop_front() noexcept {
erase(begin());
}
private:
[[nodiscard]]
static constexpr auto GetHookMode() noexcept {
return HookTraits::template Hook<T>::mode;
}
[[nodiscard]]
static constexpr pointer Cast(RedBlackTreeNode *node) noexcept {
return HookTraits::Cast(node);
}
[[nodiscard]]
static constexpr const_pointer Cast(const RedBlackTreeNode *node) noexcept {
return HookTraits::Cast(const_cast<RedBlackTreeNode *>(node));
}
[[nodiscard]]
static constexpr auto &ToHook(T &t) noexcept {
return HookTraits::ToHook(t);
}
[[nodiscard]]
static constexpr const auto &ToHook(const T &t) noexcept {
return HookTraits::ToHook(t);
}
[[nodiscard]]
static constexpr RedBlackTreeNode &ToNode(T &t) noexcept {
return ToHook(t).node;
}
[[nodiscard]]
static constexpr const RedBlackTreeNode &ToNode(const T &t) noexcept {
return ToHook(t).node;
}
[[nodiscard]]
constexpr RedBlackTreeNode *GetRoot() const noexcept {
return head.GetLeft();
}
[[nodiscard]]
constexpr bool IsRoot(const RedBlackTreeNode &node) const noexcept {
return &node == GetRoot();
}
constexpr void SetRoot(RedBlackTreeNode *root) noexcept {
head.SetChild(RedBlackTreeNode::Direction::LEFT, root);
}
[[gnu::pure]]
constexpr RedBlackTreeNode::Direction GetInsertDirection(RedBlackTreeNode &parent,
const_reference new_value) const noexcept {
const auto &parent_value = *Cast(&parent);
const std::weak_ordering compare_result = ops.compare(ops.get_key(new_value), ops.get_key(parent_value));
return compare_result == std::weak_ordering::less
? RedBlackTreeNode::Direction::LEFT
: RedBlackTreeNode::Direction::RIGHT;
}
std::optional<RedBlackTreeNode::Direction> rotate1, rotate2;
RedBlackTreeNode &insert(RedBlackTreeNode *base,
reference value) noexcept {
if (base == nullptr) {
auto &node = ToNode(value);
node.Init(RedBlackTreeNode::Color::RED);
return node;
}
/* the actual insert is here */
const auto insert_direction = GetInsertDirection(*base, value);
auto &new_child = insert(base->GetChild(insert_direction), value);
base->SetChild(insert_direction, &new_child);
const bool red_red_conflict = !IsRoot(*base) &&
base->color == RedBlackTreeNode::Color::RED &&
new_child.color == RedBlackTreeNode::Color::RED;
/* rotate */
if (rotate1) {
base->SetChild(*rotate1, &base->GetChild(*rotate1)->Rotate(*rotate1));
rotate1.reset();
}
if (rotate2) {
base->color = RedBlackTreeNode::Color::RED;
base = &base->Rotate(*rotate2);
base->color = RedBlackTreeNode::Color::BLACK;
rotate2.reset();
}
if (red_red_conflict) {
const auto direction = base->GetDirectionInParent();
const auto other_direction = RedBlackTreeNode::OtherDirection(direction);
if (auto *sibling = base->parent->GetChild(other_direction);
sibling != nullptr &&
sibling->color == RedBlackTreeNode::Color::RED) {
sibling->color = RedBlackTreeNode::Color::BLACK;
base->color = RedBlackTreeNode::Color::BLACK;
if (!IsRoot(*base->parent))
base->parent->color = RedBlackTreeNode::Color::RED;
} else if (const auto *other_child = base->GetChild(other_direction);
other_child != nullptr && other_child->color == RedBlackTreeNode::Color::RED) {
rotate1 = direction;
rotate2 = other_direction;
} else if (const auto *child = base->GetChild(direction);
child != nullptr && child->color == RedBlackTreeNode::Color::RED) {
rotate2 = other_direction;
}
}
return *base;
}
void dispose_all(RedBlackTreeNode *node, Disposer<value_type> auto disposer) noexcept {
if (node == nullptr)
return;
for (auto *i : node->children)
dispose_all(i, disposer);
disposer(Cast(node));
}
};

356
src/util/RedBlackTree.hxx Normal file
View File

@ -0,0 +1,356 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <mk@cm4all.com>
#pragma once
#include <algorithm> // for std::any_of()
#include <array>
#include <cassert>
#include <utility> // for std::exchange()
struct RedBlackTreeNode {
RedBlackTreeNode *parent;
enum class Direction : std::size_t { LEFT, RIGHT };
std::array<RedBlackTreeNode *, 2> children;
enum class Color { HEAD, BLACK, RED };
Color color;
constexpr RedBlackTreeNode() noexcept = default;
struct Head {};
explicit constexpr RedBlackTreeNode(Head) noexcept
:children({}),
color(Color::HEAD) {}
RedBlackTreeNode(const RedBlackTreeNode &) = delete;
RedBlackTreeNode &operator=(const RedBlackTreeNode &) = delete;
constexpr void Init(Color _color) noexcept {
children = {};
color = _color;
}
[[nodiscard]]
constexpr bool IsHead() const noexcept {
return color == Color::HEAD;
}
[[nodiscard]]
constexpr bool IsRoot() const noexcept {
assert(!IsHead());
return parent->IsHead();
}
[[nodiscard]]
static constexpr Direction OtherDirection(Direction direction) noexcept {
return static_cast<Direction>(static_cast<std::size_t>(direction) ^ 1);
}
[[nodiscard]]
constexpr RedBlackTreeNode *GetChild(Direction direction) const noexcept {
return children[static_cast<std::size_t>(direction)];
}
[[nodiscard]]
constexpr RedBlackTreeNode *GetLeft() const noexcept {
return GetChild(Direction::LEFT);
}
[[nodiscard]]
constexpr RedBlackTreeNode *GetRight() const noexcept {
return GetChild(Direction::RIGHT);
}
[[nodiscard]]
constexpr RedBlackTreeNode *GetOtherChild(Direction direction) const noexcept {
return GetChild(OtherDirection(direction));
}
/**
* Set a new child and return the old one.
*/
constexpr auto *SetChild(Direction direction,
RedBlackTreeNode *child) noexcept {
auto *old = std::exchange(children[static_cast<std::size_t>(direction)],
child);
if (child != nullptr)
child->parent = this;
return old;
}
constexpr auto *SetChild(Direction direction,
RedBlackTreeNode &child) noexcept {
auto *old = std::exchange(children[static_cast<std::size_t>(direction)],
&child);
child.parent = this;
return old;
}
constexpr auto *SetOtherChild(Direction direction,
RedBlackTreeNode *child) noexcept {
return SetChild(OtherDirection(direction), child);
}
[[nodiscard]]
constexpr Direction GetChildDirection(const RedBlackTreeNode &child) const noexcept {
assert(child.parent == this);
assert(&child == GetChild(Direction::LEFT) ||
&child == GetChild(Direction::RIGHT));
return &child == GetChild(Direction::LEFT)
? Direction::LEFT
: Direction::RIGHT;
}
constexpr void ReplaceChild(RedBlackTreeNode &old_child,
RedBlackTreeNode *new_child) noexcept {
SetChild(GetChildDirection(old_child), new_child);
}
constexpr void ReplaceChild(RedBlackTreeNode &old_child,
RedBlackTreeNode &new_child) noexcept {
SetChild(GetChildDirection(old_child), new_child);
}
[[nodiscard]]
constexpr Direction GetDirectionInParent() const noexcept {
assert(parent != nullptr);
assert(!IsHead());
return parent->GetChildDirection(*this);
}
[[nodiscard]]
auto &Rotate(RedBlackTreeNode::Direction direction) noexcept {
assert(!IsHead());
auto *x = GetOtherChild(direction);
assert(x != nullptr);
auto *y = x->SetChild(direction, this);
SetOtherChild(direction, y);
return *x;
}
void RotateInParent(RedBlackTreeNode::Direction direction) noexcept {
assert(parent != nullptr);
assert(!IsHead());
auto &p = *parent;
const auto direction_in_parent = p.GetChildDirection(*this);
auto &new_node = Rotate(direction);
assert(new_node.parent == this);
assert(p.GetChild(direction_in_parent) == this);
p.SetChild(direction_in_parent, new_node);
}
[[nodiscard]]
constexpr static RedBlackTreeNode &GetLeftMost(RedBlackTreeNode *node) noexcept {
assert(node != nullptr);
assert(!node->IsHead());
while (auto *left = node->GetChild(Direction::LEFT)) {
assert(left->parent == node);
node = left;
}
return *node;
}
[[nodiscard]]
constexpr RedBlackTreeNode &GetLeftMost() noexcept {
return GetLeftMost(this);
}
private:
[[nodiscard]]
constexpr static RedBlackTreeNode *GetLeftHandedParent(RedBlackTreeNode *node) noexcept {
assert(node != nullptr);
assert(!node->IsHead());
while (true) {
assert(node->parent != nullptr);
auto &p = *node->parent;
if (p.IsHead())
return nullptr;
assert(node->color != RedBlackTreeNode::Color::RED ||
p.color != RedBlackTreeNode::Color::RED);
if (p.GetChildDirection(*node) == Direction::LEFT)
return &p;
node = &p;
}
}
public:
[[nodiscard]]
constexpr static RedBlackTreeNode *GetNextNode(RedBlackTreeNode *node) noexcept {
assert(node != nullptr);
assert(!node->IsHead());
if (auto *right = node->GetChild(Direction::RIGHT)) {
assert(node->color != RedBlackTreeNode::Color::RED ||
right->color != RedBlackTreeNode::Color::RED);
return &right->GetLeftMost();
}
assert(node->parent != nullptr);
auto &p = *node->parent;
if (p.IsHead())
return nullptr;
if (p.GetChildDirection(*node) == Direction::LEFT)
return &p;
return GetLeftHandedParent(&p);
}
private:
[[nodiscard]]
constexpr bool HasTwoChildren() const noexcept {
return children[0] != nullptr && children[1] != nullptr;
}
constexpr RedBlackTreeNode *GetAnyChild() const noexcept {
return children[children[1] != nullptr];
}
public:
constexpr void Unlink() noexcept {
assert(parent != nullptr);
assert(!IsHead());
if (HasTwoChildren()) {
/* swap with successor, because it, by
definition, doesn't have two children; the
rest of this method assumes we have exactly
one child or none */
auto &right = *GetRight();
auto &successor = right.GetLeftMost();
auto &p = *parent;
const auto direction_in_parent = p.GetChildDirection(*this);
successor.SetChild(Direction::LEFT, GetLeft());
SetChild(Direction::LEFT, nullptr);
SetChild(Direction::RIGHT, successor.GetRight());
if (&successor == &right) {
assert(successor.parent == this);
successor.SetChild(Direction::RIGHT, *this);
} else {
assert(successor.parent != this);
successor.parent->SetChild(Direction::LEFT, *this);
}
p.SetChild(direction_in_parent, successor);
} else {
/* if there is exactly one child, it must be red */
assert(GetAnyChild() == nullptr || GetAnyChild()->color == Color::RED);
}
assert(!HasTwoChildren());
auto &p = *parent;
if (auto *child = GetAnyChild()) {
p.ReplaceChild(*this, *child);
child->color = Color::BLACK;
} else if (IsRoot()) {
p.SetChild(Direction::LEFT, nullptr);
} else {
if (color == Color::BLACK)
FixDoubleBlack();
p.ReplaceChild(*this, nullptr);
}
}
private:
constexpr std::pair<Direction, RedBlackTreeNode *> GetRedChild() const noexcept {
if (auto *left = GetLeft(); left != nullptr && left->color == Color::RED)
return {Direction::LEFT, left};
if (auto *right = GetRight(); right != nullptr && right->color == Color::RED)
return {Direction::RIGHT, right};
return {};
}
constexpr void FixDoubleBlack() noexcept {
assert(parent != nullptr);
assert(!IsHead());
assert(color == Color::BLACK);
if (IsRoot())
return;
auto &p = *parent;
const auto direction = p.GetChildDirection(*this);
const auto other_direction = OtherDirection(direction);
auto *const sibling = p.GetChild(other_direction);
if (sibling == nullptr) {
p.FixDoubleBlack();
return;
}
switch (sibling->color) {
case Color::RED:
p.color = Color::RED;
sibling->color = Color::BLACK;
p.RotateInParent(direction);
FixDoubleBlack();
break;
case Color::BLACK:
if (const auto [red_direction, red] = sibling->GetRedChild(); red != nullptr) {
/* at least one red child */
if (direction == red_direction) {
red->color = p.color;
sibling->RotateInParent(other_direction);
} else {
red->color = sibling->color;
sibling->color = p.color;
}
p.RotateInParent(direction);
p.color = Color::BLACK;
} else {
/* no red child (both children are
either black or nullptr) */
sibling->color = Color::RED;
if (p.color == Color::BLACK)
p.FixDoubleBlack();
else
p.color = Color::BLACK;
}
break;
case Color::HEAD:
// unreachable
assert(false);
break;
}
}
};

View File

@ -0,0 +1,190 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright CM4all GmbH
// author: Max Kellermann <mk@cm4all.com>
#include "util/IntrusiveTreeSet.hxx"
#include <gtest/gtest.h>
#include <string>
namespace {
struct IntItem final : IntrusiveTreeSetHook<IntrusiveHookMode::TRACK> {
int value;
IntItem(int _value) noexcept:value(_value) {}
struct GetKey {
constexpr int operator()(const IntItem &item) const noexcept {
return item.value;
}
};
};
} // anonymous namespace
TEST(IntrusiveTreeSet, Basic)
{
IntItem a{1}, b{2}, c{3}, d{4}, e{5}, f{1};
IntrusiveTreeSet<IntItem,
IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> set;
EXPECT_EQ(set.size(), 0U);
EXPECT_TRUE(set.empty());
EXPECT_FALSE(a.is_linked());
EXPECT_FALSE(b.is_linked());
set.insert(b);
EXPECT_FALSE(a.is_linked());
EXPECT_TRUE(b.is_linked());
EXPECT_EQ(set.size(), 1U);
EXPECT_EQ(set.find(2), set.iterator_to(b));
EXPECT_EQ(&set.front(), &b);
set.insert(a);
EXPECT_EQ(&set.front(), &a);
EXPECT_TRUE(a.is_linked());
EXPECT_TRUE(b.is_linked());
set.insert(c);
EXPECT_EQ(&set.front(), &a);
EXPECT_EQ(set.size(), 3U);
EXPECT_NE(set.find(3), set.end());
EXPECT_EQ(set.find(3), set.iterator_to(c));
EXPECT_EQ(set.find(4), set.end());
EXPECT_TRUE(c.is_linked());
set.erase(set.iterator_to(c));
EXPECT_FALSE(c.is_linked());
EXPECT_EQ(set.size(), 2U);
EXPECT_EQ(set.find(3), set.end());
EXPECT_EQ(&set.front(), &a);
set.insert(c);
set.insert(d);
set.insert(e);
EXPECT_EQ(set.size(), 5U);
EXPECT_EQ(&set.front(), &a);
EXPECT_EQ(set.find(1), set.iterator_to(a));
EXPECT_EQ(set.find(2), set.iterator_to(b));
EXPECT_EQ(set.find(3), set.iterator_to(c));
EXPECT_EQ(set.find(4), set.iterator_to(d));
EXPECT_EQ(set.find(5), set.iterator_to(e));
EXPECT_TRUE(a.is_linked());
EXPECT_FALSE(f.is_linked());
set.erase(set.iterator_to(a));
EXPECT_FALSE(a.is_linked());
EXPECT_FALSE(f.is_linked());
EXPECT_EQ(set.find(1), set.end());
EXPECT_EQ(set.size(), 4U);
EXPECT_EQ(&set.front(), &b);
set.insert(f);
EXPECT_FALSE(a.is_linked());
EXPECT_TRUE(f.is_linked());
EXPECT_EQ(set.find(1), set.iterator_to(f));
EXPECT_EQ(set.size(), 5U);
EXPECT_EQ(&set.front(), &f);
set.pop_front();
EXPECT_FALSE(f.is_linked());
set.clear_and_dispose([](auto *i){ i->value = -1; });
EXPECT_EQ(set.size(), 0U);
EXPECT_TRUE(set.empty());
EXPECT_EQ(a.value, 1);
EXPECT_EQ(b.value, -1);
EXPECT_EQ(c.value, -1);
EXPECT_EQ(d.value, -1);
EXPECT_EQ(e.value, -1);
EXPECT_EQ(f.value, 1);
}
template<int... values>
static constexpr auto
MakeIntItems(std::integer_sequence<int, values...>) noexcept
-> std::array<IntItem, sizeof...(values)>
{
return {values...};
}
TEST(IntrusiveTreeSet, RandomOrder)
{
auto items = MakeIntItems(std::make_integer_sequence<int, 32>());
IntrusiveTreeSet<IntItem,
IntrusiveTreeSetOperators<IntItem, IntItem::GetKey>> set;
set.insert(items[0]);
set.insert(items[5]);
set.insert(items[10]);
set.insert(items[15]);
set.insert(items[20]);
set.insert(items[25]);
set.insert(items[30]);
set.insert(items[1]);
set.insert(items[2]);
set.insert(items[3]);
set.insert(items[31]);
set.insert(items[4]);
set.insert(items[6]);
set.insert(items[7]);
set.insert(items[21]);
set.insert(items[22]);
set.insert(items[23]);
set.insert(items[24]);
set.insert(items[26]);
set.insert(items[8]);
set.insert(items[9]);
set.insert(items[11]);
set.insert(items[12]);
set.insert(items[13]);
set.insert(items[14]);
set.insert(items[27]);
set.insert(items[28]);
set.insert(items[29]);
set.insert(items[16]);
set.insert(items[17]);
set.insert(items[18]);
set.insert(items[19]);
EXPECT_EQ(set.size(), items.size());
for (const auto &i : items) {
EXPECT_TRUE(i.is_linked());
}
int expected = 0;
for (const auto &i : set) {
EXPECT_EQ(i.value, expected++);
}
for (std::size_t remove = 0; remove < items.size(); ++remove) {
EXPECT_TRUE(items[remove].is_linked());
set.pop_front();
EXPECT_FALSE(items[remove].is_linked());
expected = remove + 1;
for (const auto &i : set) {
EXPECT_EQ(i.value, expected++);
}
}
}

View File

@ -8,6 +8,7 @@ test(
'TestIntrusiveForwardList.cxx',
'TestIntrusiveHashSet.cxx',
'TestIntrusiveList.cxx',
'TestIntrusiveTreeSet.cxx',
'TestMimeType.cxx',
'TestRingBuffer.cxx',
'TestSplitString.cxx',