::StackType otherStackCopy(parOther.m_stack);
+ std::vector localCopy;
+ localCopy.reserve(parOther.m_stack.size());
+ while (not otherStackCopy.empty()) {
+ P convertedItem = otherStackCopy.top();
+ localCopy.push_back(convertedItem);
+ otherStackCopy.pop();
+ }
+ m_stack.reserve(parOther.m_stack.capacity());
+ for (typename std::vector
::reverse_iterator itRev = localCopy.rbegin(), itRevEnd = localCopy.rend(); itRev != itRevEnd; ++itRev) {
+ assert(m_stack.capacity() > m_stack.size());
+ m_stack.push(*itRev);
+ }
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ typename TreeIterator_const_layer::reference TreeIterator_const_layer::operator* () {
+ assert(not this->Exhausted());
+ return this->m_stack.top()->content;
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ typename TreeIterator_const_layer::const_reference TreeIterator_const_layer::operator* () const {
+ assert(not this->Exhausted());
+ return this->m_stack.top()->content;
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ typename TreeIterator_const_layer::pointer TreeIterator_const_layer::operator-> () {
+ assert(not this->Exhausted());
+ return &this->m_stack.top()->content;
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ typename TreeIterator_const_layer::const_pointer TreeIterator_const_layer::operator-> () const {
+ assert(not this->Exhausted());
+ return &this->m_stack.top()->content;
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ N* TreeIterator_const_layer::GetPointer() {
+ assert(not this->Exhausted());
+ return this->m_stack.top();
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ const N* TreeIterator_const_layer::GetPointer() const {
+ assert(not this->Exhausted());
+ return this->m_stack.top();
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ typename TreeIterator_const_layer::const_reference TreeIterator_const_layer::operator* () const {
+ assert(not this->Exhausted());
+ return this->m_stack.top()->content;
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ typename TreeIterator_const_layer::const_pointer TreeIterator_const_layer::operator-> () const {
+ assert(not this->Exhausted());
+ return &this->m_stack.top()->content;
+ }
+
+ ///---------------------------------------------------------------------
+ ///---------------------------------------------------------------------
+ template
+ const N* TreeIterator_const_layer::GetPointer() const {
+ assert(not this->Exhausted());
+ return this->m_stack.top();
+ }
+ } //namespace implem
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ TreeIterator::TreeIterator() {
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ TreeIterator::TreeIterator (const TreeIterator& parOther) :
+ parent_type(parOther)
+ {
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ template
+ TreeIterator::TreeIterator (const TreeIterator& parOther) :
+ parent_type(parOther)
+ {
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ template
+ TreeIterator::TreeIterator (S parCopyStackBottomUp, size_type parStackLen, size_type parMaxDepthHint) {
+ assert(parStackLen > 0);
+ this->m_stack.reserve(std::max(parStackLen, parMaxDepthHint));
+ typename StackType::value_type prevNode = *parCopyStackBottomUp;
+ ++parCopyStackBottomUp;
+ this->m_stack.push(prevNode);
+
+ for (size_type z = 1; z < parStackLen; ++z) {
+ typename StackType::value_type currNode = *parCopyStackBottomUp;
+ if (prevNode->left == currNode) {
+ assert(this->m_stack.capacity() > this->m_stack.size());
+ this->m_stack.push(currNode);
+ }
+ else {
+ //If you get this assertion make sure the iterator you are
+ //passing in is reversed (ie: from leaf to root)
+ assert(currNode == prevNode->right);
+ }
+
+ prevNode = currNode;
+ ++parCopyStackBottomUp;
+ }
+ assert(not this->Exhausted());
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ TreeIterator::TreeIterator (NodeTypePointer parRoot, size_type parMaxDepthHint) {
+ if (parMaxDepthHint > 0)
+ this->m_stack.reserve(parMaxDepthHint);
+ RecurseLeft(parRoot);
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ TreeIterator::~TreeIterator() {
+ }
+
+ ///-------------------------------------------------------------------------
+ ///Post-increment
+ ///-------------------------------------------------------------------------
+ template
+ TreeIterator TreeIterator::operator++ (int) {
+ assert(not this->Exhausted());
+ TreeIterator retVal = *this;
+ ++(*this);
+ return retVal;
+ }
+
+ ///-------------------------------------------------------------------------
+ ///Pre-increment
+ ///-------------------------------------------------------------------------
+ template
+ TreeIterator& TreeIterator::operator++() {
+ assert(not this->Exhausted());
+ NodeTypePointer currNode = this->m_stack.top();
+#if defined(ASSERTIONSENABLED)
+ const size_type stackCapacity = this->m_stack.capacity();
+#endif
+ this->m_stack.pop();
+#if defined(ASSERTIONSENABLED)
+ //It shouldn't normally happen, but it's just to make sure
+ assert(stackCapacity == this->m_stack.capacity());
+#endif
+ RecurseLeft(currNode->right);
+ return *this;
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ const TreeIterator& TreeIterator::operator= (const TreeIterator& parOther) {
+ this->m_stack = parOther.m_stack;
+ assert(this->m_stack.capacity() >= parOther.m_stack.capacity());
+ return *this;
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ bool TreeIterator::operator== (const TreeIterator& parOther) const {
+ return this->m_stack.size() == parOther.m_stack.size() and (this->m_stack.empty() or parOther.m_stack.top() == this->m_stack.top());
+ }
+
+ ///-------------------------------------------------------------------------
+ ///-------------------------------------------------------------------------
+ template
+ void TreeIterator::RecurseLeft (NodeTypePointer parFrom) {
+ NodeTypePointer currNode = parFrom;
+ while (NULL != currNode) {
+ assert(this->m_stack.capacity() > this->m_stack.size());
+ this->m_stack.push(currNode);
+ currNode = currNode->left;
+ }
+ }
+} //namespace dhandy
diff --git a/include/duckhandy/small_object_allocator.hpp b/include/duckhandy/small_object_allocator.hpp
new file mode 100644
index 0000000..b3fe28b
--- /dev/null
+++ b/include/duckhandy/small_object_allocator.hpp
@@ -0,0 +1,268 @@
+/* Copyright 2016-2024 Michele Santullo
+ * This file is part of "duckhandy".
+ *
+ * "duckhandy" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "duckhandy" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "duckhandy". If not, see .
+ */
+
+#ifndef idE77208CAFC79452DA12757DD0F6692D3
+#define idE77208CAFC79452DA12757DD0F6692D3
+
+#include "alignment.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if !defined(NDEBUG) && !defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+# define DEBUG_SMALL_OBJECT_ALLOCATOR
+#endif
+
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+# include "tiger_bt.hpp"
+# include "lengthof.h"
+#endif
+
+namespace dhandy {
+namespace implem {
+[[gnu::pure,gnu::always_inline]]
+unsigned int ffs (unsigned int val) { return static_cast(::ffs(static_cast(val))); }
+
+[[gnu::pure,gnu::always_inline]]
+unsigned int ffs (unsigned long val) { return static_cast(::ffsl(static_cast(val))); }
+
+[[gnu::pure,gnu::always_inline]]
+unsigned int ffs (unsigned long long val) { return static_cast(::ffsll(static_cast(val))); }
+
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+template typename A>
+consteval std::uint32_t make_signature() {
+ return static_cast(
+ bt::tiger(__PRETTY_FUNCTION__, lengthof(__PRETTY_FUNCTION__), bt::TigerPaddingV2).a & 0xFFFFFFFF
+ );
+}
+#endif
+} //namespace implem
+
+template
+struct AllocatorFunction {
+ std::unique_ptr operator()() {
+ return std::make_unique();
+ }
+};
+
+template typename A=AllocatorFunction>
+class SmallObjectAllocator {
+public:
+ typedef T value_type;
+ typedef T* pointer;
+ typedef const T* const_pointer;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef std::size_t size_type;
+ typedef std::ptrdiff_t difference_type;
+ typedef std::true_type propagate_on_container_move_assignment;
+
+private:
+ typedef std::uint_fast32_t uint_freelist_t;
+ typedef std::uint32_t index_t;
+
+ struct TAndPtrFakeStruct { //only used for sizeof()
+ TAndPtrFakeStruct() = delete;
+ T t;
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+ std::uint32_t signature;
+#endif
+ index_t block_index; //index in a lookup table so that lookup[index-1]->next == owner
+ };
+
+ static constexpr std::uint32_t signature = implem::make_signature();
+ static constexpr size_type size = sizeof(TAndPtrFakeStruct);
+ static constexpr size_type align = alignof(TAndPtrFakeStruct);
+ static constexpr size_type object_size = sizeof(T); //(size < sizeof(T*) ? sizeof(T*) : size);
+ static constexpr size_type object_align = alignof(T); //(align < alignof(T*) ? alignof(T*) : align);
+ static constexpr size_type block_ptr_offset = offsetof(TAndPtrFakeStruct, block);
+
+public:
+ static constexpr size_type objects_per_block = CHAR_BIT * sizeof(uint_freelist_t);
+
+private:
+ struct Block {
+ typedef typename std::aligned_storage::type raw_t;
+
+ raw_t data[objects_per_block];
+ std::unique_ptr next;
+ uint_freelist_t freelist{0};
+ };
+
+public:
+
+ SmallObjectAllocator() = default;
+ ~SmallObjectAllocator() noexcept = default;
+
+ [[nodiscard]] constexpr pointer allocate (size_type size);
+ //[[nodiscard]] constexpr std::allocation_result allocate_at_least (size_type size);
+ constexpr void deallocate (pointer ptr, size_type size);
+
+ //template
+ //constexpr bool operator== (const allocator& rhs) noexcept;
+
+
+private:
+ static index_t* fetch_block_index (Block::raw_t* in_ptr);
+ static void set_block_indices (Block* block, index_t new_index);
+ static void set_debug_signatures (Block* block);
+
+ std::vector m_block_list;
+ std::unique_ptr m_head;
+ index_t m_prev_index;
+ index_t m_curr_index{0};
+};
+
+template typename A>
+constexpr auto SmallObjectAllocator::allocate (size_type size) -> pointer {
+ assert(object_size == size);
+#if defined(NDEBUG)
+ static_cast(size);
+#endif
+
+ assert(0 == m_curr_index or m_curr_index <= m_block_list.size());
+ assert(m_head or m_block_list.empty());
+ if (!m_curr_index or !~m_block_list[m_curr_index-1]->freelist) {
+ A alloc_function{};
+ auto new_block = alloc_function();
+ set_debug_signatures(new_block.get());
+ new_block->next = std::move(m_head);
+ m_head.swap(new_block);
+ m_block_list.push_back(m_head.get());
+ m_prev_index = m_curr_index;
+ m_curr_index = static_cast(m_block_list.size());
+
+ if (m_head->next)
+ set_block_indices(m_head->next.get(), m_curr_index);
+ }
+
+ Block*const block = m_block_list[m_curr_index-1];
+ assert(block != nullptr);
+ const uint_freelist_t neg_freelist = static_cast(~block->freelist);
+ assert(neg_freelist != 0);
+
+ const unsigned int object_num = implem::ffs(neg_freelist);
+ assert(object_num);
+ const unsigned int object_index = object_num - 1;
+ assert(object_index < objects_per_block);
+ constexpr uint_freelist_t one = 1;
+ block->freelist |= one << object_index;
+
+ index_t* const index_ptr = fetch_block_index(block->data + object_index);
+ assert(index_ptr);
+ *index_ptr = m_prev_index;
+ return reinterpret_cast(block->data + object_index);
+}
+
+template typename A>
+constexpr void SmallObjectAllocator::deallocate (pointer ptr, size_type size) {
+ assert(object_size == size);
+ auto* const block_ptr = reinterpret_cast(ptr);
+ const index_t owner_prev_index = *fetch_block_index(block_ptr);
+ Block* const owner = (0 == owner_prev_index ? m_head.get() : m_block_list[owner_prev_index-1]->next.get());
+
+ assert(owner);
+ const auto flag_index = std::distance(owner->data, block_ptr);
+ assert(flag_index >= 0);
+ assert(flag_index < objects_per_block);
+
+ constexpr uint_freelist_t one = 1;
+ const uint_freelist_t mask = one << flag_index;
+ assert((owner->freelist & mask) != 0);
+ owner->freelist &= ~mask;
+
+ if (0 == owner->freelist) {
+ if (owner_prev_index) {
+ Block& prev = *m_block_list[owner_prev_index-1];
+ auto found = std::find(m_block_list.begin(), m_block_list.end(), owner);
+ assert(m_block_list.end() != found);
+ it's all fucked up here, I can't delete the item because that would
+ invalidate all the subsequent indices. It means all blocks following
+ the one being deleted would have to get set_block_indices() invoked
+ on them which is insane so this whole approach is fucke'
+
+ auto empty_block = std::move(prev.next);
+ assert(!prev.next);
+ prev.next = std::move(empty_block->next);
+ set_block_indices(prev.next.get(), owner_prev_index);
+
+ }
+ else {
+ assert(m_block_list.empty());
+ m_head.reset();
+ m_curr_index = 0;
+ }
+ }
+}
+
+template typename A>
+auto SmallObjectAllocator::fetch_block_index (Block::raw_t* in_ptr) -> index_t* {
+ assert(in_ptr);
+
+ char* ptr = reinterpret_cast(in_ptr);
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+ ptr = align_ptr_to(ptr, object_size);
+ {
+ std::uint32_t read_signature;
+ std::memcpy(&read_signature, ptr, sizeof(signature));
+ assert(signature == read_signature);
+ }
+ ptr = align_ptr_to(ptr, sizeof(signature));
+#else
+ ptr = align_ptr_to(ptr, object_size);
+#endif
+
+ index_t* const retval = reinterpret_cast(ptr);
+ return retval;
+}
+
+template typename A>
+void SmallObjectAllocator::set_block_indices (Block* block, index_t new_index) {
+ uint_freelist_t flag = 1;
+ for (unsigned int z = 0; z < objects_per_block; ++z, flag<<=1) {
+ if (flag & block->freelist) {
+ index_t* const dst_index = fetch_block_index(block->data + z);
+ assert(dst_index);
+ *dst_index = new_index;
+ }
+ }
+}
+
+template typename A>
+void SmallObjectAllocator::set_debug_signatures (Block* block) {
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+ const auto sig = signature;
+ for (unsigned int z = 0; z < objects_per_block; ++z) {
+ std::memcpy(align_ptr_to(block->data + z, object_size), &sig, sizeof(signature));
+ }
+#else
+ static_cast(block);
+#endif
+}
+} //namespace dhandy
+
+#endif
diff --git a/include/duckhandy/tree_iterator.hpp b/include/duckhandy/tree_iterator.hpp
new file mode 100644
index 0000000..40b3e41
--- /dev/null
+++ b/include/duckhandy/tree_iterator.hpp
@@ -0,0 +1,152 @@
+/* Copyright 2016-2024 Michele Santullo
+ * This file is part of "duckhandy".
+ *
+ * "duckhandy" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "duckhandy" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "duckhandy". If not, see .
+ */
+
+#ifndef id6109D5EDE99D43C4909F084A231BF2C2
+#define id6109D5EDE99D43C4909F084A231BF2C2
+
+#include
+#include
+#include
+#include
+#include
+
+namespace dhandy {
+ namespace implem {
+ template
+ class TreeIteratorStack : public std::stack> {
+ public:
+ void reserve (std::size_t size) {
+ std::stack
>::c.reserve(size);
+ }
+
+ std::size_t capacity() const {
+ return std::stack
>::c.capacity();
+ }
+ private:
+ };
+
+ template
+ class TreeIterator_base {
+ template friend class TreeIterator_base;
+ public:
+ explicit TreeIterator_base ( void ) {}
+ TreeIterator_base ( const TreeIterator_base& parOther );
+ template
+ explicit TreeIterator_base ( const TreeIterator_base& parOther );
+ ~TreeIterator_base ( void ) {}
+ protected:
+ typedef TreeIteratorStack StackType;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+ bool Exhausted ( void ) const;
+ StackType m_stack;
+ };
+
+ template
+ class TreeIterator_const_layer;
+ template
+ class TreeIterator_const_layer : protected TreeIterator_base {
+ template friend class TreeIterator_const_layer;
+ typedef TreeIterator_base parent_type;
+ public:
+ typedef const T* pointer;
+ typedef const T* const_pointer;
+ typedef const T& reference;
+ typedef const T& const_reference;
+ typedef typename parent_type::size_type size_type;
+ typedef typename parent_type::difference_type difference_type;
+ typedef N NodeType;
+ typedef const N* NodeTypePointer;
+ enum { IS_CONST = 1 };
+ TreeIterator_const_layer ( void ) {}
+ TreeIterator_const_layer ( const TreeIterator_const_layer& parOther ) : parent_type(parOther) {}
+ template
+ explicit TreeIterator_const_layer ( const TreeIterator_const_layer& parOther ) : parent_type(parOther) {}
+ const_reference operator* ( void ) const;
+ const_pointer operator-> ( void ) const;
+ const N* GetPointer ( void ) const;
+ protected:
+ typedef typename parent_type::StackType StackType;
+ };
+ template
+ class TreeIterator_const_layer : protected TreeIterator_base {
+ template friend class TreeIterator_const_layer;
+ typedef TreeIterator_base parent_type;
+ public:
+ typedef T* pointer;
+ typedef const T* const_pointer;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef typename parent_type::size_type size_type;
+ typedef typename parent_type::difference_type difference_type;
+ typedef N NodeType;
+ typedef N* NodeTypePointer;
+ enum { IS_CONST = 0 };
+ TreeIterator_const_layer ( void ) {}
+ TreeIterator_const_layer ( const TreeIterator_const_layer& parOther ) : parent_type(parOther) {}
+ template
+ explicit TreeIterator_const_layer ( const TreeIterator_const_layer& parOther ) : parent_type(parOther) {}
+ reference operator* ( void );
+ const_reference operator* ( void ) const;
+ pointer operator-> ( void );
+ const_pointer operator-> ( void ) const;
+ const N* GetPointer ( void ) const;
+ N* GetPointer ( void );
+ protected:
+ typedef typename parent_type::StackType StackType;
+ };
+ } //namespace implem
+
+ template
+ class TreeIterator : public implem::TreeIterator_const_layer::value> {
+ typedef implem::TreeIterator_const_layer::value> parent_type;
+ typedef typename parent_type::NodeTypePointer NodeTypePointer;
+ typedef typename parent_type::NodeType NodeType;
+ typedef typename parent_type::StackType StackType;
+ public:
+ typedef T value_type;
+ typedef std::forward_iterator_tag iterator_category;
+ typedef typename parent_type::difference_type difference_type;
+ typedef typename parent_type::size_type size_type;
+ typedef typename parent_type::pointer pointer;
+ typedef typename parent_type::const_pointer const_pointer;
+ typedef typename parent_type::reference reference;
+ typedef typename parent_type::const_reference const_reference;
+
+ TreeIterator ( void );
+ TreeIterator ( const TreeIterator& parOther );
+ TreeIterator ( NodeTypePointer parRoot, size_type parMaxDepthHint );
+ template
+ TreeIterator ( S parCopyStackBottomUp, size_type parStackLen, size_type parMaxDepthHint );
+ template
+ TreeIterator ( const TreeIterator& parOther );
+ ~TreeIterator ( void );
+
+ const TreeIterator& operator= ( const TreeIterator& parOther );
+ bool operator== ( const TreeIterator& parOther ) const;
+ bool operator!= ( const TreeIterator& parOther ) const { return not (*this == parOther); }
+ TreeIterator& operator++ ( void ); //pre
+ TreeIterator operator++ ( int ); //post
+
+ private:
+ void RecurseLeft ( NodeTypePointer parFrom );
+ };
+} //namespace dhandy
+
+#include "implem/tree_iterator.inl"
+
+#endif
diff --git a/test/unit/meson.build b/test/unit/meson.build
index 7777af0..9fa0534 100644
--- a/test/unit/meson.build
+++ b/test/unit/meson.build
@@ -10,6 +10,8 @@ unit_test_prog = executable(meson.project_name(),
'version_test.cpp',
'tiger_test.cpp',
'infix_iterator.cpp',
+ 'tree_iterator_test.cpp',
+ 'small_object_allocator_test.cpp',
install: false,
dependencies: [sprout_dep, catch2_dep],
include_directories: [public_incl],
diff --git a/test/unit/small_object_allocator_test.cpp b/test/unit/small_object_allocator_test.cpp
new file mode 100644
index 0000000..a7258ab
--- /dev/null
+++ b/test/unit/small_object_allocator_test.cpp
@@ -0,0 +1,75 @@
+/* Copyright 2016-2024 Michele Santullo
+ * This file is part of "duckhandy".
+ *
+ * "duckhandy" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "duckhandy" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "duckhandy". If not, see .
+ */
+
+#include "catch2/catch_test_macros.hpp"
+#include "duckhandy/small_object_allocator.hpp"
+#include
+
+namespace {
+static const void* g_alloc_last{nullptr};
+static std::size_t g_alloc_count{0};
+
+template
+struct TestAlloc {
+ std::unique_ptr operator()() {
+ auto ret = std::make_unique();
+ g_alloc_last = ret.get();
+ ++g_alloc_count;
+ return ret;
+ }
+};
+} //unnamed namespace
+
+TEST_CASE("Check SmallObjectAllocator", "[SmallObjectAllocator][containers][memory][allocators]") {
+ dhandy::SmallObjectAllocator soa;
+ CHECK(g_alloc_last == nullptr);
+ CHECK(g_alloc_count == 0u);
+
+ int* const int01 = soa.allocate(sizeof(int));
+ const void* old_block = g_alloc_last;
+ REQUIRE(int01 != nullptr);
+ CHECK(g_alloc_last != nullptr);
+ CHECK(g_alloc_count == 1);
+
+ int* const int02 = soa.allocate(sizeof(int));
+ REQUIRE(int02 != nullptr);
+ CHECK(int01 != int02);
+ CHECK(g_alloc_last == old_block);
+ CHECK(g_alloc_count == 1);
+
+ int* prev_int = int02;
+ for (std::size_t z = 2; z < dhandy::SmallObjectAllocator::objects_per_block; ++z) {
+ int* const new_int = soa.allocate(sizeof(int));
+ REQUIRE(new_int != nullptr);
+ CHECK(new_int != int01);
+ CHECK(new_int != prev_int);
+ prev_int = new_int;
+ CHECK(g_alloc_last == old_block);
+ CHECK(g_alloc_count == 1);
+ CHECK(reinterpret_cast(new_int) % alignof(int) == 0);
+ }
+
+ int* const int03 = soa.allocate(sizeof(int));
+ REQUIRE(int03 != nullptr);
+ CHECK(int03 != int02);
+ CHECK(int03 != int01);
+ CHECK(g_alloc_last != old_block);
+ old_block = g_alloc_last;
+ CHECK(g_alloc_count == 2);
+
+ soa.deallocate(int02, sizeof(int));
+}
diff --git a/test/unit/tree_iterator_test.cpp b/test/unit/tree_iterator_test.cpp
new file mode 100644
index 0000000..b0533a4
--- /dev/null
+++ b/test/unit/tree_iterator_test.cpp
@@ -0,0 +1,136 @@
+/* Copyright 2016-2024 Michele Santullo
+ * This file is part of "duckhandy".
+ *
+ * "duckhandy" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "duckhandy" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "duckhandy". If not, see .
+ */
+
+#include "catch2/catch_test_macros.hpp"
+#include "duckhandy/tree_iterator.hpp"
+
+namespace {
+
+enum Letters : unsigned int {
+ A = 'a', B, C, D, E, F, G, H, I
+};
+
+struct TestNode {
+ TestNode (unsigned int val) : content(val) {}
+ TestNode* left{nullptr}, *right{nullptr};
+ unsigned int content{0};
+};
+} //unnamed namespace
+
+TEST_CASE("Check TreeIterator", "[TreeIterator][containers][iterator]") {
+ typedef dhandy::TreeIterator TestIterator;
+
+ TestIterator empty;
+
+ {
+ TestNode root{0xDEADBEEFu};
+ TestIterator it{&root, 1};
+ CHECK(*it == 0xDEADBEEFu);
+
+ ++it;
+ CHECK(it == empty);
+ }
+
+ {
+ TestNode root{A};
+ TestNode l{B}, ll{C}, lr{D};
+ TestNode r{E}, rl{F}, rr{G}, rll{H}, rrr{I};
+
+ root.left = &l;
+ root.right = &r;
+ l.left = ≪
+ l.right = &lr;
+ r.left = &rl;
+ r.right = &rr;
+ rl.left = &rll;
+ rr.right = &rrr;
+
+ TestIterator it{&root, 4};
+ CHECK(*it == C); //ll
+ ++it;
+ CHECK(*it == B); //l
+ it++;
+ CHECK(*it == D); //lr
+ it++;
+ CHECK(*it == A); //root
+ ++it;
+ CHECK(*it == H); //rll
+ it++;
+ CHECK(*it == F); //rl
+ it++;
+ CHECK(*it == E); //r
+ ++it;
+ CHECK(*it == G); //rr
+ ++it;
+ CHECK(*it == I); //rrr
+ CHECK(it != empty);
+ it++;
+ CHECK(it == empty);
+ }
+
+ {
+ TestNode root{A};
+ TestNode l{B}, ll{C}, lll{D}, llll{E}, lllll{F};
+ root.left = &l;
+ l.left = ≪
+ ll.left = &lll;
+ lll.left = &llll;
+ llll.left = &lllll;
+
+ TestIterator it{&root, 6};
+ CHECK(it != empty);
+ CHECK(*it == F);
+ ++it;
+ CHECK(*it == E);
+ ++it;
+ CHECK(*it == D);
+ ++it;
+ CHECK(*it == C);
+ ++it;
+ CHECK(*it == B);
+ ++it;
+ CHECK(*it == A);
+ ++it;
+ CHECK(it == empty);
+ }
+
+ {
+ TestNode root{A};
+ TestNode r{B}, rr{C}, rrr{D}, rrrr{E}, rrrrr{F};
+ root.right = &r;
+ r.right = &rr;
+ rr.right = &rrr;
+ rrr.right = &rrrr;
+ rrrr.right = &rrrrr;
+
+ TestIterator it{&root, 6};
+ CHECK(it != empty);
+ CHECK(*it == A);
+ ++it;
+ CHECK(*it == B);
+ ++it;
+ CHECK(*it == C);
+ ++it;
+ CHECK(*it == D);
+ ++it;
+ CHECK(*it == E);
+ ++it;
+ CHECK(*it == F);
+ ++it;
+ CHECK(it == empty);
+ }
+}