diff --git a/include/etl/cyclic_value.h b/include/etl/cyclic_value.h index c33b306b..964ed664 100644 --- a/include/etl/cyclic_value.h +++ b/include/etl/cyclic_value.h @@ -65,7 +65,7 @@ namespace etl /// Default constructor. /// The initial value is set to the first value. //************************************************************************* - cyclic_value() + ETL_CONSTEXPR cyclic_value() : value(First) { } @@ -75,7 +75,7 @@ namespace etl /// Set to an initial value. /// Clamped to the range. //************************************************************************* - explicit cyclic_value(T initial) + ETL_CONSTEXPR14 explicit cyclic_value(T initial) { set(initial); } @@ -83,7 +83,7 @@ namespace etl //************************************************************************* /// Copy constructor. //************************************************************************* - cyclic_value(const cyclic_value& other) + ETL_CONSTEXPR cyclic_value(const cyclic_value& other) : value(other.value) { } @@ -91,7 +91,7 @@ namespace etl //************************************************************************* /// Assignment operator. //************************************************************************* - cyclic_value& operator=(const cyclic_value& other) + ETL_CONSTEXPR14 cyclic_value& operator=(const cyclic_value& other) { value = other.value; @@ -103,7 +103,7 @@ namespace etl /// Truncates to the First/Last range. ///\param value The value. //************************************************************************* - void set(T value_) + ETL_CONSTEXPR14 void set(T value_) { value = etl::clamp(value_, First, Last); } @@ -111,7 +111,7 @@ namespace etl //************************************************************************* /// Resets the value to the first in the range. //************************************************************************* - void to_first() + ETL_CONSTEXPR14 void to_first() { value = First; } @@ -119,7 +119,7 @@ namespace etl //************************************************************************* /// Resets the value to the last in the range. //************************************************************************* - void to_last() + ETL_CONSTEXPR14 void to_last() { value = Last; } @@ -128,7 +128,7 @@ namespace etl /// Advances to value by a number of steps. ///\param n The number of steps to advance. //************************************************************************* - void advance(int n) + ETL_CONSTEXPR14 void advance(int n) { if (n > 0) { @@ -150,7 +150,7 @@ namespace etl /// Conversion operator. /// \return The value of the underlying type. //************************************************************************* - operator T() + ETL_CONSTEXPR14 operator T() { return value; } @@ -159,7 +159,7 @@ namespace etl /// Const conversion operator. /// \return The value of the underlying type. //************************************************************************* - operator const T() const + ETL_CONSTEXPR operator const T() const { return value; } @@ -167,7 +167,7 @@ namespace etl //************************************************************************* /// ++ operator. //************************************************************************* - cyclic_value& operator++() + ETL_CONSTEXPR14 cyclic_value& operator++() { if (value >= Last) ETL_UNLIKELY { @@ -184,7 +184,7 @@ namespace etl //************************************************************************* /// ++ operator. //************************************************************************* - cyclic_value operator++(int) + ETL_CONSTEXPR14 cyclic_value operator++(int) { cyclic_value temp(*this); @@ -196,7 +196,7 @@ namespace etl //************************************************************************* /// -- operator. //************************************************************************* - cyclic_value& operator--() + ETL_CONSTEXPR14 cyclic_value& operator--() { if (value <= First) ETL_UNLIKELY { @@ -213,7 +213,7 @@ namespace etl //************************************************************************* /// -- operator. //************************************************************************* - cyclic_value operator--(int) + ETL_CONSTEXPR14 cyclic_value operator--(int) { cyclic_value temp(*this); @@ -225,7 +225,7 @@ namespace etl //************************************************************************* /// = operator. //************************************************************************* - cyclic_value& operator=(T t) + ETL_CONSTEXPR14 cyclic_value& operator=(T t) { set(t); return *this; @@ -235,7 +235,7 @@ namespace etl /// = operator. //************************************************************************* template - cyclic_value& operator=(const cyclic_value& other) + ETL_CONSTEXPR14 cyclic_value& operator=(const cyclic_value& other) { set(other.get()); return *this; @@ -244,7 +244,7 @@ namespace etl //************************************************************************* /// Gets the value. //************************************************************************* - T get() const + ETL_CONSTEXPR T get() const { return value; } @@ -286,7 +286,7 @@ namespace etl //************************************************************************* /// Operator ==. //************************************************************************* - friend bool operator==(const cyclic_value& lhs, const cyclic_value& rhs) + friend ETL_CONSTEXPR bool operator==(const cyclic_value& lhs, const cyclic_value& rhs) { return lhs.value == rhs.value; } @@ -294,7 +294,7 @@ namespace etl //************************************************************************* /// Operator !=. //************************************************************************* - friend bool operator!=(const cyclic_value& lhs, const cyclic_value& rhs) + friend ETL_CONSTEXPR bool operator!=(const cyclic_value& lhs, const cyclic_value& rhs) { return !(lhs == rhs); } @@ -322,7 +322,7 @@ namespace etl /// Sets 'first' and 'last' to the template parameter values. /// The initial value is set to the first value. //************************************************************************* - cyclic_value() + ETL_CONSTEXPR cyclic_value() : value(First) , first_value(First) , last_value(Last) @@ -335,7 +335,7 @@ namespace etl ///\param first The first value in the range. ///\param last The last value in the range. //************************************************************************* - cyclic_value(T first_, T last_) + ETL_CONSTEXPR cyclic_value(T first_, T last_) : value(first_) , first_value(first_) , last_value(last_) @@ -349,7 +349,7 @@ namespace etl ///\param first The first value in the range. ///\param last The last value in the range. //************************************************************************* - cyclic_value(T first_, T last_, T initial) + ETL_CONSTEXPR14 cyclic_value(T first_, T last_, T initial) : first_value(first_) , last_value(last_) { @@ -359,7 +359,7 @@ namespace etl //************************************************************************* /// Copy constructor. //************************************************************************* - cyclic_value(const cyclic_value& other) + ETL_CONSTEXPR cyclic_value(const cyclic_value& other) : value(other.value) , first_value(other.first_value) , last_value(other.last_value) @@ -372,7 +372,7 @@ namespace etl ///\param first The first value in the range. ///\param last The last value in the range. //************************************************************************* - void set(T first_, T last_) + ETL_CONSTEXPR14 void set(T first_, T last_) { first_value = first_; last_value = last_; @@ -383,7 +383,7 @@ namespace etl /// Sets the value. ///\param value The value. //************************************************************************* - void set(T value_) + ETL_CONSTEXPR14 void set(T value_) { value = etl::clamp(value_, first_value, last_value); } @@ -391,7 +391,7 @@ namespace etl //************************************************************************* /// Resets the value to the first in the range. //************************************************************************* - void to_first() + ETL_CONSTEXPR14 void to_first() { value = first_value; } @@ -399,7 +399,7 @@ namespace etl //************************************************************************* /// Resets the value to the last in the range. //************************************************************************* - void to_last() + ETL_CONSTEXPR14 void to_last() { value = last_value; } @@ -408,7 +408,7 @@ namespace etl /// Advances to value by a number of steps. ///\param n The number of steps to advance. //************************************************************************* - void advance(int n) + ETL_CONSTEXPR14 void advance(int n) { if (n > 0) { @@ -430,7 +430,7 @@ namespace etl /// Conversion operator. /// \return The value of the underlying type. //************************************************************************* - operator T() + ETL_CONSTEXPR14 operator T() { return value; } @@ -439,7 +439,7 @@ namespace etl /// Const conversion operator. /// \return The value of the underlying type. //************************************************************************* - operator const T() const + ETL_CONSTEXPR operator const T() const { return value; } @@ -447,7 +447,7 @@ namespace etl //************************************************************************* /// ++ operator. //************************************************************************* - cyclic_value& operator++() + ETL_CONSTEXPR14 cyclic_value& operator++() { if (value >= last_value) { @@ -464,7 +464,7 @@ namespace etl //************************************************************************* /// ++ operator. //************************************************************************* - cyclic_value operator++(int) + ETL_CONSTEXPR14 cyclic_value operator++(int) { cyclic_value temp(*this); @@ -476,7 +476,7 @@ namespace etl //************************************************************************* /// -- operator. //************************************************************************* - cyclic_value& operator--() + ETL_CONSTEXPR14 cyclic_value& operator--() { if (value <= first_value) { @@ -493,7 +493,7 @@ namespace etl //************************************************************************* /// -- operator. //************************************************************************* - cyclic_value operator--(int) + ETL_CONSTEXPR14 cyclic_value operator--(int) { cyclic_value temp(*this); @@ -505,7 +505,7 @@ namespace etl //************************************************************************* /// = operator. //************************************************************************* - cyclic_value& operator=(T t) + ETL_CONSTEXPR14 cyclic_value& operator=(T t) { set(t); return *this; @@ -514,7 +514,7 @@ namespace etl //************************************************************************* /// = operator. //************************************************************************* - cyclic_value& operator=(const cyclic_value& other) + ETL_CONSTEXPR14 cyclic_value& operator=(const cyclic_value& other) { value = other.value; first_value = other.first_value; @@ -525,7 +525,7 @@ namespace etl //************************************************************************* /// Gets the value. //************************************************************************* - T get() const + ETL_CONSTEXPR T get() const { return value; } @@ -533,7 +533,7 @@ namespace etl //************************************************************************* /// Gets the first value. //************************************************************************* - T first() const + ETL_CONSTEXPR T first() const { return first_value; } @@ -541,7 +541,7 @@ namespace etl //************************************************************************* /// Gets the last value. //************************************************************************* - T last() const + ETL_CONSTEXPR T last() const { return last_value; } @@ -569,7 +569,7 @@ namespace etl //************************************************************************* /// Operator ==. //************************************************************************* - friend bool operator==(const cyclic_value& lhs, const cyclic_value& rhs) + friend ETL_CONSTEXPR bool operator==(const cyclic_value& lhs, const cyclic_value& rhs) { return (lhs.value == rhs.value) && (lhs.first_value == rhs.first_value) && (lhs.last_value == rhs.last_value); } @@ -577,7 +577,7 @@ namespace etl //************************************************************************* /// Operator !=. //************************************************************************* - friend bool operator!=(const cyclic_value& lhs, const cyclic_value& rhs) + friend ETL_CONSTEXPR bool operator!=(const cyclic_value& lhs, const cyclic_value& rhs) { return !(lhs == rhs); } diff --git a/include/etl/file_error_numbers.h b/include/etl/file_error_numbers.h index 0953b26f..945f1d87 100644 --- a/include/etl/file_error_numbers.h +++ b/include/etl/file_error_numbers.h @@ -111,4 +111,5 @@ SOFTWARE. #define ETL_SIGNAL_FILE_ID "78" #define ETL_FORMAT_FILE_ID "79" #define ETL_INPLACE_FUNCTION_FILE_ID "80" +#define ETL_INTRUSIVE_AVL_TREE_FILE_ID "81" #endif diff --git a/include/etl/fixed_iterator.h b/include/etl/fixed_iterator.h index 5d343ec3..fb274b6c 100644 --- a/include/etl/fixed_iterator.h +++ b/include/etl/fixed_iterator.h @@ -52,7 +52,7 @@ namespace etl //*************************************************************************** /// Default constructor. //*************************************************************************** - fixed_iterator() + ETL_CONSTEXPR fixed_iterator() : it(TIterator()) { } @@ -60,7 +60,7 @@ namespace etl //*************************************************************************** /// Construct from iterator. //*************************************************************************** - fixed_iterator(TIterator it_) + ETL_CONSTEXPR fixed_iterator(TIterator it_) : it(it_) { } @@ -68,7 +68,7 @@ namespace etl //*************************************************************************** /// Copy constructor //*************************************************************************** - fixed_iterator(const fixed_iterator& other) + ETL_CONSTEXPR fixed_iterator(const fixed_iterator& other) : it(other.it) { } @@ -76,7 +76,7 @@ namespace etl //*************************************************************************** /// Increment (Does nothing). //*************************************************************************** - fixed_iterator& operator++() + ETL_CONSTEXPR14 fixed_iterator& operator++() { return *this; } @@ -84,7 +84,7 @@ namespace etl //*************************************************************************** /// Increment (Does nothing). //*************************************************************************** - fixed_iterator operator++(int) + ETL_CONSTEXPR14 fixed_iterator operator++(int) { return *this; } @@ -92,7 +92,7 @@ namespace etl //*************************************************************************** /// Decrement (Does nothing). //*************************************************************************** - fixed_iterator& operator--() + ETL_CONSTEXPR14 fixed_iterator& operator--() { return *this; } @@ -100,7 +100,7 @@ namespace etl //*************************************************************************** /// Decrement (Does nothing). //*************************************************************************** - fixed_iterator operator--(int) + ETL_CONSTEXPR14 fixed_iterator operator--(int) { return *this; } @@ -108,7 +108,7 @@ namespace etl //*************************************************************************** /// Dereference operator. //*************************************************************************** - typename etl::iterator_traits::value_type operator*() + ETL_CONSTEXPR14 typename etl::iterator_traits::reference operator*() { return *it; } @@ -116,7 +116,7 @@ namespace etl //*************************************************************************** /// Dereference operator. //*************************************************************************** - const typename etl::iterator_traits::value_type operator*() const + ETL_CONSTEXPR typename etl::iterator_traits::reference operator*() const { return *it; } @@ -124,7 +124,7 @@ namespace etl //*************************************************************************** /// -> operator. //*************************************************************************** - TIterator operator->() + ETL_CONSTEXPR14 TIterator operator->() { return it; } @@ -132,7 +132,7 @@ namespace etl //*************************************************************************** /// -> operator. //*************************************************************************** - const TIterator operator->() const + ETL_CONSTEXPR const TIterator operator->() const { return it; } @@ -140,7 +140,7 @@ namespace etl //*************************************************************************** /// Conversion operator. //*************************************************************************** - operator TIterator() const + ETL_CONSTEXPR operator TIterator() const { return it; } @@ -148,7 +148,7 @@ namespace etl //*************************************************************************** /// += operator. //*************************************************************************** - fixed_iterator& operator+=(typename etl::iterator_traits::difference_type /*offset*/) + ETL_CONSTEXPR14 fixed_iterator& operator+=(typename etl::iterator_traits::difference_type /*offset*/) { return *this; } @@ -156,7 +156,7 @@ namespace etl //*************************************************************************** /// -= operator. //*************************************************************************** - fixed_iterator& operator-=(typename etl::iterator_traits::difference_type /*offset*/) + ETL_CONSTEXPR14 fixed_iterator& operator-=(typename etl::iterator_traits::difference_type /*offset*/) { return *this; } @@ -164,7 +164,7 @@ namespace etl //*************************************************************************** /// Assignment from iterator. //*************************************************************************** - fixed_iterator& operator=(TIterator new_it) + ETL_CONSTEXPR14 fixed_iterator& operator=(TIterator new_it) { it = new_it; return *this; @@ -173,7 +173,7 @@ namespace etl //*************************************************************************** /// Assignment from fixed_iterator. //*************************************************************************** - fixed_iterator& operator=(fixed_iterator other) + ETL_CONSTEXPR14 fixed_iterator& operator=(fixed_iterator other) { it = other.it; return *this; @@ -188,7 +188,8 @@ namespace etl /// + difference operator. //***************************************************************************** template - etl::fixed_iterator& operator+(etl::fixed_iterator& lhs, typename etl::iterator_traits::difference_type /*rhs*/) + ETL_CONSTEXPR14 etl::fixed_iterator& operator+(etl::fixed_iterator& lhs, + typename etl::iterator_traits::difference_type /*rhs*/) { return lhs; } @@ -197,7 +198,8 @@ namespace etl /// - difference operator. //***************************************************************************** template - etl::fixed_iterator& operator-(etl::fixed_iterator& lhs, typename etl::iterator_traits::difference_type /*rhs*/) + ETL_CONSTEXPR14 etl::fixed_iterator& operator-(etl::fixed_iterator& lhs, + typename etl::iterator_traits::difference_type /*rhs*/) { return lhs; } @@ -206,8 +208,8 @@ namespace etl /// - fixed_iterator operator. //***************************************************************************** template - typename etl::iterator_traits::difference_type operator-(const etl::fixed_iterator& lhs, - const etl::fixed_iterator& rhs) + ETL_CONSTEXPR typename etl::iterator_traits::difference_type operator-(const etl::fixed_iterator& lhs, + const etl::fixed_iterator& rhs) { return TIterator(lhs) - TIterator(rhs); } @@ -216,7 +218,7 @@ namespace etl /// Equality operator. fixed_iterator == fixed_iterator. //***************************************************************************** template - bool operator==(const etl::fixed_iterator& lhs, const etl::fixed_iterator& rhs) + ETL_CONSTEXPR bool operator==(const etl::fixed_iterator& lhs, const etl::fixed_iterator& rhs) { return TIterator(lhs) == TIterator(rhs); } @@ -225,7 +227,7 @@ namespace etl /// Equality operator. fixed_iterator == iterator. //***************************************************************************** template - bool operator==(const etl::fixed_iterator& lhs, TIterator rhs) + ETL_CONSTEXPR bool operator==(const etl::fixed_iterator& lhs, TIterator rhs) { return TIterator(lhs) == rhs; } @@ -234,7 +236,7 @@ namespace etl /// Equality operator. iterator == fixed_iterator. //***************************************************************************** template - bool operator==(TIterator lhs, const etl::fixed_iterator& rhs) + ETL_CONSTEXPR bool operator==(TIterator lhs, const etl::fixed_iterator& rhs) { return lhs == TIterator(rhs); } @@ -243,7 +245,7 @@ namespace etl /// Inequality operator. fixed_iterator == fixed_iterator. //***************************************************************************** template - bool operator!=(const etl::fixed_iterator& lhs, const etl::fixed_iterator& rhs) + ETL_CONSTEXPR bool operator!=(const etl::fixed_iterator& lhs, const etl::fixed_iterator& rhs) { return !(lhs == rhs); } @@ -252,7 +254,7 @@ namespace etl /// Inequality operator. fixed_iterator == iterator. //***************************************************************************** template - bool operator!=(const etl::fixed_iterator& lhs, TIterator rhs) + ETL_CONSTEXPR bool operator!=(const etl::fixed_iterator& lhs, TIterator rhs) { return !(lhs == rhs); } @@ -261,7 +263,7 @@ namespace etl /// Inequality operator. iterator == fixed_iterator. //***************************************************************************** template - bool operator!=(TIterator& lhs, const etl::fixed_iterator& rhs) + ETL_CONSTEXPR bool operator!=(TIterator lhs, const etl::fixed_iterator& rhs) { return !(lhs == rhs); } diff --git a/include/etl/functional.h b/include/etl/functional.h index 37bbd175..e23d15eb 100644 --- a/include/etl/functional.h +++ b/include/etl/functional.h @@ -363,25 +363,25 @@ namespace etl public: - binder1st(const TFunction& f, const typename TFunction::first_argument_type& v) + ETL_CONSTEXPR binder1st(const TFunction& f, const typename TFunction::first_argument_type& v) : operation(f) , value(v) { } - typename TFunction::result_type operator()(typename TFunction::second_argument_type& x) const + ETL_CONSTEXPR typename TFunction::result_type operator()(typename TFunction::second_argument_type& x) const { return operation(value, x); } - typename TFunction::result_type operator()(const typename TFunction::second_argument_type& x) const + ETL_CONSTEXPR typename TFunction::result_type operator()(const typename TFunction::second_argument_type& x) const { return operation(value, x); } }; template - binder1st bind1st(const F& f, const T& x) + ETL_CONSTEXPR binder1st bind1st(const F& f, const T& x) { return binder1st(f, x); } @@ -397,25 +397,25 @@ namespace etl public: - binder2nd(const TFunction& f, const typename TFunction::second_argument_type& v) + ETL_CONSTEXPR binder2nd(const TFunction& f, const typename TFunction::second_argument_type& v) : operation(f) , value(v) { } - typename TFunction::result_type operator()(typename TFunction::first_argument_type& x) const + ETL_CONSTEXPR typename TFunction::result_type operator()(typename TFunction::first_argument_type& x) const { return operation(x, value); } - typename TFunction::result_type operator()(const typename TFunction::first_argument_type& x) const + ETL_CONSTEXPR typename TFunction::result_type operator()(const typename TFunction::first_argument_type& x) const { return operation(x, value); } }; template - binder2nd bind2nd(const F& f, const T& x) + ETL_CONSTEXPR binder2nd bind2nd(const F& f, const T& x) { return binder2nd(f, x); } diff --git a/include/etl/intrusive_avl_tree.h b/include/etl/intrusive_avl_tree.h new file mode 100644 index 00000000..ff92935c --- /dev/null +++ b/include/etl/intrusive_avl_tree.h @@ -0,0 +1,2011 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Sergei Shirokov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_INTRUSIVE_AVL_TREE_INCLUDED +#define ETL_INTRUSIVE_AVL_TREE_INCLUDED + +#include "platform.h" +#include "error_handler.h" +#include "intrusive_links.h" +#include "iterator.h" +#include "memory.h" +#include "type_traits.h" +#include "utility.h" + +#include + +namespace etl +{ + //*************************************************************************** + /// Exception for the intrusive_avl_tree. + /// \ingroup intrusive_avl_tree + //*************************************************************************** + class intrusive_avl_tree_exception : public exception + { + public: + + intrusive_avl_tree_exception(const string_type reason_, const string_type file_name_, const numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Iterator exception for the intrusive_avl_tree. + /// \ingroup intrusive_avl_tree + //*************************************************************************** + class intrusive_avl_tree_iterator_exception : public intrusive_avl_tree_exception + { + public: + + intrusive_avl_tree_iterator_exception(const string_type file_name_, const numeric_type line_number_) + : intrusive_avl_tree_exception(ETL_ERROR_TEXT("intrusive_avl_tree:iterator", ETL_INTRUSIVE_AVL_TREE_FILE_ID "A"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Already-linked exception for the intrusive_avl_tree. + ///\ingroup intrusive_avl_tree + //*************************************************************************** + class intrusive_avl_tree_value_is_already_linked : public intrusive_avl_tree_exception + { + public: + + intrusive_avl_tree_value_is_already_linked(const string_type file_name_, const numeric_type line_number_) + : intrusive_avl_tree_exception(ETL_ERROR_TEXT("intrusive_avl_tree:value is already linked", ETL_INTRUSIVE_AVL_TREE_FILE_ID"B"), file_name_, + line_number_) + { + } + }; + + //*************************************************************************** + /// \ingroup intrusive_avl_tree + /// Base for intrusive AVL tree. Stores elements derived from 'intrusive_avl_tree_base::link_type'. + /// \tparam ID_ The link ID that the value is derived from. + //*************************************************************************** + template + class intrusive_avl_tree_base + { + public: + + enum + { + ID = ID_, + }; + +#if ETL_USING_CPP11 + intrusive_avl_tree_base(const intrusive_avl_tree_base&) = delete; + intrusive_avl_tree_base& operator=(const intrusive_avl_tree_base&) = delete; +#endif + + /// Base for elements of this AVL tree. + /// It's expected that a tree node type is inherited from this base. + /// + /// Almost nothing is exposed from this type as public (or even protected) API - + /// this is done deliberately and by design, so that: + /// - impose as little "intrusive"-ness as possible when you inherit your nodes from this base. + /// - don't worry about possible name conflicts + /// - hide implementation details + struct link_type : private etl::tree_link + { + private: + + typedef etl::tree_link base; + + public: + + link_type() ETL_NOEXCEPT + : base() + , etl_size(0) + { + } + +#if ETL_USING_CPP11 + //*************************************************************************** + /// Constructs a new item by moving `other` item into `this` one. + /// Complexity: O(1) + /// After construction `this` item will replace `other` item + /// in the same tree position as the `other` was, so no tree balancing is needed. + /// The `other` item becomes unlinked. + /// + /// Note also that move construction/assignment of the `intrusive_avl_tree` class itself + /// is heavily based on this move constructor for its `origin` sentinel item. + //*************************************************************************** + link_type(link_type&& other) ETL_NOEXCEPT + : base() + , etl_size(0) + { + move_impl(other); + } + + //*************************************************************************** + /// Assigns `other` item by moving it into `this` one. + /// Does nothing in case of self-assignment (when `this == &other`). + /// Complexity: + /// - O(log(N)) if `this` item is already linked to a tree, and so + /// the item has to be erased from the tree (with rebalancing). + /// - O(1) if `this` item is not linked to any tree. + /// Might rotate the original tree of `this` item as necessary + /// to keep the tree balanced after erasing the item. + /// After assignment `this` item will replace `other` item + /// in the same tree position as the `other` was, so no tree balancing is needed. + /// The `other` item becomes unlinked. + /// Note that after assignment `this` item might change its tree. + //*************************************************************************** + link_type& operator=(link_type&& other) ETL_NOEXCEPT + { + if (this != &other) + { + // Before move assigning `other` we have to + // make sure this item is not linked to any tree. + erase_impl(this); + move_impl(other); + } + return *this; + } +#endif + +#if ETL_USING_CPP11 + // Disable copy construction and assignment. + link_type(const link_type&) = delete; + link_type& operator=(const link_type&) = delete; +#endif + + //*************************************************************************** + /// Destructor of a tree link. + /// Complexity: O(log(N)). + /// Might rotate the tree as necessary to keep it balanced. + /// NB! The tree itself is not the real owner (of memory) of its nodes (and their inherited links). + /// The real owner (aka the user) of a node could destroy it as he/she wishes, + /// with or without prior explicit erasure from its tree. So, if the node link happens + /// to be still linked to a tree during its destruction, then we have to unlink it - + /// otherwise the node's parent and its former children will keep dangling pointers to the node, + /// which will essentially break the tree consistency and lead to UB. + //*************************************************************************** + ~link_type() + { + // We can't just remove item (by simple rewiring of links at parent and children) + // b/c its former "owner" tree has to be rebalanced after the removal - + // hence the full-blown erasing which might rotate the tree as necessary. + erase_impl(this); + } + + private: + + friend class intrusive_avl_tree_base; + + union + { + int_fast8_t etl_bf; ///< Stores -1, 0, or +1 balancing factor in the real nodes. + size_t etl_size; ///< Stores total number of items in the tree (origin node only). + }; + +#if ETL_USING_CPP11 +#else + // Disable copy construction and assignment. + link_type(const link_type&); + link_type& operator=(const link_type&); +#endif + + ETL_NODISCARD + bool is_linked() const + { + return base::is_linked(); + } + + ETL_NODISCARD + bool is_origin() const + { + return ETL_NULLPTR == base::etl_parent; + } + + ETL_NODISCARD + link_type* get_parent() + { + return static_cast(base::etl_parent); + } + + ETL_NODISCARD + const link_type* get_parent() const + { + return static_cast(base::etl_parent); + } + + ETL_NODISCARD + bool is_child(const bool is_right) const + { + const link_type* parent = get_parent(); + return (ETL_NULLPTR != parent) && (this == parent->get_child(is_right)); + } + + ETL_NODISCARD + bool is_left_child() const + { + return is_child(false); + } + + ETL_NODISCARD + bool is_right_child() const + { + return is_child(true); + } + + ETL_NODISCARD + link_type* get_child(const bool is_right) + { + return static_cast(is_right ? base::etl_right : base::etl_left); + } + + ETL_NODISCARD + const link_type* get_child(const bool is_right) const + { + return static_cast(is_right ? base::etl_right : base::etl_left); + } + + void set_child(link_type* const child, const bool is_right) + { + base*& child_ref = is_right ? base::etl_right : base::etl_left; + child_ref = child; + } + + ETL_NODISCARD + link_type* get_left() + { + return static_cast(base::etl_left); + } + + ETL_NODISCARD + const link_type* get_left() const + { + return static_cast(base::etl_left); + } + + ETL_NODISCARD + link_type* get_right() + { + return static_cast(base::etl_right); + } + + ETL_NODISCARD + const link_type* get_right() const + { + return static_cast(base::etl_right); + } + + void move_impl(link_type& other) + { + const bool is_right = other.is_right_child(); + base::etl_parent = other.etl_parent; + base::etl_left = other.etl_left; + base::etl_right = other.etl_right; + etl_size = other.etl_size; + + other.clear(); + other.etl_size = 0; + + if (ETL_NULLPTR != base::etl_parent) + { + get_parent()->link_child(this, is_right); + } + if (ETL_NULLPTR != base::etl_left) + { + get_left()->set_parent(this); + } + if (ETL_NULLPTR != base::etl_right) + { + get_right()->set_parent(this); + } + } + + void rotate(const bool is_right) + { + const bool was_right = is_right_child(); + link_type* const child = get_child(!is_right); + etl::link_rotate(this, child); + if (link_type* const parent = child->get_parent()) + { + parent->set_child(child, was_right); + } + } + + ETL_NODISCARD + link_type* adjust_balance(const bool increase) + { + const int_fast8_t new_bf = etl_bf + (increase ? +1 : -1); + if ((-1 <= new_bf) && (new_bf <= +1)) + { + etl_bf = new_bf; + return this; + } + + const bool is_right_rotation = new_bf < 0; + const int_fast8_t sign = is_right_rotation ? +1 : -1; + link_type* const z_leaf = get_child(!is_right_rotation); + if (z_leaf->etl_bf * sign <= 0) + { + rotate(is_right_rotation); + if (z_leaf->etl_bf == 0) + { + etl_bf = -sign; + z_leaf->etl_bf = +sign; + } + else + { + etl_bf = 0; + z_leaf->etl_bf = 0; + } + return z_leaf; + } + + link_type* const y_leaf = z_leaf->get_child(is_right_rotation); + z_leaf->rotate(!is_right_rotation); + rotate(is_right_rotation); + if (y_leaf->etl_bf == 0) + { + etl_bf = 0; + z_leaf->etl_bf = 0; + } + else if (y_leaf->etl_bf == sign) + { + etl_bf = 0; + y_leaf->etl_bf = 0; + z_leaf->etl_bf = -sign; + } + else + { + etl_bf = +sign; + y_leaf->etl_bf = 0; + z_leaf->etl_bf = 0; + } + return y_leaf; + } + + void link_child(link_type* child, const bool is_right) + { + if (is_right) + { + etl::link_right(this, child); + } + else + { + etl::link_left(this, child); + } + } + + }; // link_type + + //************************************************************************* + /// Checks if the tree is in the empty state. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + bool empty() const ETL_NOEXCEPT + { + return ETL_NULLPTR == get_root(); + } + + //************************************************************************* + /// Returns the number of elements. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + size_t size() const ETL_NOEXCEPT + { + return get_origin().etl_size; + } + + //************************************************************************* + /// Unlinks all current items, leaving this tree in the empty state. + /// Complexity: O(N). + /// Operation invalidates all existing iterators. + /// + /// Note that the same "clear all" effect could be achieved by using the `erase` + /// method for each item, but b/c of intermediate tree rebalancing + /// complexity will be higher - O(N*log(N)). + //************************************************************************* + void clear() ETL_NOEXCEPT + { +#if ETL_USING_CPP11 + auto unlinker = [](link_type& link) + { + link.clear(); + link.etl_size = 0; + }; +#else + struct + { + void operator()(link_type& link) const + { + link.clear(); + link.etl_size = 0; + } + } unlinker; +#endif + + // No need to balance b/c everything will be unlinked. + // Note that "post-order" visitation is important - + // it ensures that once a link is passed to the "visitor" functor, + // traversal won't use a pointer to this link anymore, + // so we could efficiently clear the link. + visit_post_order_impl(&origin, false, unlinker); + origin.set_left(ETL_NULLPTR); + origin.etl_size = 0; + } + + //******************************************* + /// Swap with another tree. + /// Complexity: O(1). + /// Does nothing in case of self-swap (when `this == &other`). + //******************************************* + void swap(intrusive_avl_tree_base& other) ETL_NOEXCEPT + { + if (this != &other) + { + ETL_OR_STD::swap(origin, other.origin); + } + } + + //************************************************************************* + /// Swaps the trees. + /// Complexity: O(1). + /// Does nothing in case of self-swap (when `&lhs == &rhs`). + //************************************************************************* + friend void swap(intrusive_avl_tree_base& lhs, intrusive_avl_tree_base& rhs) ETL_NOEXCEPT + { + if (&lhs != &rhs) + { + lhs.swap(rhs); + } + } + + protected: + + //************************************************************************* + /// Default constructor. + //************************************************************************* + intrusive_avl_tree_base() ETL_NOEXCEPT {} + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move constructor. + /// Complexity: O(1). + /// NB! Proper `= default` move behavior is actually based on the move + /// construction of the `origin` link (see `link_type(link_type&& other)`). + //************************************************************************* + intrusive_avl_tree_base(intrusive_avl_tree_base&&) ETL_NOEXCEPT = default; + + //************************************************************************* + /// Move assignment. + /// Does nothing in case of self-assignment (when `this == &other`). + /// Complexity: O(N), where N is the size of `this` tree before assignment - + /// all former items have to be unlinked. + //************************************************************************* + intrusive_avl_tree_base& operator=(intrusive_avl_tree_base&& other) ETL_NOEXCEPT + { + if (this != &other) + { + intrusive_avl_tree_base tmp(etl::move(other)); + swap(tmp); + } + return *this; + } +#endif + + //************************************************************************* + /// Destructor of a tree. + /// Complexity: O(N). + /// All existing nodes will stay alive + /// but will be completely unlinked from the tree (and from each other). + /// If you want more control on what happens with still linked tree nodes, then + /// enumerate and unlink (aka "erase") them first (potentially moving to somewhere else). + /// If needed, the whole content of the tree could be O(1) effectively moved + /// to another tree, leaving this tree as empty - use C++11 move constructor of the tree. + //*************************************************************************** + ~intrusive_avl_tree_base() + { + // It's important to clear, so that none of the former (but still alive) items + // stay linked to this tree (neither directly at the root item + // nor transitively via "parent" links from leaf items up to the origin). + clear(); + } + + ETL_NODISCARD + link_type* get_root() ETL_NOEXCEPT + { + return static_cast(origin.etl_left); + } + + ETL_NODISCARD + const link_type* get_root() const ETL_NOEXCEPT + { + return static_cast(origin.etl_left); + } + + ETL_NODISCARD + link_type& get_origin() ETL_NOEXCEPT + { + return origin; + } + + const link_type& get_origin() const ETL_NOEXCEPT + { + return origin; + } + + template + ETL_NODISCARD + static TLink* get_origin(TLink* link) ETL_NOEXCEPT + { + while ((ETL_NULLPTR != link) && !link->is_origin()) + { + link = link->get_parent(); + } + return link; + } + + ETL_NODISCARD + static bool is_real_link(const link_type* link) ETL_NOEXCEPT + { + return (ETL_NULLPTR != link) && !link->is_origin(); + } + + template + ETL_NODISCARD + static TLink* begin_impl(TLink& origin) ETL_NOEXCEPT + { + TLink* curr = &origin; + TLink* next = curr->get_child(false); + while (ETL_NULLPTR != next) + { + curr = next; + next = next->get_child(false); + } + return curr; + } + + template + ETL_NODISCARD + static TLink* end_impl(TLink& origin) ETL_NOEXCEPT + { + return &origin; + } + + template + ETL_NODISCARD + static TLink* find_extremum_impl(TLink* curr, const bool is_max) ETL_NOEXCEPT + { + if (ETL_NULLPTR != curr) + { + TLink* next = curr->get_child(is_max); + while (ETL_NULLPTR != next) + { + curr = next; + next = curr->get_child(is_max); + } + } + return curr; + } + + template + ETL_NODISCARD + static TLink* next_in_order_impl(TLink* curr) ETL_NOEXCEPT + { + if ((ETL_NULLPTR == curr) || curr->is_origin()) + { + return curr; + } + + if (TLink* const next = curr->get_child(true)) + { + return find_extremum_impl(next, false); + } + + while (curr->is_right_child()) + { + curr = curr->get_parent(); + } + return curr->get_parent(); + } + + template + ETL_NODISCARD + static TLink* prev_in_order_impl(TLink* curr) ETL_NOEXCEPT + { + if (ETL_NULLPTR == curr) + { + return ETL_NULLPTR; + } + + if (TLink* const next = curr->get_child(false)) + { + return find_extremum_impl(next, true); + } + + while (curr->is_left_child()) + { + curr = curr->get_parent(); + } + return curr->is_origin() ? curr : curr->get_parent(); + } + + template + static void visit_in_order_impl(TLink* curr, const bool is_reverse, Visitor visitor) + { + TLink* prev = ETL_NULLPTR; + while (ETL_NULLPTR != curr) + { + TLink* next = curr->get_parent(); + if (prev == next) + { + if (TLink* const child1 = curr->get_child(is_reverse)) + { + next = child1; + } + else + { + if (!curr->is_origin()) + { + visitor(*curr); + } + + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + } + else if (prev == curr->get_child(is_reverse)) + { + if (!curr->is_origin()) + { + visitor(*curr); + } + + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + + prev = etl::exchange(curr, next); + } + } + + template + static void visit_post_order_impl(TLink* curr, const bool is_reverse, Visitor visitor) + { + TLink* prev = ETL_NULLPTR; + while (ETL_NULLPTR != curr) + { + TLink* next = curr->get_parent(); + if (prev == next) + { + if (TLink* const child1 = curr->get_child(is_reverse)) + { + next = child1; + } + else if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + else if (!curr->is_origin()) + { + visitor(*curr); + } + } + else if (prev == curr->get_child(is_reverse)) + { + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + else if (!curr->is_origin()) + { + visitor(*curr); + } + } + else if (!curr->is_origin()) + { + visitor(*curr); + } + + prev = etl::exchange(curr, next); + } + } + + template + static void visit_pre_order_impl(TLink* curr, const bool is_reverse, Visitor visitor) + { + TLink* prev = ETL_NULLPTR; + while (ETL_NULLPTR != curr) + { + TLink* next = curr->get_parent(); + if (prev == next) + { + if (!curr->is_origin()) + { + visitor(*curr); + } + + if (TLink* const child1 = curr->get_child(is_reverse)) + { + next = child1; + } + else if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + else if (prev == curr->get_child(is_reverse)) + { + if (TLink* const child2 = curr->get_child(!is_reverse)) + { + next = child2; + } + } + + prev = etl::exchange(curr, next); + } + } + + ETL_NODISCARD + static int_fast8_t get_balance_factor_impl(const link_type* const curr) ETL_NOEXCEPT + { + return (ETL_NULLPTR != curr) ? curr->etl_bf : 0; + } + + template + ETL_NODISCARD + static TLink* get_parent_impl(TLink* const curr) ETL_NOEXCEPT + { + if (ETL_NULLPTR == curr) + { + return ETL_NULLPTR; + } + TLink* const parent = curr->get_parent(); + if ((ETL_NULLPTR == parent) || parent->is_origin()) + { + return ETL_NULLPTR; + } + return parent; + } + + template + ETL_NODISCARD + static TLink* get_child_impl(TLink* const curr, const bool is_right) ETL_NOEXCEPT + { + if (ETL_NULLPTR == curr) + { + return ETL_NULLPTR; + } + return curr->get_child(is_right); + } + + template + void assign_impl(TIterator first, TIterator last, TBinaryCompare binary_comp) + { +#if ETL_IS_DEBUG_BUILD + const intmax_t diff = etl::distance(first, last); + ETL_ASSERT(diff >= 0, ETL_ERROR(intrusive_avl_tree_iterator_exception)); +#endif + + // Add all the elements. + while (first != last) + { + link_type& link = *first++; + TValue& value = static_cast(link); +#if ETL_USING_CPP11 + find_or_insert_impl( // + [&value, &binary_comp](const TValue& other) { return binary_comp(value, other); }, // + [&value] { return &value; }); +#else + const CompareFactory compareFactory(value, binary_comp); + find_or_insert_impl(compareFactory, compareFactory); +#endif + } + } + + template + static TValue* find_impl(TLink* const root, TCompare comp) + { + // Try to find existing node. + TLink* curr = root; + while (ETL_NULLPTR != curr) + { + TValue* const result = static_cast(curr); + const int cmp = comp(*result); + if (0 == cmp) + { + // Found! + return result; + } + + curr = curr->get_child(cmp > 0); + } + + // Not found. + return ETL_NULLPTR; + } + + template + etl::pair find_or_insert_impl(TCompare comp, TFactory factory) + { + // Try to find existing node. + bool is_right = false; + link_type* curr = get_root(); + link_type* parent = &origin; + while (ETL_NULLPTR != curr) + { + TValue* const result = static_cast(curr); + const int cmp = comp(*result); + if (0 == cmp) + { + // Found! Tree was not modified. + return etl::make_pair(result, false); + } + + parent = curr; + is_right = cmp > 0; + curr = curr->get_child(is_right); + } + + // Try to instantiate new node. + TValue* const result = factory(); + if (ETL_NULLPTR == result) + { + // Failed (or rejected)! The tree was not modified. + return etl::make_pair(ETL_NULLPTR, false); + } + + link_type& result_link = static_cast(*result); + ETL_ASSERT(!(result_link.is_linked()), ETL_ERROR(intrusive_avl_tree_value_is_already_linked)); + + // Link the new node. + parent->link_child(&result_link, is_right); + get_origin(parent)->etl_size += 1; + + retrace_on_insert(&result_link); + + // Successfully linked, so the tree was modified. + return etl::make_pair(result, true); + } + + template + static TValue* find_bound_impl(TLink* const root, TCompare comp) + { + TValue* result = ETL_NULLPTR; + TLink* next = root; + while (ETL_NULLPTR != next) + { + TValue* const next_value = static_cast(next); + const int cmp = comp(*next_value); + if ((cmp > 0) || (IsUpper && (cmp == 0))) + { + next = next->get_right(); + } + else + { + result = next_value; + next = next->get_left(); + } + } + return result; + } + + static void erase_impl(link_type* const z_link) ETL_NOEXCEPT + { + // Remove only real and still-linked items. + // Tree itself uses this `link_type` for its `origin` sentinel item, + // but you can't (and there is no need to) erase it. + if ((ETL_NULLPTR == z_link) || z_link->is_origin() || !z_link->is_linked()) + { + return; + } + + link_type* parent = z_link->get_parent(); + bool is_right = z_link->is_right_child(); + + if (!z_link->has_left()) + { + parent->link_child(z_link->get_right(), is_right); + } + else if (!z_link->has_right()) + { + parent->link_child(z_link->get_left(), is_right); + } + else + { + link_type* const y_link = next_in_order_impl(z_link); + link_type* const y_link_parent = y_link->get_parent(); + y_link->etl_bf = z_link->etl_bf; + if (z_link != y_link_parent) + { + y_link_parent->link_child(y_link->get_right(), y_link->is_right_child()); + link_type* const z_right = z_link->get_right(); + y_link->set_right(z_right); + z_right->set_parent(y_link); + parent->link_child(y_link, is_right); + + is_right = false; + parent = y_link_parent; + } + else + { + parent->link_child(y_link, is_right); + + is_right = true; + parent = y_link; + } + link_type* const z_left = z_link->get_left(); + y_link->set_left(z_left); + z_left->set_parent(y_link); + } + + z_link->clear(); + z_link->etl_size = 0; + get_origin(parent)->etl_size -= 1; + + retrace_on_erase(parent, is_right); + } + + private: + + /// This is the origin link whose left child points to the root link (if any) of the tree. + /// This link instance is an internal implementation detail, and it's almost never exposed to the public + /// API of the tree - the only exception is its natural usage for the "end" terminal iterator. + /// So, the end iterator contains the address of this sentinel node (rather than a non-null pointer). + link_type origin; + +#if ETL_USING_CPP11 +#else + // Disable copy construction and assignment. + intrusive_avl_tree_base(const intrusive_avl_tree_base&); + intrusive_avl_tree_base& operator=(const intrusive_avl_tree_base&); + + template + struct CompareFactory + { + TValue& value_ref; + TBinaryCompare binary_comp; + + CompareFactory(TValue& value, TBinaryCompare comp) + : value_ref(value) + , binary_comp(comp) + { + } + + /// Adapts binary comparator to a "unary" one. + ETL_NODISCARD + int operator()(const TValue& other) const + { + return binary_comp(value_ref, other); + } + + /// Fake "factory" that returns address of existing value. + ETL_NODISCARD + TValue* operator()() const + { + return &value_ref; + } + + }; // CompareFactory +#endif + + static void retrace_on_insert(link_type* curr) + { + link_type* parent = curr->get_parent(); + while (!parent->is_origin()) + { + const bool is_right = curr->is_right_child(); + curr = parent->adjust_balance(is_right); + parent = curr->get_parent(); + if (curr->etl_bf == 0) + { + break; + } + } + + if (parent->is_origin()) + { + parent->set_left(curr); + } + } + + static void retrace_on_erase(link_type* parent, bool is_right) + { + while (!parent->is_origin()) + { + link_type* const curr = parent->adjust_balance(!is_right); + parent = curr->get_parent(); + if ((curr->etl_bf != 0) || parent->is_origin()) + { + if (parent->is_origin()) + { + parent->set_left(curr); + } + break; + } + is_right = curr->is_right_child(); + } + } + + }; // intrusive_avl_tree_base + + //*************************************************************************** + /// \ingroup intrusive_avl_tree + /// An intrusive AVL tree. Stores elements derived from 'intrusive_avl_tree::link_type'. + /// + /// NB! The tree itself is not the real owner (of memory) of its nodes. + /// The node `TValue` type contains all the necessary means (via required inheritance from `link_type`) + /// to be able to participate in the tree as a "linked" node - hence the "intrusive" term. + /// + /// \warning This tree cannot be used for concurrent access from multiple threads. + /// \tparam TValue The type of value that the tree holds. + /// \tparam ID_ The link ID that the value is derived from. + //*************************************************************************** + template + class intrusive_avl_tree : public etl::intrusive_avl_tree_base + { + typedef etl::intrusive_avl_tree_base base; + + public: + + // Node typedef. + typedef typename base::link_type link_type; + + // STL style typedefs. + typedef TValue value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef size_t size_type; +#if ETL_USING_CPP11 + typedef value_type&& rvalue_reference; +#endif + + //************************************************************************* + /// Defines iterator of mutable items of the tree. + /// + /// Normally, an iterator might... + /// - either reference an existing item; + /// - or be equal to the `end`. + /// Iterator could also be in "valueless" state: + /// - see default constructor; + /// - special cases of `find_or_insert`; + /// - advanced traversal methods, like `get_parent` and `get_child`. + /// Both "end" and "valueless" conditions could be easily isolated using `has_value` or `bool operator`: + /// - `has_value() == true` - iterator references a real node + /// - `it == end()` - terminal sentinel + /// - `!has_value() && it != end()` - valueless iterator + /// The real item (referenced by the iterator) could be modified by the user, + /// but so that it doesn't affect current ordering of the tree. + //************************************************************************* + class iterator : public etl::iterator + { + public: + + friend class intrusive_avl_tree; + friend class const_iterator; + + iterator() ETL_NOEXCEPT + : p_value(ETL_NULLPTR) + { + } + + //************************************************************************* + /// Increments iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + iterator& operator++() ETL_NOEXCEPT + { + p_value = base::next_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Increments iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the increment). + //************************************************************************* + iterator operator++(int) ETL_NOEXCEPT + { + iterator temp(*this); + p_value = base::next_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Decrements iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + iterator& operator--() ETL_NOEXCEPT + { + p_value = base::prev_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Decrements iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the decrement). + //************************************************************************* + iterator operator--(int) ETL_NOEXCEPT + { + iterator temp(*this); + p_value = base::prev_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Dereferences an existing mutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + reference operator*() const + { + ETL_ASSERT(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); + +#include "private/diagnostic_null_dereference_push.h" + + return *static_cast(p_value); +#include "private/diagnostic_pop.h" + } + + //************************************************************************* + /// Gets address of an existing mutable item (if any). + /// Returns `nullptr` if iterator doesn't reference a real item. + //************************************************************************* + pointer get() const ETL_NOEXCEPT + { + return static_cast(has_value() ? p_value : ETL_NULLPTR); + } + + //************************************************************************* + /// Dereferences an existing mutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + pointer operator->() const + { + ETL_ASSERT_OR_RETURN_VALUE(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception), ETL_NULLPTR); + + return static_cast(p_value); + } + + friend bool operator==(const iterator& lhs, const iterator& rhs) ETL_NOEXCEPT + { + return lhs.p_value == rhs.p_value; + } + + friend bool operator!=(const iterator& lhs, const iterator& rhs) ETL_NOEXCEPT + { + return !(lhs == rhs); + } + + ETL_EXPLICIT operator bool() const ETL_NOEXCEPT + { + return has_value(); + } + + ETL_NODISCARD + bool has_value() const ETL_NOEXCEPT + { + return base::is_real_link(p_value); + } + + //************************************************************************* + /// Gets balance factor of tree node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result: -1, 0 or +1 depending on children tree height difference. + //************************************************************************* + ETL_NODISCARD + int_fast8_t get_balance_factor() const ETL_NOEXCEPT + { + return base::get_balance_factor_impl(p_value); + } + + //************************************************************************* + /// Gets parent node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no parent. + //************************************************************************* + ETL_NODISCARD + iterator get_parent() const ETL_NOEXCEPT + { + return iterator(base::get_parent_impl(p_value)); + } + + //************************************************************************* + /// Gets a child node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no such child. + //************************************************************************* + ETL_NODISCARD + iterator get_child(const bool is_right) const ETL_NOEXCEPT + { + return iterator(base::get_child_impl(p_value, is_right)); + } + + private: + + ETL_EXPLICIT iterator(link_type* value) + : p_value(value) + { + } + + link_type* p_value; + + }; // iterator + + //************************************************************************* + /// Defines constant iterator of immutable items of the tree. + /// + /// Normally, an iterator might... + /// - either reference an existing item; + /// - or be equal to the `end`. + /// Iterator could also be in "valueless" state: + /// - see default constructor; + /// - advanced traversal methods, like `get_parent` and `get_child`. + /// Both "end" and "valueless" conditions could be easily isolated using `has_value` or `bool operator`: + /// - `has_value() == true` - iterator references a real node + /// - `it == end()` - terminal sentinel + /// - `!has_value() && it != end()` - valueless iterator + //************************************************************************* + class const_iterator : public etl::iterator + { + public: + + friend class intrusive_avl_tree; + + const_iterator() ETL_NOEXCEPT + : p_value(ETL_NULLPTR) + { + } + + // Implicit by design - it's always safe to make const iterator from ordinary one. + const_iterator(const typename intrusive_avl_tree::iterator& other) ETL_NOEXCEPT // NOLINT + : p_value(other.p_value) + { + } + + //************************************************************************* + /// Increments const iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + const_iterator& operator++() ETL_NOEXCEPT + { + p_value = base::next_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Increments const iterator to point to the next ("greater") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the increment). + const_iterator operator++(int) ETL_NOEXCEPT + { + const_iterator temp(*this); + p_value = base::next_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Decrements const iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + //************************************************************************* + const_iterator& operator--() ETL_NOEXCEPT + { + p_value = base::prev_in_order_impl(p_value); + return *this; + } + + //************************************************************************* + /// Decrements const iterator to point to the previous ("smaller") item. + /// Complexity: amortized O(1), worst-case O(log(N)). + /// Returns original iterator (before the decrement). + //************************************************************************* + const_iterator operator--(int) ETL_NOEXCEPT + { + const_iterator temp(*this); + p_value = base::prev_in_order_impl(p_value); + return temp; + } + + //************************************************************************* + /// Dereferences an existing immutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + const_reference operator*() const + { + ETL_ASSERT(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); + +#include "private/diagnostic_null_dereference_push.h" + + return *static_cast(p_value); +#include "private/diagnostic_pop.h" + } + + //************************************************************************* + /// Gets address of an existing immutable item (if any). + /// Returns `nullptr` if iterator doesn't reference a real item. + //************************************************************************* + const_pointer get() const ETL_NOEXCEPT + { + return static_cast(has_value() ? p_value : ETL_NULLPTR); + } + + //************************************************************************* + /// Dereferences an existing immutable item. + /// Could be used only for iterator which has value (`has_value() == true`). + /// If asserts or exceptions are enabled, throws an `etl::intrusive_avl_tree_iterator_exception` + /// if iterator doesn't reference a real item. + //************************************************************************* + const_pointer operator->() const + { + ETL_ASSERT_OR_RETURN_VALUE(has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception), ETL_NULLPTR); + + return static_cast(p_value); + } + + friend bool operator==(const const_iterator& lhs, const const_iterator& rhs) ETL_NOEXCEPT + { + return lhs.p_value == rhs.p_value; + } + + friend bool operator!=(const const_iterator& lhs, const const_iterator& rhs) ETL_NOEXCEPT + { + return !(lhs == rhs); + } + + ETL_EXPLICIT operator bool() const ETL_NOEXCEPT + { + return has_value(); + } + + ETL_NODISCARD + bool has_value() const ETL_NOEXCEPT + { + return base::is_real_link(p_value); + } + + //************************************************************************* + /// Gets balance factor of tree node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result: -1, 0 or +1 depending on children tree height difference. + //************************************************************************* + ETL_NODISCARD + int_fast8_t get_balance_factor() const ETL_NOEXCEPT + { + return base::get_balance_factor_impl(p_value); + } + + //************************************************************************* + /// Gets parent node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no parent. + //************************************************************************* + ETL_NODISCARD + const_iterator get_parent() const ETL_NOEXCEPT + { + return const_iterator(base::get_parent_impl(p_value)); + } + + //************************************************************************* + /// Gets a child node. + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if there is no such child. + //************************************************************************* + ETL_NODISCARD + const_iterator get_child(const bool is_right) const ETL_NOEXCEPT + { + return const_iterator(base::get_child_impl(p_value, is_right)); + } + + private: + + ETL_EXPLICIT const_iterator(const link_type* value) + : p_value(value) + { + } + + const link_type* p_value; + + }; // const_iterator + + //************************************************************************* + /// Default constructor. + //************************************************************************* + intrusive_avl_tree() ETL_NOEXCEPT + : intrusive_avl_tree_base() + { + } + + //************************************************************************* + /// Constructor from range of items. + /// Complexity: O(N*log(N)). + /// NB! All items in the range must be in the "unlinked" state initially; otherwise the "already linked" exception is thrown. + /// On success, all inserted items (except possible duplicates; see the comparator description below) will be linked. + /// + /// The binary comparator should accept two `const value_type&` arguments (aka `lhs` and `rhs`), + /// and return integer result (aka `lhs - rhs` "subtraction" result): + /// - `>0` if the `lhs` is "greater" than the `rhs` + /// - `0` if the `lhs` is "equal" to the `rhs` + /// - `<0` if the `lhs` is "smaller" than the `rhs` + /// - if throws then exception is propagated + /// Hints: + /// - if you want duplicates then always return either `>0` or `<0`, even for equal arguments: + /// - `<0` (e.g. `-1`) will prepend a duplicate item to the tree + /// - `>0` (e.g. `+1`) will append a duplicate item to the tree + /// - if you want to avoid duplicates in the result tree then use `0` for "equal" arguments - + /// in such case only the first item of duplicates in the range + /// will be linked to the tree; the rest will be skipped (left unlinked) + /// - `etl::compare::cmp` could be directly used as comparator (if value's `less` is defined) - + /// its `Less`, `Equal` and `Greater` are convertible/compatible with expected result integers. + /// + /// If asserts or exceptions are enabled, throws: + /// - an etl::intrusive_avl_tree_iterator_exception if the `first` > `last` (DEBUG build only). + /// - an etl::intrusive_avl_tree_value_is_already_linked if any item (in `[first, last)` range) is already linked to some other tree. + /// - whatever the `binary_comp` might throw + /// Any exception during tree building will unlink already inserted nodes - so it's all or nothing. + //************************************************************************* + template + intrusive_avl_tree(TIterator first, TIterator last, TBinaryCompare binary_comp, + typename etl::enable_if::value, int>::type = 0) + { + base::template assign_impl(first, last, binary_comp); + } + + //************************************************************************* + /// Destructor. + /// Complexity: O(N). + //************************************************************************* + ~intrusive_avl_tree() {} + +#if ETL_USING_CPP11 + //************************************************************************* + /// Move constructor. + /// Complexity: O(1). + //************************************************************************* + intrusive_avl_tree(intrusive_avl_tree&&) ETL_NOEXCEPT = default; + + //************************************************************************* + /// Move assignment. + /// Does nothing in case of self-assignment (when `this == &other`). + /// Complexity: O(N), where N is the size of `this` tree before assignment - + /// all former items have to be unlinked. + //************************************************************************* + intrusive_avl_tree& operator=(intrusive_avl_tree&&) ETL_NOEXCEPT = default; + + intrusive_avl_tree(const intrusive_avl_tree&) = delete; + intrusive_avl_tree& operator=(const intrusive_avl_tree&) = delete; +#endif + + //************************************************************************* + /// Gets the beginning of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator begin() ETL_NOEXCEPT + { + return iterator(base::begin_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator begin() const ETL_NOEXCEPT + { + return const_iterator(base::begin_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator cbegin() const ETL_NOEXCEPT + { + return begin(); + } + + //************************************************************************* + /// Gets the end of the intrusive_avl_tree. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + iterator end() ETL_NOEXCEPT + { + return iterator(base::end_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the end of the intrusive_avl_tree. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + const_iterator end() const ETL_NOEXCEPT + { + return const_iterator(base::end_impl(base::get_origin())); + } + + //************************************************************************* + /// Gets the end of the intrusive_avl_tree. + /// Complexity: O(1). + //************************************************************************* + ETL_NODISCARD + const_iterator cend() const ETL_NOEXCEPT + { + return end(); + } + + //************************************************************************* + /// Gets the maximum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator max() ETL_NOEXCEPT + { + return --end(); + } + + //************************************************************************* + /// Gets the maximum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator max() const ETL_NOEXCEPT + { + return --end(); + } + + //************************************************************************* + /// Gets the minimum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator min() ETL_NOEXCEPT + { + return begin(); + } + + //************************************************************************* + /// Gets the minimum of the intrusive_avl_tree. + /// Complexity: O(log(N)). + /// Returns `end` if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator min() const ETL_NOEXCEPT + { + return begin(); + } + + //************************************************************************* + /// Returns an iterator pointing to the first item that compares as "not less". + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if no such item exists. + //************************************************************************* + template + iterator lower_bound(TCompare comp) + { + pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + template + const_iterator lower_bound(TCompare comp) const + { + const_pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Returns an iterator pointing to the first item that compares as "greater". + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if no such item exists. + //************************************************************************* + template + iterator upper_bound(TCompare comp) + { + pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + template + const_iterator upper_bound(TCompare comp) const + { + const_pointer ptr = base::template find_bound_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Gets root node (if any). + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if tree is empty. + //************************************************************************* + ETL_NODISCARD + iterator get_root() ETL_NOEXCEPT + { + return iterator(base::get_root()); + } + + //************************************************************************* + /// Gets root node (if any). + /// Complexity: O(1). + /// Normally is not needed unless advanced traversal is required. + /// Result iterator will be valueless (`has_value() == false`) if tree is empty. + //************************************************************************* + ETL_NODISCARD + const_iterator get_root() const ETL_NOEXCEPT + { + return const_iterator(base::get_root()); + } + + //************************************************************************* + /// Finds an item using given unary comparator. + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if there is no matching item. + //************************************************************************* + template + iterator find(TCompare comp) + { + pointer ptr = base::template find_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Finds a constant item using given unary comparator. + /// Complexity: O(log(N)) assuming O(1) for the comparator. + /// + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" + /// - if throws then exception is propagated + /// + /// Result iterator will be `end()` if there is no matching item. + //************************************************************************* + template + const_iterator find(TCompare comp) const + { + const_pointer ptr = base::template find_impl(base::get_root(), comp); + return make_iterator(ptr, end()); + } + + //************************************************************************* + /// Finds an existing item using given unary comparator, and if not found + /// then invokes `factory` functor to insert a new item. The new item + /// is inserted at the tree position where the search has stopped. + /// Complexity: O(log(N)) assuming O(1) for the comparator and factory. + /// Operation does NOT invalidate any already existing iterators, + /// but depending on where the new item was linked to the tree + /// the existing iterators may skip the recently linked item. + /// + /// The unary comparator should accept single `const value_type& value` argument, + /// and return integer result: + /// - `>0` (e.g. `+1`) if the find target is "greater" than the argument + /// - `0` if the target is found + /// - `<0` (e.g. `-1`) if the target is "smaller" than the argument + /// - if throws then exception is propagated + /// Hint: If result tree should contain "duplicates" then return non-zero + /// result even for "equal" items, like e.g.: + /// - `+1` to append a new item after existing duplicates + /// - `-1` to prepend a new item before existing duplicates + /// + /// The factory functor should return address of a "new" value (castable to the `link_type*`). + /// The returned value should not be already linked to any tree, otherwise throws + /// `intrusive_avl_tree_value_is_already_linked` (if asserts or exceptions are enabled). + /// If return address is `nullptr` then tree won't be modified, + /// and final result iterator will be valueless. + /// If factory throws then exception is propagated (without modifying the tree). + /// + /// Result contains a pair of: + /// - iterator to found (or inserted) item (could be valueless, but never `end()`). + /// - boolean indicating whether tree was modified (due to successful insertion). + //************************************************************************* + template + etl::pair find_or_insert(TCompare comp, TFactory factory) + { + const etl::pair ptr_mod = base::template find_or_insert_impl(comp, factory); + return etl::make_pair(make_iterator(ptr_mod.first, iterator()), ptr_mod.second); + } + + //************************************************************************* + /// Erases the value at the specified position. + /// Complexity: O(log(N)) - includes tree rebalancing. + /// Operation invalidates any existing iterator to the same item, + /// but it does NOT affect any other iterators. + /// \param position iterator must originate from the same tree instance. + /// Returns iterator to the next tree node (after the erased one). + /// If asserts or exceptions are enabled, throws etl::intrusive_avl_tree_iterator_exception + /// if iterator doesn't reference a real item. + /// Hint: use `clear` method if you need to erase all items - no tree rebalancing will be involved. + //************************************************************************* + iterator erase(iterator position) + { + ETL_ASSERT(position.has_value(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); +#if ETL_IS_DEBUG_BUILD + // Iterator must originate from the same tree instance. + // The check is still of the same O(log(N)) complexity. + ETL_ASSERT(base::get_origin(position.p_value) == &base::get_origin(), ETL_ERROR(intrusive_avl_tree_iterator_exception)); +#endif + + iterator next(position); + ++next; + + base::erase_impl(position.p_value); + + return next; + } + + //************************************************************************* + /// Erases the value at the specified position. + /// Complexity: O(log(N)) - includes tree rebalancing. + /// Operation invalidates any existing iterator to the same item, + /// but it does NOT affect any other iterators. + /// \param position iterator must originate from the same tree instance. + /// Returns iterator to the next tree node (after the erased one). + /// If asserts or exceptions are enabled, throws etl::intrusive_avl_tree_iterator_exception + /// if iterator doesn't reference a real item. + /// Hint: use `clear` method if you need to erase all items - no tree rebalancing will be involved. + //************************************************************************* + iterator erase(const_iterator position) + { + // It's safe to `const_cast` b/c we just need iterator to locate corresponding link. + // Tree itself is not `const` anyway - so it's a legitimate operation, and it also matches + // with similar `std::list::erase` or `etl::intrusive_list::erase` methods. + return erase(iterator(const_cast(position.p_value))); + } + + //************************************************************************* + /// Visits all items of the tree in (ascending or descending) order. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order: `false` -> ascending, `true` -> descending. + /// The `visitor` functor will be called with reference to the next in-order item. + /// The item could be modified but so that it doesn't affect current ordering of the tree. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_in_order(const bool is_reverse, Visitor visitor) + { +#if ETL_USING_CPP11 + base::visit_in_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_in_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree in (ascending or descending) order. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order: `false` -> ascending, `true` -> descending. + /// The `visitor` functor will be called with const reference to the next in-order item. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_in_order(const bool is_reverse, Visitor visitor) const + { +#if ETL_USING_CPP11 + base::visit_in_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](const link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_in_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "post" ordering - + /// child items first, and then "current" item (finishing with the root one). + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with reference to the next post-order item. + /// The item could be modified but so that it doesn't affect current ordering of the tree. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_post_order(const bool is_reverse, Visitor visitor) + { +#if ETL_USING_CPP11 + base::visit_post_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_post_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "post" ordering - + /// child items first, and then "current" item (finishing with the root one). + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with const reference to the next post-order item. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_post_order(const bool is_reverse, Visitor visitor) const + { +#if ETL_USING_CPP11 + base::visit_post_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](const link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_post_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "pre" ordering - + /// "current" item first (starting from the root), and then children. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with reference to the next pre-order item. + /// The item could be modified but so that it doesn't affect current ordering of the tree. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_pre_order(const bool is_reverse, Visitor visitor) + { +#if ETL_USING_CPP11 + base::visit_pre_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_pre_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + //************************************************************************* + /// Visits all items of the tree using "pre" ordering - + /// "current" item first (starting from the root), and then children. + /// See https://en.wikipedia.org/wiki/Tree_traversal + /// Complexity: O(N); no recursion. + /// `is_reverse` determines in which order to visit children: + /// - `false` -> "smaller" child first (if any), and then "bigger" one + /// - `true` -> "bigger" child first (if any), and then "smaller" one + /// The `visitor` functor will be called with const reference to the next pre-order item. + /// NB! The visitor must not modify the tree during visitation. + /// If visitor throws then exception is propagated. + //************************************************************************* + template + void visit_pre_order(const bool is_reverse, Visitor visitor) const + { +#if ETL_USING_CPP11 + base::visit_pre_order_impl( // + &base::get_origin(), is_reverse, // + [&visitor](const link_type& link) { visitor(static_cast(link)); }); +#else + const CastingVisitor casting_visitor(visitor); + base::visit_pre_order_impl(&base::get_origin(), is_reverse, casting_visitor); +#endif + } + + private: + +#if ETL_USING_CPP11 +#else + // Disable copy construction and assignment. + intrusive_avl_tree(const intrusive_avl_tree&); + intrusive_avl_tree& operator=(const intrusive_avl_tree& rhs); + + template + struct CastingVisitor + { + Visitor& visitor; + ETL_EXPLICIT CastingVisitor(Visitor& visitor) + : visitor(visitor) + { + } + + template + void operator()(TLink& link) const + { + visitor(static_cast(link)); + } + }; +#endif + + template + ETL_NODISCARD + static TIterator make_iterator(Pointer const ptr, const TIterator end) + { + return (ETL_NULLPTR != ptr) ? TIterator(ptr) : end; + } + + }; // intrusive_avl_tree + +} // namespace etl + +#endif diff --git a/include/etl/invert.h b/include/etl/invert.h index fddcebf9..f56a492e 100644 --- a/include/etl/invert.h +++ b/include/etl/invert.h @@ -48,7 +48,7 @@ namespace etl //***************************************************************** // Constructor. //***************************************************************** - invert() + ETL_CONSTEXPR invert() : offset(TInput(0)) , minuend((etl::numeric_limits::is_signed) ? TInput(0) : etl::numeric_limits::max()) { @@ -57,7 +57,7 @@ namespace etl //***************************************************************** // Constructor. //***************************************************************** - invert(TInput offset_, TInput minuend_) + ETL_CONSTEXPR invert(TInput offset_, TInput minuend_) : offset(offset_) , minuend(minuend_) { @@ -66,7 +66,7 @@ namespace etl //***************************************************************** // operator () //***************************************************************** - TInput operator()(TInput value) const + ETL_CONSTEXPR TInput operator()(TInput value) const { return minuend - (value - offset); } diff --git a/include/etl/iterator.h b/include/etl/iterator.h index cda84758..a990fb6c 100644 --- a/include/etl/iterator.h +++ b/include/etl/iterator.h @@ -502,90 +502,93 @@ namespace etl typedef TIterator pointer; typedef value_type&& reference; - move_iterator() {} + ETL_CONSTEXPR move_iterator() + : current() + { + } - explicit move_iterator(TIterator itr) + ETL_CONSTEXPR explicit move_iterator(TIterator itr) : current(itr) { } template - move_iterator(const move_iterator& itr) + ETL_CONSTEXPR move_iterator(const move_iterator& itr) : current(itr.base()) { } template - move_iterator& operator=(const move_iterator& itr) + ETL_CONSTEXPR14 move_iterator& operator=(const move_iterator& itr) { current = itr.current; return *this; } - iterator_type base() const + ETL_CONSTEXPR iterator_type base() const { return current; } - pointer operator->() const + ETL_CONSTEXPR pointer operator->() const { return current; } - reference operator*() const + ETL_CONSTEXPR reference operator*() const { return etl::move(*current); } - move_iterator& operator++() + ETL_CONSTEXPR14 move_iterator& operator++() { ++current; return *this; } - move_iterator& operator--() + ETL_CONSTEXPR14 move_iterator& operator--() { --current; return *this; } - move_iterator operator++(int) + ETL_CONSTEXPR14 move_iterator operator++(int) { move_iterator temp = *this; ++current; return temp; } - move_iterator operator--(int) + ETL_CONSTEXPR14 move_iterator operator--(int) { move_iterator temp = *this; --current; return temp; } - move_iterator operator+(difference_type n) const + ETL_CONSTEXPR move_iterator operator+(difference_type n) const { return move_iterator(current + n); } - move_iterator operator-(difference_type n) const + ETL_CONSTEXPR move_iterator operator-(difference_type n) const { return move_iterator(current - n); } - move_iterator operator+=(difference_type n) + ETL_CONSTEXPR14 move_iterator& operator+=(difference_type n) { current += n; return *this; } - move_iterator operator-=(difference_type n) + ETL_CONSTEXPR14 move_iterator& operator-=(difference_type n) { current -= n; return *this; } - reference operator[](difference_type n) const + ETL_CONSTEXPR reference operator[](difference_type n) const { return etl::move(current[n]); } @@ -596,55 +599,55 @@ namespace etl }; template - bool operator==(const etl::move_iterator& lhs, const etl::move_iterator& rhs) + ETL_CONSTEXPR bool operator==(const etl::move_iterator& lhs, const etl::move_iterator& rhs) { return lhs.base() == rhs.base(); } template - bool operator!=(const etl::move_iterator& lhs, const etl::move_iterator& rhs) + ETL_CONSTEXPR bool operator!=(const etl::move_iterator& lhs, const etl::move_iterator& rhs) { return !(lhs == rhs); } template - bool operator<(const etl::move_iterator& lhs, const etl::move_iterator& rhs) + ETL_CONSTEXPR bool operator<(const etl::move_iterator& lhs, const etl::move_iterator& rhs) { return lhs.base() < rhs.base(); } template - bool operator<=(const etl::move_iterator& lhs, const etl::move_iterator& rhs) + ETL_CONSTEXPR bool operator<=(const etl::move_iterator& lhs, const etl::move_iterator& rhs) { return !(rhs < lhs); } template - bool operator>(const etl::move_iterator& lhs, const etl::move_iterator& rhs) + ETL_CONSTEXPR bool operator>(const etl::move_iterator& lhs, const etl::move_iterator& rhs) { return (rhs < lhs); } template - bool operator>=(const etl::move_iterator& lhs, const etl::move_iterator& rhs) + ETL_CONSTEXPR bool operator>=(const etl::move_iterator& lhs, const etl::move_iterator& rhs) { return !(lhs < rhs); } template - move_iterator operator+(typename move_iterator::difference_type n, const move_iterator& rhs) + ETL_CONSTEXPR move_iterator operator+(typename move_iterator::difference_type n, const move_iterator& rhs) { return rhs + n; } template - auto operator-(const move_iterator& lhs, const move_iterator& rhs) -> decltype(lhs.base() - rhs.base()) + ETL_CONSTEXPR auto operator-(const move_iterator& lhs, const move_iterator& rhs) -> decltype(lhs.base() - rhs.base()) { return lhs.base() - rhs.base(); } template - etl::move_iterator make_move_iterator(TIterator itr) + ETL_CONSTEXPR etl::move_iterator make_move_iterator(TIterator itr) { return etl::move_iterator(itr); } diff --git a/include/etl/memory.h b/include/etl/memory.h index 6bd07d61..6a675f19 100644 --- a/include/etl/memory.h +++ b/include/etl/memory.h @@ -1916,7 +1916,7 @@ namespace etl //********************************* template - default_delete(const default_delete&) ETL_NOEXCEPT + ETL_CONSTEXPR default_delete(const default_delete&) ETL_NOEXCEPT { } @@ -1943,7 +1943,7 @@ namespace etl //********************************* template - default_delete(const default_delete&) ETL_NOEXCEPT + ETL_CONSTEXPR default_delete(const default_delete&) ETL_NOEXCEPT { } diff --git a/include/etl/pseudo_moving_average.h b/include/etl/pseudo_moving_average.h index a75550ab..ddaa3cec 100644 --- a/include/etl/pseudo_moving_average.h +++ b/include/etl/pseudo_moving_average.h @@ -127,7 +127,7 @@ namespace etl /// Constructor /// \param initial_value The initial value for the average. //************************************************************************* - pseudo_moving_average(const T initial_value) + ETL_CONSTEXPR pseudo_moving_average(const T initial_value) : average(initial_value * SCALE) { } @@ -208,7 +208,7 @@ namespace etl /// Constructor /// \param initial_value The initial value for the average. //************************************************************************* - pseudo_moving_average(const T initial_value, const size_t sample_size) + ETL_CONSTEXPR pseudo_moving_average(const T initial_value, const size_t sample_size) : average(initial_value * SCALE) , samples(sample_t(sample_size)) { @@ -292,7 +292,7 @@ namespace etl /// Constructor /// \param initial_value The initial value for the average. //************************************************************************* - pseudo_moving_average(const T initial_value) + ETL_CONSTEXPR pseudo_moving_average(const T initial_value) : reciprocal_samples_plus_1(T(1.0) / T(SAMPLE_SIZE_ + 1U)) , average(initial_value) { @@ -363,7 +363,7 @@ namespace etl /// Constructor /// \param initial_value The initial value for the average. //************************************************************************* - pseudo_moving_average(const T initial_value, const size_t sample_size) + ETL_CONSTEXPR pseudo_moving_average(const T initial_value, const size_t sample_size) : reciprocal_samples_plus_1(T(1.0) / T(sample_size + 1U)) , average(initial_value) { diff --git a/include/etl/rescale.h b/include/etl/rescale.h index fa183f22..75ff0733 100644 --- a/include/etl/rescale.h +++ b/include/etl/rescale.h @@ -51,7 +51,7 @@ namespace etl //***************************************************************** /// Constructor. //***************************************************************** - rescale(TInput input_min_value_, TInput input_max_value_, TOutput output_min_value_, TOutput output_max_value_) + ETL_CONSTEXPR rescale(TInput input_min_value_, TInput input_max_value_, TOutput output_min_value_, TOutput output_max_value_) : input_min_value(input_min_value_) , output_min_value(output_min_value_) , output_max_value(output_max_value_) @@ -62,7 +62,7 @@ namespace etl //***************************************************************** /// operator () //***************************************************************** - TOutput operator()(TInput value) const + ETL_CONSTEXPR TOutput operator()(TInput value) const { return TOutput(((value - input_min_value) * multiplier)) + output_min_value; } diff --git a/include/etl/threshold.h b/include/etl/threshold.h index f1fbf09c..c5ed5bd1 100644 --- a/include/etl/threshold.h +++ b/include/etl/threshold.h @@ -50,7 +50,7 @@ namespace etl //***************************************************************** // Constructor. //***************************************************************** - threshold(TInput threshold_value_, TInput true_value_, TInput false_value_, TCompare compare_ = TCompare()) + ETL_CONSTEXPR threshold(TInput threshold_value_, TInput true_value_, TInput false_value_, TCompare compare_ = TCompare()) : threshold_value(threshold_value_) , true_value(true_value_) , false_value(false_value_) @@ -61,7 +61,7 @@ namespace etl //***************************************************************** // operator () //***************************************************************** - TInput operator()(TInput value) const + ETL_CONSTEXPR TInput operator()(TInput value) const { return compare(value, threshold_value) ? true_value : false_value; } diff --git a/include/etl/utility.h b/include/etl/utility.h index 4c7e4719..55f35033 100644 --- a/include/etl/utility.h +++ b/include/etl/utility.h @@ -228,7 +228,7 @@ namespace etl } /// Copy constructor - pair(const pair& other) + ETL_CONSTEXPR pair(const pair& other) : first(other.first) , second(other.second) { @@ -254,16 +254,16 @@ namespace etl /// Constructing from std::pair template - pair(const std::pair& other) + ETL_CONSTEXPR pair(const std::pair& other) : first(other.first) , second(other.second) { } #if ETL_USING_CPP11 - /// Constructing to etl::pair + /// Constructing from std::pair (move) template - pair(std::pair&& other) + ETL_CONSTEXPR pair(std::pair&& other) : first(etl::forward(other.first)) , second(etl::forward(other.second)) { @@ -892,13 +892,13 @@ namespace etl template struct coordinate_2d { - coordinate_2d() + ETL_CONSTEXPR coordinate_2d() : x(T(0)) , y(T(0)) { } - coordinate_2d(T x_, T y_) + ETL_CONSTEXPR coordinate_2d(T x_, T y_) : x(x_) , y(y_) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a7e61fb9..042d428d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -206,6 +206,7 @@ add_executable(etl_tests test_inplace_function.cpp test_instance_count.cpp test_integral_limits.cpp + test_intrusive_avl_tree.cpp test_intrusive_forward_list.cpp test_intrusive_links.cpp test_intrusive_list.cpp diff --git a/test/meson.build b/test/meson.build index 8de41419..14fd63ad 100644 --- a/test/meson.build +++ b/test/meson.build @@ -194,6 +194,7 @@ etl_test_sources = files( 'test_inplace_function.cpp', 'test_instance_count.cpp', 'test_integral_limits.cpp', + 'test_intrusive_avl_tree.cpp', 'test_intrusive_forward_list.cpp', 'test_intrusive_links.cpp', 'test_intrusive_list.cpp', diff --git a/test/syntax_check/CMakeLists.txt b/test/syntax_check/CMakeLists.txt index 00ebd0da..62fa017e 100644 --- a/test/syntax_check/CMakeLists.txt +++ b/test/syntax_check/CMakeLists.txt @@ -247,6 +247,7 @@ target_sources(tests PRIVATE inplace_function.h.t.cpp instance_count.h.t.cpp integral_limits.h.t.cpp + intrusive_avl_tree.h.t.cpp intrusive_forward_list.h.t.cpp intrusive_links.h.t.cpp intrusive_list.h.t.cpp diff --git a/test/syntax_check/intrusive_avl_tree.h.t.cpp b/test/syntax_check/intrusive_avl_tree.h.t.cpp new file mode 100644 index 00000000..13985fd7 --- /dev/null +++ b/test/syntax_check/intrusive_avl_tree.h.t.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Sergei Shirokov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include diff --git a/test/test_cyclic_value.cpp b/test/test_cyclic_value.cpp index 2245b047..7077628a 100644 --- a/test/test_cyclic_value.cpp +++ b/test/test_cyclic_value.cpp @@ -492,5 +492,33 @@ namespace CHECK(data1 == compare2); CHECK(data2 == compare1); } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_cyclic_value_constexpr_ctor) + { + constexpr etl::cyclic_value cv; + static_assert(cv.get() == 0, "constexpr default ctor"); + CHECK(true); + } + + //************************************************************************* + TEST(test_cyclic_value_constexpr_ctor_with_range) + { + constexpr etl::cyclic_value cv(0, 9); + static_assert(cv.first() == 0, "constexpr range ctor first"); + static_assert(cv.last() == 9, "constexpr range ctor last"); + CHECK(true); + } + + //************************************************************************* + TEST(test_cyclic_value_constexpr_copy_ctor) + { + constexpr etl::cyclic_value cv1; + constexpr etl::cyclic_value cv2(cv1); + static_assert(cv2.get() == 0, "constexpr copy ctor"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_fixed_iterator.cpp b/test/test_fixed_iterator.cpp index fb49804a..72b6c907 100644 --- a/test/test_fixed_iterator.cpp +++ b/test/test_fixed_iterator.cpp @@ -228,5 +228,42 @@ namespace CHECK(fi1 == fi2); CHECK(fi1 != fi3); } + + //************************************************************************* + TEST(test_dereference_write_through) + { + int value = 42; + etl::fixed_iterator fi(&value); + + // Writing through the dereferenced iterator should modify the underlying value. + *fi = 99; + CHECK_EQUAL(99, value); + + // Increment does nothing, so writing again still targets the same location. + ++fi; + *fi = 123; + CHECK_EQUAL(123, value); + } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_fixed_iterator_constexpr_ctor) + { + static constexpr int data[] = {1, 2, 3}; + constexpr etl::fixed_iterator fi(&data[1]); + static_assert(*fi == 2, "constexpr ctor and deref"); + CHECK(true); + } + + //************************************************************************* + TEST(test_fixed_iterator_constexpr_copy_ctor) + { + static constexpr int data[] = {1, 2, 3}; + constexpr etl::fixed_iterator fi1(&data[0]); + constexpr etl::fixed_iterator fi2(fi1); + static_assert(*fi2 == 1, "constexpr copy ctor"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_functional.cpp b/test/test_functional.cpp index 76f2e417..1c17255d 100644 --- a/test/test_functional.cpp +++ b/test/test_functional.cpp @@ -591,5 +591,47 @@ namespace CHECK(!result_equal); } #endif + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_binder1st_constexpr) + { + struct plus_op + { + typedef int first_argument_type; + typedef int second_argument_type; + typedef int result_type; + ETL_CONSTEXPR int operator()(int a, int b) const + { + return a + b; + } + }; + + constexpr plus_op op; + constexpr etl::binder1st b = etl::bind1st(op, 10); + static_assert(b(5) == 15, "constexpr bind1st"); + CHECK(true); + } + + //************************************************************************* + TEST(test_binder2nd_constexpr) + { + struct plus_op + { + typedef int first_argument_type; + typedef int second_argument_type; + typedef int result_type; + ETL_CONSTEXPR int operator()(int a, int b) const + { + return a + b; + } + }; + + constexpr plus_op op; + constexpr etl::binder2nd b = etl::bind2nd(op, 10); + static_assert(b(5) == 15, "constexpr bind2nd"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_intrusive_avl_tree.cpp b/test/test_intrusive_avl_tree.cpp new file mode 100644 index 00000000..77754bfa --- /dev/null +++ b/test/test_intrusive_avl_tree.cpp @@ -0,0 +1,1606 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Sergei Shirokov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "data.h" + +#include "etl/compare.h" +#include "etl/intrusive_avl_tree.h" +#include "etl/optional.h" + +#include +#include +#include +#include +#include +#include + +typedef TestDataM ItemM; +typedef TestDataNDC ItemNDC; + +namespace +{ + typedef etl::intrusive_avl_tree_base<0>::link_type ZeroLink; + typedef etl::intrusive_avl_tree_base<1>::link_type FirstLink; + typedef etl::intrusive_avl_tree_base<2>::link_type SecondLink; + + //*************************************************************************** + class ItemNDCNode + : public ZeroLink + , public FirstLink + { + public: + + using compare = etl::compare; + + ItemNDCNode(const int value, const int index) + : data(value, index) + { + } + + friend bool operator<(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + return lhs.data < rhs.data; + } + + friend bool operator==(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + return lhs.data == rhs.data; + } + + struct CompareByValue + { + int value; + etl::optional excptn; + + explicit CompareByValue(const int value_, const etl::optional& excptn_ = etl::nullopt) + : value(value_) + , excptn(excptn_) + { + } + + ETL_NODISCARD + int operator()(const ItemNDCNode& other) const + { + if (excptn.has_value()) + { + throw etl::exception(excptn.value()); + } + return value - other.data.value; + } + + }; // CompareByValue + + ETL_NODISCARD + static compare::cmp_result always_less(const ItemNDCNode& other) + { + (void)other; + return compare::Less; + } + + ETL_NODISCARD + static compare::cmp_result always_greater(const ItemNDCNode& other) + { + (void)other; + return compare::Greater; + } + + ETL_NODISCARD + static compare::cmp_result compare_prepend_dups(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + const auto cmp = compare::cmp(lhs, rhs); + return (cmp != compare::Equal) ? cmp : compare::Less; + } + + ETL_NODISCARD + static compare::cmp_result compare_append_dups(const ItemNDCNode& lhs, const ItemNDCNode& rhs) + { + const auto cmp = compare::cmp(lhs, rhs); + return (cmp != compare::Equal) ? cmp : compare::Greater; + } + + ItemNDC data; + + }; // ItemNDCNode + + //*************************************************************************** + class ItemMNode : public SecondLink + { + public: + + explicit ItemMNode(const int value) + : data(value) + { + } + + ETL_NODISCARD + friend bool operator<(const ItemMNode& lhs, const ItemMNode& rhs) + { + return lhs.data < rhs.data; + } + + ETL_NODISCARD + friend bool operator==(const ItemMNode& lhs, const ItemMNode& rhs) + { + return lhs.data == rhs.data; + } + + struct CompareByValue + { + int value; + + ETL_NODISCARD + int operator()(const ItemMNode& other) const + { + return value - other.data.value; + } + }; + + ETL_NODISCARD + static int compare(const ItemMNode& lhs, const ItemMNode& rhs) + { + return lhs.data.value - rhs.data.value; + } + + ItemM data; + + }; // ItemMNode + + //*************************************************************************** + typedef etl::intrusive_avl_tree DataNDC0; + typedef etl::intrusive_avl_tree DataNDC1; + typedef etl::intrusive_avl_tree DataM; + + typedef std::vector InitialDataM; + typedef std::vector InitialDataNDC; + + SUITE(test_intrusive_avl_tree) + { + //************************************************************************* + struct SetupFixture + { + InitialDataNDC sorted_data; + InitialDataNDC unsorted_data; + InitialDataM sorted_data_moveable; + + SetupFixture() + { + sorted_data.clear(); + for (int i = 0; i < 31; ++i) + { + sorted_data.emplace_back(i, i); + sorted_data_moveable.emplace_back(i); + } + + constexpr std::array unsorted_order{ + 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 30, 29, 28, + }; + unsorted_data.clear(); + for (const auto idx : unsorted_order) + { + unsorted_data.emplace_back(static_cast(idx), static_cast(idx)); + } + } + + template + int verify_link(const Iterator it) const + { + if (!it.has_value()) + { + return 0; + } + + const auto parent = it.get_parent(); + const auto left_child = it.get_child(false); + const auto right_child = it.get_child(true); + if (parent.has_value()) + { + const auto parent_left_child = parent.get_child(false); + const auto parent_right_child = parent.get_child(true); + CHECK((it == parent_left_child) || (it == parent_right_child)); + } + + const int left_height = verify_link(left_child); + if (left_child.has_value()) + { + CHECK(it == left_child.get_parent()); + } + + const int right_height = verify_link(right_child); + if (right_child.has_value()) + { + CHECK(it == right_child.get_parent()); + } + + const int_least8_t balance_factor = it.get_balance_factor(); + CHECK((-1 <= balance_factor) && (balance_factor <= 1)); + CHECK_EQUAL(right_height - left_height, static_cast(balance_factor)); + return 1 + etl::max(right_height, left_height); + } + + template + void verify_tree(const Tree& tree) const + { + typedef typename Tree::value_type value_type; + typedef etl::reverse_iterator rev_it; + + if (tree.empty()) + { + CHECK(tree.begin() == tree.end()); + CHECK(!tree.get_root().has_value()); + } + else + { + CHECK(tree.get_root().has_value()); + CHECK(!tree.get_root().get_parent().has_value()); + } + + const auto root = tree.get_root(); + verify_link(root); + CHECK(etl::is_sorted(tree.begin(), tree.end())); + CHECK(etl::is_sorted(rev_it(tree.end()), rev_it(tree.begin()), etl::greater())); + CHECK(tree.min() == tree.begin()); + CHECK(tree.max() == --tree.end()); + } + + template + ETL_NODISCARD + std::string to_graphviz(const Tree& tree) const + { + std::ostringstream ss; + ss << "// https://magjac.com/graphviz-visual-editor/\n"; + ss << "// OR paste to `dot -Tsvg > output.svg`.\n//\n"; + ss << "digraph {\n"; + ss << "node[style=filled,fontcolor=white,shape=egg,fillcolor=black,fontsize=28,fontname=Menlo];\n"; + + const auto beg = tree.begin(); + const auto end = tree.end(); + ss << "origin[fillcolor=gray];"; + for (auto curr = beg; curr != end; ++curr) + { + ss << curr->data.index; + ss << "[label=" << curr->data.value; + const auto balance_factor = curr.get_balance_factor(); + if (balance_factor != 0) + { + const std::string bf_color = (balance_factor < 0) ? "blue" : "orangered"; + ss << ",fillcolor=" << bf_color; + } + ss << "];"; + } + ss << "\n"; + for (auto curr = beg; curr != end; ++curr) + { + if (auto child = curr.get_child(false)) + { + ss << curr->data.index; + ss << ":sw->" << child->data.index << ":n;"; + } + if (auto child = curr.get_child(true)) + { + ss << curr->data.index; + ss << ":se->" << child->data.index << ":n;"; + } + } + if (auto root = tree.get_root()) + { + ss << "origin:sw->" << root->data.index << ":n[label=root,fontsize=28];"; + } + ss << "\n}\n\n"; + return ss.str(); + } + }; + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_default_constructor) + { + DataNDC0 data0; + DataNDC1 data1; + + CHECK(data0.empty()); + CHECK_EQUAL(0, data0.size()); + CHECK(data1.empty()); + CHECK_EQUAL(0, data1.size()); + + CHECK(data0.max() == data0.end()); + CHECK(data0.min() == data0.end()); + CHECK(data0.begin() == data0.end()); + CHECK(data0.cbegin() == data0.cend()); + CHECK(data0.lower_bound(ItemNDCNode::always_less) == data0.end()); + CHECK(data0.lower_bound(ItemNDCNode::always_greater) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::always_less) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::always_greater) == data0.end()); + + CHECK(data1.max() == data1.end()); + CHECK(data1.min() == data1.end()); + CHECK(data1.begin() == data1.end()); + CHECK(data1.cbegin() == data1.cend()); + CHECK(data1.lower_bound(ItemNDCNode::always_less) == data1.end()); + CHECK(data1.lower_bound(ItemNDCNode::always_greater) == data1.end()); + CHECK(data1.upper_bound(ItemNDCNode::always_less) == data1.end()); + CHECK(data1.upper_bound(ItemNDCNode::always_greater) == data1.end()); + + verify_tree(data0); + verify_tree(data1); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_range_append_duplicates) + { + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(5, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare_append_dups); + verify_tree(data0); + CHECK_EQUAL(duplicate_data.size(), data0.size()); + + std::vector> actual_values_idx; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + actual_values_idx.emplace_back(it->data.value, it->data.index); + } + const std::vector> expected_values_idx{ + std::make_pair(5, 1), std::make_pair(7, 5), std::make_pair(10, 0), std::make_pair(10, 2), + std::make_pair(10, 4), std::make_pair(10, 6), std::make_pair(12, 7), std::make_pair(15, 3), + }; + CHECK(actual_values_idx == expected_values_idx); + CHECK_EQUAL(data0.min()->data.index, 1); + CHECK_EQUAL(data0.max()->data.index, 3); + + size_t duplicate_count = 0; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + if (it->data.value == 10) + { + ++duplicate_count; + } + } + CHECK_EQUAL(4U, duplicate_count); + + while (true) + { + auto it = data0.find(ItemNDCNode::CompareByValue{10}); + if (it == data0.end()) + { + break; + } + data0.erase(it); + verify_tree(data0); + } + + std::vector values_after_erase; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + values_after_erase.push_back(it->data.value); + } + const std::vector expected_after_erase{5, 7, 12, 15}; + CHECK(values_after_erase == expected_after_erase); + CHECK(data0.find(ItemNDCNode::CompareByValue{10}) == data0.end()); + CHECK_EQUAL(data0.min()->data.index, 1); + CHECK_EQUAL(data0.max()->data.index, 3); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_range_prepend_duplicates) + { + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(7, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare_prepend_dups); + verify_tree(data0); + CHECK_EQUAL(duplicate_data.size(), data0.size()); + + std::vector> actual_values_idx; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + actual_values_idx.emplace_back(it->data.value, it->data.index); + } + const std::vector> expected_values_idx{ + std::make_pair(7, 5), std::make_pair(7, 1), std::make_pair(10, 6), std::make_pair(10, 4), + std::make_pair(10, 2), std::make_pair(10, 0), std::make_pair(12, 7), std::make_pair(15, 3), + }; + CHECK(actual_values_idx == expected_values_idx); + CHECK_EQUAL(data0.min()->data.index, 5); + CHECK_EQUAL(data0.max()->data.index, 3); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_range_avoid_duplicates) + { + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(7, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + std::vector> actual_values_idx; + for (auto it = data0.begin(); it != data0.end(); ++it) + { + actual_values_idx.emplace_back(it->data.value, it->data.index); + } + const std::vector> expected_values_idx{ + std::make_pair(7, 1), + std::make_pair(10, 0), + std::make_pair(12, 7), + std::make_pair(15, 3), + }; + CHECK(actual_values_idx == expected_values_idx); + CHECK_EQUAL(data0.min()->data.index, 1); + CHECK_EQUAL(data0.max()->data.index, 3); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_with_bad_iterator) + { + auto action = [this]() + { + // Note end/begin order -> should throw! + DataNDC0 data0(sorted_data.end(), sorted_data.begin(), ItemNDCNode::compare::cmp); + }; + CHECK_THROW(action(), etl::intrusive_avl_tree_iterator_exception); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_partial_construction_unlinks_inserted_items) + { + InitialDataNDC nodes; + nodes.emplace_back(0, 0); + nodes.emplace_back(1, 1); + nodes.emplace_back(2, 2); + nodes.emplace_back(3, 3); + nodes.emplace_back(4, 4); + + DataNDC0 other_tree; + other_tree.find_or_insert(ItemNDCNode::CompareByValue{2}, [&nodes] { return &nodes[2]; }); + + auto construct_failing_tree = [&]() + { + DataNDC0 data0(nodes.begin(), nodes.end(), ItemNDCNode::compare::cmp); + }; + CHECK_THROW(construct_failing_tree(), etl::intrusive_avl_tree_value_is_already_linked); + + // The foreign tree still owns the conflicting node. + CHECK_EQUAL(1, other_tree.size()); + verify_tree(other_tree); + { + const auto it = other_tree.find(ItemNDCNode::CompareByValue{2}); + CHECK(it.has_value()); + CHECK_EQUAL(&nodes[2], it.get()); + } + + // Previously inserted nodes from the failed construction must have been unlinked. + other_tree.erase(other_tree.find(ItemNDCNode::CompareByValue{2})); + CHECK(other_tree.empty()); + + DataNDC0 recovered(nodes.begin(), nodes.end(), ItemNDCNode::compare::cmp); + CHECK_EQUAL(nodes.size(), recovered.size()); + verify_tree(recovered); + CHECK(std::equal(recovered.begin(), recovered.end(), nodes.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_empty_begin_end_root) + { + DataNDC0 data0; + + CHECK(data0.begin() == data0.end()); + CHECK(!data0.get_root().has_value()); + + const DataNDC0::const_iterator begin = data0.begin(); + const DataNDC0::const_iterator end = data0.end(); + CHECK(begin == end); + + CHECK(data0.cbegin() == data0.cend()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_iterator) + { + typedef etl::reverse_iterator rev_it; + + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + bool are_equal = std::equal(data0.begin(), data0.end(), sorted_data.begin()); + CHECK(are_equal); + + are_equal = std::equal(rev_it(data0.end()), rev_it(data0.begin()), sorted_data.rbegin()); + CHECK(are_equal); + + auto curr = data0.begin(); + CHECK(curr); + CHECK(curr.has_value()); + const auto& front = *curr; + CHECK_EQUAL(&front, curr.get()); + CHECK_EQUAL(front.data.value, sorted_data.front().data.value); + CHECK_EQUAL(curr->data.value, sorted_data.front().data.value); + auto prev = curr++; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(false)); + CHECK(curr != data0.begin()); + CHECK(prev == data0.begin()); + CHECK(prev-- == data0.begin()); + CHECK(prev == data0.end()); + + curr = data0.end(); + CHECK(!curr); + CHECK(!curr.has_value()); + CHECK(nullptr == curr.get()); + CHECK(curr-- == data0.end()); + CHECK(curr != data0.end()); + CHECK(curr); + CHECK(curr.has_value()); + CHECK_EQUAL(curr->data.value, sorted_data.back().data.value); + + prev = curr--; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(true)); + + curr = data0.get_root(); + CHECK(curr.has_value()); + CHECK_EQUAL(curr->data.value, sorted_data.at(sorted_data.size() / 2).data.value); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_iterator_default) + { + DataNDC0::iterator it; + + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + CHECK_EQUAL(0, it.get_balance_factor()); + CHECK_EQUAL(false, it.get_parent().has_value()); + CHECK_EQUAL(false, it.get_child(true).has_value()); + CHECK_EQUAL(false, it.get_child(false).has_value()); + + ++it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + --it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_const_iterator) + { + typedef etl::reverse_iterator rev_it; + + const DataNDC0 data0(unsorted_data.begin(), unsorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + +#ifdef ETL_AVL_DUMP_GRAPHVIZ + const auto tree_dump = to_graphviz(data0); + std::cout << tree_dump; +#endif + + bool are_equal = std::equal(data0.begin(), data0.end(), sorted_data.begin()); + CHECK(are_equal); + + are_equal = std::equal(rev_it(data0.cend()), rev_it(data0.cbegin()), sorted_data.rbegin()); + CHECK(are_equal); + + auto curr = data0.begin(); + CHECK(curr); + CHECK(curr.has_value()); + const auto& front = *curr; + CHECK_EQUAL(&front, curr.get()); + CHECK_EQUAL(front.data.value, sorted_data.front().data.value); + CHECK_EQUAL(curr->data.value, sorted_data.front().data.value); + auto prev = curr++; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(false)); + CHECK(curr != data0.begin()); + CHECK(prev == data0.begin()); + CHECK(prev-- == data0.begin()); + CHECK(prev == data0.end()); + + curr = data0.end(); + CHECK(!curr); + CHECK(!curr.has_value()); + CHECK(nullptr == curr.get()); + CHECK(curr-- == data0.end()); + CHECK(curr); + CHECK(curr.has_value()); + CHECK(curr != data0.end()); + CHECK_EQUAL(curr->data.value, sorted_data.back().data.value); + + prev = curr--; + CHECK(curr == prev.get_parent()); + CHECK(prev == curr.get_child(true)); + + curr = data0.get_root(); + CHECK(curr.has_value()); + CHECK_EQUAL(curr->data.value, sorted_data.at(sorted_data.size() / 2).data.value); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_const_iterator_default) + { + DataNDC0::const_iterator it; + + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + CHECK_EQUAL(0, it.get_balance_factor()); + CHECK_EQUAL(false, it.get_parent().has_value()); + CHECK_EQUAL(false, it.get_child(true).has_value()); + CHECK_EQUAL(false, it.get_child(false).has_value()); + + ++it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + + --it; + CHECK(nullptr == it.get()); + CHECK_EQUAL(false, it.has_value()); + CHECK_EQUAL(false, static_cast(it)); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_iterator_misuse_assertions) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + auto dereference_default_iterator = []() + { + DataNDC0::iterator it; + return (*it).data.value; + }; + CHECK_THROW(dereference_default_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto arrow_default_iterator = []() + { + DataNDC0::iterator it; + return it->data.value; + }; + CHECK_THROW(arrow_default_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto dereference_end_iterator = [&]() + { + return (*data0.end()).data.value; + }; + CHECK_THROW(dereference_end_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto arrow_end_iterator = [&]() + { + return data0.end()->data.value; + }; + CHECK_THROW(arrow_end_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto erase_default_iterator = [&]() + { + DataNDC0::iterator it; + data0.erase(it); + }; + CHECK_THROW(erase_default_iterator(), etl::intrusive_avl_tree_iterator_exception); + + auto erase_end_iterator = [&]() + { + data0.erase(data0.end()); + }; + CHECK_THROW(erase_end_iterator(), etl::intrusive_avl_tree_iterator_exception); + + // Try foreign tree begin iterator. + { + DataNDC0 data0b; + auto erase_foreign_iterator = [&]() + { + data0b.erase(data0.begin()); + }; + CHECK_THROW(erase_foreign_iterator(), etl::intrusive_avl_tree_iterator_exception); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + auto iterator = data0.find(ItemNDCNode::always_less); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::always_greater); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::CompareByValue{5}); + CHECK(iterator.has_value()); + CHECK(iterator != data0.end()); + CHECK_EQUAL(iterator->data, sorted_data[5].data); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_const) + { + const DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + auto iterator = data0.find(ItemNDCNode::always_less); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::always_greater); + CHECK(!iterator.has_value()); + CHECK(iterator == data0.end()); + + iterator = data0.find(ItemNDCNode::CompareByValue{5}); + CHECK(iterator != data0.end()); + CHECK(iterator.has_value()); + CHECK_EQUAL(iterator->data, sorted_data[5].data); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_throwing) + { + const DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + + const ItemNDCNode::CompareByValue throwing_compare{0, etl::exception("13", __FILE__, __LINE__)}; + CHECK_THROW(data0.find(throwing_compare), etl::exception); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert) + { + DataNDC0 data0; + + ItemNDCNode node0a(0, 0); + ItemNDCNode node0b(0, 1); + + // Insert new. + { + CHECK(data0.empty()); + const auto it_mod = data0.find_or_insert(ItemNDCNode::CompareByValue{0}, [&node0a] { return &node0a; }); + CHECK(!data0.empty()); + CHECK_EQUAL(1, data0.size()); + CHECK_EQUAL(data0.min().get(), &node0a); + CHECK_EQUAL(data0.max().get(), &node0a); + verify_tree(data0); + + CHECK(it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK(it_mod.first != data0.end()); + CHECK_EQUAL(&node0a, it_mod.first.get()); + } + + // Find existing. + { + const auto it_mod = data0.find_or_insert(ItemNDCNode::CompareByValue{0}, [&node0b] { return &node0b; }); + + CHECK(!it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK(it_mod.first != data0.end()); + CHECK_EQUAL(&node0a, it_mod.first.get()); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_unsorted) + { + DataNDC0 data0; + verify_tree(data0); + + for (auto& item : unsorted_data) + { + data0.find_or_insert(ItemNDCNode::CompareByValue{item.data.value}, [&item] { return &item; }); + verify_tree(data0); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_null_factory) + { + DataNDC0 data0; + + const auto it_mod = data0.find_or_insert(ItemNDCNode::always_greater, [] { return nullptr; }); + CHECK(data0.empty()); + CHECK_EQUAL(0, data0.size()); + verify_tree(data0); + + CHECK(!it_mod.second); + CHECK(!it_mod.first.has_value()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_already_linked) + { + DataNDC0 data0; + + ItemNDCNode node0(0, 0); + + auto insert = [&]() + { + return data0.find_or_insert(ItemNDCNode::always_greater, [&node0] { return &node0; }); + }; + + auto it_mod = insert(); + CHECK(!data0.empty()); + CHECK_EQUAL(1, data0.size()); + verify_tree(data0); + + // Insert the same node again -> should throw. + { + CHECK_THROW(insert(), etl::intrusive_avl_tree_value_is_already_linked); + CHECK(!data0.empty()); + CHECK_EQUAL(1, data0.size()); + verify_tree(data0); + } + + // But it's ok to erase it first, and then reinsert. + { + data0.erase(it_mod.first); + CHECK_EQUAL(0, data0.size()); + + it_mod = insert(); + CHECK_EQUAL(1, data0.size()); + + CHECK(it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK(it_mod.first != data0.end()); + CHECK_EQUAL(&node0, it_mod.first.get()); + verify_tree(data0); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_or_insert_throwing) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + CHECK_EQUAL(sorted_data.size(), data0.size()); + + // Try throwing factory. + { + auto throwing_action = [&]() + { + return data0.find_or_insert(ItemNDCNode::always_greater, []() -> ItemNDCNode* { throw etl::exception("123", __FILE__, __LINE__); }); + }; + CHECK_THROW(throwing_action(), etl::exception); + CHECK_EQUAL(sorted_data.size(), data0.size()); + verify_tree(data0); + CHECK(std::equal(data0.begin(), data0.end(), sorted_data.begin())); + } + + // Try throwing comparator. + { + auto throwing_action = [&]() + { + return data0.find_or_insert([](const ItemNDCNode&) -> int { throw etl::exception("321", __FILE__, __LINE__); }, + []() -> ItemNDCNode* { return nullptr; }); + }; + CHECK_THROW(throwing_action(), etl::exception); + CHECK_EQUAL(sorted_data.size(), data0.size()); + verify_tree(data0); + CHECK(std::equal(data0.begin(), data0.end(), sorted_data.begin())); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_erase) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + size_t expected_size = sorted_data.size(); + for (const auto& item : unsorted_data) + { + const DataNDC0::iterator it = data0.find(ItemNDCNode::CompareByValue{item.data.value}); + CHECK(it != data0.end()); + + CHECK_EQUAL(expected_size, data0.size()); + const DataNDC0::iterator next_it = data0.erase(it); + CHECK_EQUAL(--expected_size, data0.size()); + verify_tree(data0); + if (next_it != data0.end()) + { + CHECK(next_it.has_value()); + const auto& next = *next_it; + CHECK(item < next); + } + } + CHECK(data0.empty()); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_erase_const_it) + { + DataNDC0 data0(sorted_data.begin(), sorted_data.end(), ItemNDCNode::compare::cmp); + verify_tree(data0); + + size_t expected_size = sorted_data.size(); + for (const auto& item : unsorted_data) + { + const DataNDC0::const_iterator it = data0.find(ItemNDCNode::CompareByValue{item.data.value}); + CHECK(it != data0.end()); + + CHECK_EQUAL(expected_size, data0.size()); + const DataNDC0::iterator next_it = data0.erase(it); + CHECK_EQUAL(--expected_size, data0.size()); + verify_tree(data0); + if (next_it != data0.end()) + { + CHECK(next_it.has_value()); + const auto& next = *next_it; + CHECK(item < next); + } + } + CHECK(data0.empty()); + } + + //************************************************************************* + struct Inserter + { + InitialDataNDC& nodes; + + template + void operator()(Tree& tree, const size_t n) const + { + const ItemNDCNode::CompareByValue comp{static_cast(n)}; + const auto it = tree.find(comp); + if (it == tree.end()) + { + bool factory_was_called = false; + auto it_mod = tree.find_or_insert(comp, + [&] + { + factory_was_called = true; + return &nodes.at(n); + }); + CHECK(it_mod.second); + CHECK(factory_was_called); + CHECK_EQUAL(n, it_mod.first->data.value); + CHECK_EQUAL(n, it_mod.first->data.index); + } + else + { + CHECK_EQUAL(n, it->data.value); + CHECK_EQUAL(n, it->data.index); + auto it_mod = tree.find_or_insert(comp, + [] + { + CHECK_MESSAGE("Should not be called!") + CHECK(false); + return nullptr; + }); + CHECK(it == it_mod.first); + CHECK_FALSE(it_mod.second); + } + } + }; + struct Eraser + { + template + void operator()(Tree& tree, const size_t n) const + { + const ItemNDCNode::CompareByValue comp{static_cast(n)}; + auto it = tree.find(comp); + if (it != tree.end()) + { + CHECK_EQUAL(n, it->data.value); + CHECK_EQUAL(n, it->data.index); + tree.erase(it); + it = tree.find(comp); + CHECK(it == tree.end()); + } + } + }; + TEST_FIXTURE(SetupFixture, test_random_insert_and_erase) + { + // Deliberately seeded with fixed number, so that if it fails then always in the same way. + std::mt19937 mte(123); + + InitialDataNDC nodes; + constexpr size_t N = 256; + for (size_t i = 0; i < N; ++i) + { + nodes.emplace_back(static_cast(i), static_cast(i)); + } + + DataNDC0 data0; + DataNDC1 data1; + + auto makeRandomNumber = [&mte](const size_t n) -> size_t + { + return mte() % n; + }; + const Eraser erase_item; + const Inserter insert_item{nodes}; + + for (size_t i = 0; i < 10000; ++i) + { + const auto number = makeRandomNumber(N); + switch (makeRandomNumber(4)) + { + case 0: erase_item(data0, number); break; + case 1: erase_item(data1, number); break; + case 2: insert_item(data0, number); break; + case 3: insert_item(data1, number); break; + } + + verify_tree(data0); + verify_tree(data1); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_item) + { + const DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + const ItemMNode min_item{std::move(sorted_data_moveable.front())}; + CHECK(min_item.data.valid); + CHECK_FALSE(sorted_data_moveable.front().data.valid); + CHECK(min_item.data.value == 0); + auto it = data0.begin(); + CHECK(it.get() == &min_item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + + const ItemMNode max_item{std::move(sorted_data_moveable.back())}; + CHECK(max_item.data.value == static_cast(sorted_data_moveable.size() - 1)); + it = --data0.end(); + CHECK(it.get() == &max_item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + + auto item_idx = sorted_data_moveable.size() / 2; + const ItemMNode root_item{std::move(sorted_data_moveable.at(item_idx))}; + CHECK_EQUAL(root_item.data.value, item_idx); + it = data0.get_root(); + CHECK(it.has_value()); + CHECK(it.get() == &root_item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + + item_idx = 11; + const ItemMNode item{std::move(sorted_data_moveable.at(item_idx))}; + CHECK(item.data.value == static_cast(item_idx)); + it = data0.find(ItemMNode::CompareByValue{static_cast(item_idx)}); + CHECK(it.get() == &item); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_move) + { + DataM data0a(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + const DataM data0b(std::move(data0a)); + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + CHECK_EQUAL(sorted_data_moveable.size(), data0b.size()); + verify_tree(data0b); + CHECK(std::equal(data0b.begin(), data0b.end(), sorted_data_moveable.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_move_empty) + { + DataM data0a; + const DataM data0b(std::move(data0a)); + + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + CHECK(data0b.empty()); + CHECK_EQUAL(0, data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_constructor_move_single_and_mutate) + { + InitialDataM nodes; + nodes.emplace_back(1); + nodes.emplace_back(2); + + DataM data0a(nodes.begin(), nodes.begin() + 1, ItemMNode::compare); + verify_tree(data0a); + + DataM data0b(std::move(data0a)); + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + CHECK_EQUAL(1, data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + + const auto it_mod = data0b.find_or_insert(ItemMNode::CompareByValue{2}, [&nodes] { return &nodes.back(); }); + CHECK(it_mod.second); + CHECK(it_mod.first.has_value()); + CHECK_EQUAL(2, it_mod.first->data.value); + CHECK_EQUAL(2, data0b.size()); + verify_tree(data0b); + + const std::vector expected_values{1, 2}; + std::vector actual_values; + for (const auto& it : data0b) + { + actual_values.push_back(it.data.value); + } + CHECK(actual_values == expected_values); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment_item) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + // Replace item 11 with a temp item. + { + ItemMNode replacement(100); + replacement = std::move(sorted_data_moveable.at(11)); + + CHECK(replacement.data.valid); + CHECK_FALSE(sorted_data_moveable.at(11).data.valid); + CHECK_TRUE(replacement.data.valid); + CHECK_EQUAL(11, replacement.data.value); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + CHECK(data0.find(ItemMNode::CompareByValue{11}).get() == &replacement); + verify_tree(data0); + } + CHECK_EQUAL(sorted_data_moveable.size() - 1, data0.size()); + verify_tree(data0); + + // Try to move (item 5) into already linked (item 3). + { + sorted_data_moveable.at(3) = std::move(sorted_data_moveable.at(5)); + CHECK_TRUE(sorted_data_moveable.at(3).data.valid); + CHECK_FALSE(sorted_data_moveable.at(5).data.valid); + CHECK_EQUAL(5, sorted_data_moveable.at(3).data.value); + CHECK_EQUAL(sorted_data_moveable.size() - 2, data0.size()); + verify_tree(data0); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment_item_self) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + ItemMNode& self = sorted_data_moveable.at(11); + ItemMNode* const self_ptr = &self; + *self_ptr = std::move(self); + + CHECK(self.data.valid); + CHECK_EQUAL(11, self.data.value); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + CHECK(data0.find(ItemMNode::CompareByValue{11}).get() == self_ptr); + verify_tree(data0); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_move_assignment_item_over_linked_target) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + ItemMNode replacement(100); + + const auto inserted = data0.find_or_insert(ItemMNode::CompareByValue{100}, [&replacement] { return &replacement; }); + CHECK(inserted.second); + CHECK_EQUAL(sorted_data_moveable.size() + 1U, data0.size()); + verify_tree(data0); + + replacement = std::move(sorted_data_moveable.at(11)); + + CHECK(replacement.data.valid); + CHECK_FALSE(sorted_data_moveable.at(11).data.valid); + CHECK_EQUAL(11, replacement.data.value); + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + CHECK(data0.find(ItemMNode::CompareByValue{11}).get() == &replacement); + CHECK(data0.find(ItemMNode::CompareByValue{100}) == data0.end()); + verify_tree(data0); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_assignment_move) + { + InitialDataM lhs_nodes; + lhs_nodes.emplace_back(40); + lhs_nodes.emplace_back(41); + InitialDataM rhs_nodes; + rhs_nodes.emplace_back(1); + rhs_nodes.emplace_back(2); + rhs_nodes.emplace_back(3); + + DataM data0a(lhs_nodes.begin(), lhs_nodes.end(), ItemMNode::compare); + DataM data0b(rhs_nodes.begin(), rhs_nodes.end(), ItemMNode::compare); + verify_tree(data0a); + verify_tree(data0b); + + data0a = std::move(data0b); + + CHECK(data0b.empty()); + CHECK_EQUAL(0, data0b.size()); + CHECK_EQUAL(rhs_nodes.size(), data0a.size()); + verify_tree(data0a); + verify_tree(data0b); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + + DataM recovery; + const auto it40 = recovery.find_or_insert(ItemMNode::CompareByValue{40}, [&lhs_nodes] { return &lhs_nodes[0]; }); + const auto it41 = recovery.find_or_insert(ItemMNode::CompareByValue{41}, [&lhs_nodes] { return &lhs_nodes[1]; }); + CHECK(it40.second); + CHECK(it41.second); + CHECK_EQUAL(2, recovery.size()); + verify_tree(recovery); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_assignment_move_self) + { + DataM data0(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + verify_tree(data0); + + DataM* const self_ptr = &data0; + *self_ptr = std::move(data0); + + CHECK_EQUAL(sorted_data_moveable.size(), data0.size()); + verify_tree(data0); + for (size_t i = 0; i < sorted_data_moveable.size(); ++i) + { + CHECK(data0.find(ItemMNode::CompareByValue{static_cast(i)}).get() == &sorted_data_moveable[i]); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_member) + { + InitialDataM lhs_nodes; + lhs_nodes.emplace_back(40); + lhs_nodes.emplace_back(41); + InitialDataM rhs_nodes; + rhs_nodes.emplace_back(1); + rhs_nodes.emplace_back(2); + rhs_nodes.emplace_back(3); + + DataM data0a(lhs_nodes.begin(), lhs_nodes.end(), ItemMNode::compare); + DataM data0b(rhs_nodes.begin(), rhs_nodes.end(), ItemMNode::compare); + verify_tree(data0a); + verify_tree(data0b); + + data0a.swap(data0b); + + CHECK_EQUAL(rhs_nodes.size(), data0a.size()); + CHECK_EQUAL(lhs_nodes.size(), data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + CHECK(std::equal(data0b.begin(), data0b.end(), lhs_nodes.begin())); + + // Try also self-swap. + data0a.swap(data0a); + verify_tree(data0a); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_swap_adl) + { + InitialDataM lhs_nodes; + lhs_nodes.emplace_back(50); + lhs_nodes.emplace_back(51); + InitialDataM rhs_nodes; + rhs_nodes.emplace_back(5); + rhs_nodes.emplace_back(6); + rhs_nodes.emplace_back(7); + + DataM data0a(lhs_nodes.begin(), lhs_nodes.end(), ItemMNode::compare); + DataM data0b(rhs_nodes.begin(), rhs_nodes.end(), ItemMNode::compare); + verify_tree(data0a); + verify_tree(data0b); + + swap(data0a, data0b); + + CHECK_EQUAL(rhs_nodes.size(), data0a.size()); + CHECK_EQUAL(lhs_nodes.size(), data0b.size()); + verify_tree(data0a); + verify_tree(data0b); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + CHECK(std::equal(data0b.begin(), data0b.end(), lhs_nodes.begin())); + + // Try also self-swap. + swap(data0a, data0a); + verify_tree(data0a); + CHECK(std::equal(data0a.begin(), data0a.end(), rhs_nodes.begin())); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_clear) + { + DataM data0a(sorted_data_moveable.begin(), sorted_data_moveable.end(), ItemMNode::compare); + CHECK_EQUAL(sorted_data_moveable.size(), data0a.size()); + data0a.clear(); + CHECK(data0a.empty()); + CHECK_EQUAL(0, data0a.size()); + verify_tree(data0a); + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_in_order) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // In-order, LNR + { + std::vector order; + data0.visit_in_order(false, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 1, 2, 3, 4, 5}; + CHECK(order == expected); + } + + // Reverse In-order, RNL + { + std::vector order; + data0.visit_in_order(true, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 3, 2, 1, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_in_order_const) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + const DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // In-order, LNR + { + std::vector order; + data0.visit_in_order(false, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 1, 2, 3, 4, 5}; + CHECK(order == expected); + } + + // Reverse In-order, RNL + { + std::vector order; + data0.visit_in_order(true, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 3, 2, 1, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_post_order) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Post-order, LRN + { + std::vector order; + data0.visit_post_order(false, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 2, 1, 5, 4, 3}; + CHECK(order == expected); + } + + // Reverse post-order, RLN + { + std::vector order; + data0.visit_post_order(true, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 2, 0, 1, 3}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_post_order_const) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + const DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Post-order, LRN + { + std::vector order; + data0.visit_post_order(false, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{0, 2, 1, 5, 4, 3}; + CHECK(order == expected); + } + + // Reverse post-order, RLN + { + std::vector order; + data0.visit_post_order(true, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{5, 4, 2, 0, 1, 3}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_pre_order) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Pre-order, NLR + { + std::vector order; + data0.visit_pre_order(false, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 1, 0, 2, 4, 5}; + CHECK(order == expected); + } + + // Reverse pre-order, NRL + { + std::vector order; + data0.visit_pre_order(true, [&order](ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 4, 5, 1, 2, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, visit_pre_order_const) + { + const std::vector ids{0, 1, 2, 3, 4, 5}; + InitialDataNDC data; + for (const auto idx : ids) + { + data.emplace_back(idx, idx); + } + const DataNDC0 data0(data.begin(), data.end(), ItemNDCNode::compare::cmp); + + // Pre-order, NLR + { + std::vector order; + data0.visit_pre_order(false, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 1, 0, 2, 4, 5}; + CHECK(order == expected); + } + + // Reverse pre-order, NRL + { + std::vector order; + data0.visit_pre_order(true, [&order](const ItemNDCNode& item) { order.push_back(item.data.value); }); + const std::vector expected{3, 4, 5, 1, 2, 0}; + CHECK(order == expected); + } + } + + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_lower_upper_bound) + { + // idx 1 5 0 2 4 6 8 9 7 3 + // vals = [7, 7, 10, 10, 10, 10, 10, 10, 12, 15] + InitialDataNDC duplicate_data; + duplicate_data.emplace_back(10, 0); + duplicate_data.emplace_back(7, 1); + duplicate_data.emplace_back(10, 2); + duplicate_data.emplace_back(15, 3); + duplicate_data.emplace_back(10, 4); + duplicate_data.emplace_back(7, 5); + duplicate_data.emplace_back(10, 6); + duplicate_data.emplace_back(12, 7); + duplicate_data.emplace_back(10, 8); + duplicate_data.emplace_back(10, 9); + + DataNDC0 data0(duplicate_data.begin(), duplicate_data.end(), ItemNDCNode::compare_append_dups); + verify_tree(data0); + + CHECK(data0.lower_bound(ItemNDCNode::always_greater) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::always_greater) == data0.end()); + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::always_less)->data.index, 1); // 7(1) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::always_less)->data.index, 1); // 7(1) + + // 0 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(0))->data.index, 1); // 7(1) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(0))->data.index, 1); // 7(1) + // 7 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(7))->data.index, 1); // 7(1) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(7))->data.index, 0); // 10(0) + // 8 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(8))->data.index, 0); // 10(0) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(8))->data.index, 0); // 10(0) + // 10 + const auto lower10 = data0.lower_bound(ItemNDCNode::CompareByValue(10)); + const auto upper10 = data0.upper_bound(ItemNDCNode::CompareByValue(10)); + CHECK_EQUAL(lower10->data.value, 10); // 10(0) + CHECK_EQUAL(lower10->data.index, 0); + CHECK_EQUAL(upper10->data.value, 12); // 12(7) + CHECK_EQUAL(upper10->data.index, 7); + CHECK_EQUAL(std::distance(lower10, upper10), 6); // duplicates of 10s + // 11 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(11))->data.index, 7); // 12(7) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(11))->data.index, 7); // 12(7) + // 12 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(12))->data.index, 7); // 12(7) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(12))->data.index, 3); // 15(3) + // 13 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(13))->data.index, 3); // 15(3) + CHECK_EQUAL(data0.upper_bound(ItemNDCNode::CompareByValue(13))->data.index, 3); // 15(3) + // 15 + CHECK_EQUAL(data0.lower_bound(ItemNDCNode::CompareByValue(15))->data.index, 3); // 15(3) + CHECK(data0.upper_bound(ItemNDCNode::CompareByValue(15)) == data0.end()); + // 16 + CHECK(data0.lower_bound(ItemNDCNode::CompareByValue(16)) == data0.end()); + CHECK(data0.upper_bound(ItemNDCNode::CompareByValue(16)) == data0.end()); + + // Make sure that `lower_bound` and `upper_bound` work on const tree. + const DataNDC0& const_data0 = data0; + CHECK_EQUAL(const_data0.lower_bound(ItemNDCNode::CompareByValue(12))->data.index, 7); // 12(7) + CHECK_EQUAL(const_data0.upper_bound(ItemNDCNode::CompareByValue(12))->data.index, 3); // 15(3) + } + } + +} // namespace diff --git a/test/test_invert.cpp b/test/test_invert.cpp index 9559137f..b9138eb5 100644 --- a/test/test_invert.cpp +++ b/test/test_invert.cpp @@ -112,5 +112,15 @@ namespace bool isEqual = std::equal(output2.begin(), output2.end(), result2b.begin(), Compare()); CHECK(isEqual); } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_invert_constexpr) + { + constexpr etl::invert inv(0, 255); + static_assert(inv(100) == 155, "constexpr invert"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_iterator.cpp b/test/test_iterator.cpp index 34fd3157..db018c40 100644 --- a/test/test_iterator.cpp +++ b/test/test_iterator.cpp @@ -550,6 +550,28 @@ namespace CHECK_EQUAL(true, mitr->valid); } + //************************************************************************* + TEST(test_move_iterator_compound_assignment_returns_reference) + { + int data[] = {10, 20, 30, 40, 50}; + + etl::move_iterator mitr(&data[0]); + + // operator+= must return a reference to *this + etl::move_iterator& ref_plus = (mitr += 2); + CHECK_EQUAL(&mitr, &ref_plus); + CHECK_EQUAL(30, *mitr); + + // operator-= must return a reference to *this + etl::move_iterator& ref_minus = (mitr -= 1); + CHECK_EQUAL(&mitr, &ref_minus); + CHECK_EQUAL(20, *mitr); + + // Chaining should work + (mitr += 1) += 1; + CHECK_EQUAL(40, *mitr); + } + //************************************************************************* TEST(test_move_iterator_subtraction) { @@ -1408,6 +1430,28 @@ namespace static_assert(etl::is_range_v == false, "Expected non range"); } +#endif + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_move_iterator_constexpr_ctor) + { + static constexpr int data[] = {10, 20, 30}; + constexpr etl::move_iterator mi(&data[0]); + static_assert(mi.base() == &data[0], "constexpr ctor"); + CHECK(true); + } + + //************************************************************************* + TEST(test_move_iterator_constexpr_operators) + { + static constexpr int data[] = {10, 20, 30}; + constexpr etl::move_iterator mi1(&data[0]); + constexpr etl::move_iterator mi2(&data[1]); + static_assert(mi1 != mi2, "constexpr operator!="); + static_assert(mi1 < mi2, "constexpr operator<"); + CHECK(true); + } #endif } } // namespace diff --git a/test/test_memory.cpp b/test/test_memory.cpp index e0e457c6..325e07ca 100644 --- a/test/test_memory.cpp +++ b/test/test_memory.cpp @@ -3006,5 +3006,17 @@ namespace unsigned char zeroes[sizeof(Data)] = {0}; CHECK(memcmp(buffer, zeroes, sizeof(Data)) == 0); } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_default_delete_constexpr_copy_ctor) + { + constexpr etl::default_delete d1; + constexpr etl::default_delete d2(d1); + (void)d2; + static_assert(sizeof(d2) > 0, "constexpr default_delete copy ctor"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_pseudo_moving_average.cpp b/test/test_pseudo_moving_average.cpp index 8d1496e6..f147a5cb 100644 --- a/test/test_pseudo_moving_average.cpp +++ b/test/test_pseudo_moving_average.cpp @@ -344,5 +344,15 @@ namespace CHECK_CLOSE(2.82, cma.value(), 0.01); } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_pseudo_moving_average_constexpr_ctor) + { + constexpr etl::pseudo_moving_average pma(0); + static_assert(sizeof(pma) > 0, "constexpr ctor"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_rescale.cpp b/test/test_rescale.cpp index c0e3c610..1b7196f5 100644 --- a/test/test_rescale.cpp +++ b/test/test_rescale.cpp @@ -84,5 +84,15 @@ namespace bool isEqual = std::equal(output2.begin(), output2.end(), result2.begin(), Compare()); CHECK(isEqual); } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_rescale_constexpr) + { + constexpr etl::rescale rs(0, 100, 0, 1000); + static_assert(rs(50) == 500, "constexpr rescale"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_threshold.cpp b/test/test_threshold.cpp index bccb6bc7..629a85a5 100644 --- a/test/test_threshold.cpp +++ b/test/test_threshold.cpp @@ -115,5 +115,16 @@ namespace bool isEqual = std::equal(output2.begin(), output2.end(), result2b.begin(), Compare()); CHECK(isEqual); } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_threshold_constexpr) + { + constexpr etl::threshold th(50, 1, 0); + static_assert(th(40) == 1, "constexpr threshold below"); + static_assert(th(60) == 0, "constexpr threshold above"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/test_utility.cpp b/test/test_utility.cpp index 08cf2e75..5c01d079 100644 --- a/test/test_utility.cpp +++ b/test/test_utility.cpp @@ -1093,5 +1093,29 @@ namespace CHECK_FALSE(pw1 == pw2); // This would FAIL with the old < > based comparison } + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_pair_constexpr_copy_ctor) + { + constexpr etl::pair p1(1, 2); + constexpr etl::pair p2(p1); + static_assert(p2.first == 1, "constexpr pair copy ctor first"); + static_assert(p2.second == 2, "constexpr pair copy ctor second"); + CHECK(true); + } + + //************************************************************************* + TEST(test_coordinate_2d_constexpr_ctors) + { + constexpr etl::coordinate_2d c1; + constexpr etl::coordinate_2d c2(3, 4); + static_assert(c1.x == 0, "constexpr default ctor x"); + static_assert(c1.y == 0, "constexpr default ctor y"); + static_assert(c2.x == 3, "constexpr value ctor x"); + static_assert(c2.y == 4, "constexpr value ctor y"); + CHECK(true); + } +#endif } } // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index 57fee1e8..e4b0e1cf 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3787,6 +3787,7 @@ + @@ -7201,6 +7202,26 @@ true true + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true true @@ -10642,6 +10663,7 @@ + false false diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 7f79e3bc..d305c89c 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -372,6 +372,9 @@ ETL\Containers + + ETL\Containers + ETL\Containers @@ -1865,6 +1868,9 @@ Tests\Containers + + Tests\Containers + Tests\Containers @@ -3017,6 +3023,9 @@ Tests\Syntax Checks\Source + + Tests\Syntax Checks\Source + Tests\Syntax Checks\Source